diff --git a/CHANGES.md b/CHANGES.md index 17d4dc77..c86c29cf 100755 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,13 +12,23 @@ ## version 10.5.00 - Modifications : - - Gestion des module dans l'interface d'administration. - - à compléter.. + - Gestion des modules dans l'interface d'administration. + +## version 10.4.05 +- Mise à jour : + - SiteMapGenerator 4.3.1 +- Modifications : + - Bouton de remontée, position plus haute et zindex augmenté. + - Éviter le chevauchement du pied de age fixe au-dessus du corps de page. +- Corrections : + - Marges du pied de page fixe placé en dehors du site. + - TinyMCE couleurs du sélecteur de paragraphe et de headers lorsque le fond est transparent. + - Thème administration, couleur du lien dans un bloc H4. ## version 10.4.04 - Correction : - Module Blog : balise non fermée dans les commentaires. -- Modifications : +- Modifications : -Constantes de modules. ## version 10.4.03 diff --git a/README.md b/README.md index 7a84065d..54fc703b 100755 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# ZwiiCMS 10.4.04 +# ZwiiCMS 10.4.05 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. diff --git a/core/class/SitemapGenerator.class.php b/core/class/SitemapGenerator.class.php deleted file mode 100755 index f15350d0..00000000 --- a/core/class/SitemapGenerator.class.php +++ /dev/null @@ -1,540 +0,0 @@ -urls = new \SplFixedArray(); - $this->baseURL = $baseURL; - $this->basePath = $basePath; - $this->document = new \DOMDocument("1.0"); - $this->document->preserveWhiteSpace = false; - $this->document->formatOutput = true; - } - - /** - * Use this to add many URL at one time. - * Each inside array can have 1 to 4 fields. - * @param $urlsArray - * @throws \InvalidArgumentException - */ - public function addUrls($urlsArray) - { - if (!is_array($urlsArray)) { - throw new \InvalidArgumentException("Array as argument should be given."); - } - foreach ($urlsArray as $url) { - $this->addUrl( - isset($url[0]) ? $url[0] : null, - isset($url[1]) ? $url[1] : null, - isset($url[2]) ? $url[2] : null, - isset($url[3]) ? $url[3] : null - ); - } - } - - /** - * Use this to add single URL to sitemap. - * @param string $url URL - * @param \DateTime $lastModified When it was modified, use ISO 8601 - * @param string $changeFrequency How often search engines should revisit this URL - * @param string $priority Priority of URL on You site - * @see http://en.wikipedia.org/wiki/ISO_8601 - * @see http://php.net/manual/en/function.date.php - * @throws \InvalidArgumentException - */ - public function addUrl($url, \DateTime $lastModified = null, $changeFrequency = null, $priority = null) - { - if ($url == null) { - throw new \InvalidArgumentException("URL is mandatory. At least one argument should be given."); - } - $urlLength = extension_loaded('mbstring') ? mb_strlen($url) : strlen($url); - if ($urlLength > 2048) { - throw new \InvalidArgumentException( - "URL length can't be bigger than 2048 characters. - Note, that precise url length check is guaranteed only using mb_string extension. - Make sure Your server allow to use mbstring extension." - ); - } - $tmp = new \SplFixedArray(1); - - $tmp[self::URL_PARAM_LOC] = $url; - - if (isset($lastModified)) { - $tmp->setSize(2); - $tmp[self::URL_PARAM_LASTMOD] = $lastModified->format(\DateTime::ATOM); - } - - if (isset($changeFrequency)) { - $tmp->setSize(3); - $tmp[self::URL_PARAM_CHANGEFREQ] = $changeFrequency; - } - - if (isset($priority)) { - $tmp->setSize(4); - $tmp[self::URL_PARAM_PRIORITY] = $priority; - } - - if ($this->urls->getSize() === 0) { - $this->urls->setSize(1); - } else { - if ($this->urls->getSize() === $this->urls->key()) { - $this->urls->setSize($this->urls->getSize() * 2); - } - } - - $this->urls[$this->urls->key()] = $tmp; - $this->urls->next(); - } - - - /** - * @throws \BadMethodCallException - * @throws \InvalidArgumentException - * @throws \LengthException - */ - public function createSitemap() - { - if (!isset($this->urls)) { - throw new \BadMethodCallException("To create sitemap, call addUrl or addUrls function first."); - } - if ($this->maxURLsPerSitemap > self::MAX_URLS_PER_SITEMAP) { - throw new \InvalidArgumentException( - "More than " . self::MAX_URLS_PER_SITEMAP . " URLs per single sitemap is not allowed." - ); - } - $generatorInfo = ''; - - - $sitemapHeader = '' . $generatorInfo . ' - - '; - - $sitemapIndexHeader = '' . $generatorInfo . ' - - '; - - - $nullUrls = 0; - foreach ($this->urls as $url) { - if (is_null($url)) { - $nullUrls++; - } - } - - $nonEmptyUrls = $this->urls->getSize() - $nullUrls; - - $chunks = ceil($nonEmptyUrls / $this->maxURLsPerSitemap); - - for ($chunkCounter = 0; $chunkCounter < $chunks; $chunkCounter++) { - $xml = new \SimpleXMLElement($sitemapHeader); - for ($urlCounter = $chunkCounter * $this->maxURLsPerSitemap; - $urlCounter < ($chunkCounter + 1) * $this->maxURLsPerSitemap && $urlCounter < $nonEmptyUrls; - $urlCounter++ - ) { - $row = $xml->addChild('url'); - - $row->addChild( - 'loc', - htmlspecialchars($this->baseURL . $this->urls[$urlCounter][self::URL_PARAM_LOC], ENT_QUOTES, 'UTF-8') - ); - - if ($this->urls[$urlCounter]->getSize() > 1) { - $row->addChild('lastmod', $this->urls[$urlCounter][self::URL_PARAM_LASTMOD]); - } - if ($this->urls[$urlCounter]->getSize() > 2) { - $row->addChild('changefreq', $this->urls[$urlCounter][self::URL_PARAM_CHANGEFREQ]); - } - if ($this->urls[$urlCounter]->getSize() > 3) { - $row->addChild('priority', $this->urls[$urlCounter][self::URL_PARAM_PRIORITY]); - } - } - if (strlen($xml->asXML()) > self::MAX_FILE_SIZE) { - throw new \LengthException( - "Sitemap size equals to " . strlen($xml->asXML()) - . " bytes is more than 10MB (" . self::MAX_FILE_SIZE . " bytes), - please decrease maxURLsPerSitemap variable." - ); - } - $this->sitemaps[] = $xml->asXML(); - } - if (count($this->sitemaps) > $this->maxSitemaps) { - throw new \LengthException( - "Sitemap index can contain {$this->maxSitemaps} sitemaps. - Perhaps You trying to submit too many maps." - ); - } - if (count($this->sitemaps) > 1) { - for ($i = 0; $i < count($this->sitemaps); $i++) { - $this->sitemaps[$i] = array( - str_replace(".xml", ($i + 1) . ".xml", $this->sitemapFileName), - $this->sitemaps[$i] - ); - } - $xml = new \SimpleXMLElement($sitemapIndexHeader); - foreach ($this->sitemaps as $sitemap) { - $row = $xml->addChild('sitemap'); - $row->addChild('loc', $this->baseURL . "/" . $this->getSitemapFileName(htmlentities($sitemap[0]))); - $row->addChild('lastmod', date('c')); - } - $this->sitemapFullURL = $this->baseURL . "/" . $this->sitemapIndexFileName; - $this->sitemapIndex = array( - $this->sitemapIndexFileName, - $xml->asXML() - ); - } else { - $this->sitemapFullURL = $this->baseURL . "/" . $this->getSitemapFileName(); - - $this->sitemaps[0] = array( - $this->sitemapFileName, - $this->sitemaps[0] - ); - } - } - - - /** - * Returns created sitemaps as array of strings. - * Use it You want to work with sitemap without saving it as files. - * @return array of strings - * @access public - */ - public function toArray() - { - if (isset($this->sitemapIndex)) { - return array_merge(array($this->sitemapIndex), $this->sitemaps); - } else { - return $this->sitemaps; - } - } - - /** - * Will write sitemaps as files. - * @access public - * @throws \BadMethodCallException - */ - public function writeSitemap() - { - if (!isset($this->sitemaps)) { - throw new \BadMethodCallException("To write sitemap, call createSitemap function first."); - } - if (isset($this->sitemapIndex)) { - $this->document->loadXML($this->sitemapIndex[1]); - $this->writeFile($this->document->saveXML(), $this->basePath, $this->sitemapIndex[0], true); - foreach ($this->sitemaps as $sitemap) { - $this->writeFile($sitemap[1], $this->basePath, $sitemap[0]); - } - } else { - $this->document->loadXML($this->sitemaps[0][1]); - $this->writeFile($this->document->saveXML(), $this->basePath, $this->sitemaps[0][0], true); - $this->writeFile($this->sitemaps[0][1], $this->basePath, $this->sitemaps[0][0]); - } - } - - - private function getSitemapFileName($name = null) - { - if (!$name) { - $name = $this->sitemapFileName; - } - if ($this->createGZipFile) { - $name .= ".gz"; - } - return $name; - } - - /** - * Save file. - * @param string $content - * @param string $filePath - * @param string $fileName - * @param bool $noGzip - * @return bool - * @access private - */ - private function writeFile($content, $filePath, $fileName, $noGzip = false) - { - if (!$noGzip && $this->createGZipFile) { - return $this->writeGZipFile($content, $filePath, $fileName); - } - $file = fopen($filePath . $fileName, 'w'); - fwrite($file, $content); - return fclose($file); - } - - /** - * Save GZipped file. - * @param string $content - * @param string $filePath - * @param string $fileName - * @return bool - * @access private - */ - private function writeGZipFile($content, $filePath, $fileName) - { - $fileName .= '.gz'; - $file = gzopen($filePath . $fileName, 'w'); - gzwrite($file, $content); - return gzclose($file); - } - - /** - * If robots.txt file exist, will update information about newly created sitemaps. - * If there is no robots.txt will, create one and put into it information about sitemaps. - * @access public - * @throws \BadMethodCallException - */ - public function updateRobots() - { - if (!isset($this->sitemaps)) { - throw new \BadMethodCallException("To update robots.txt, call createSitemap function first."); - } - $sampleRobotsFile = "User-agent: *\nAllow: /"; - if (file_exists($this->basePath . $this->robotsFileName)) { - $robotsFile = explode("\n", file_get_contents($this->basePath . $this->robotsFileName)); - $robotsFileContent = ""; - foreach ($robotsFile as $key => $value) { - if (substr($value, 0, 8) == 'Sitemap:') { - unset($robotsFile[$key]); - } else { - $robotsFileContent .= $value . "\n"; - } - } - $robotsFileContent .= "Sitemap: $this->sitemapFullURL"; - if (!isset($this->sitemapIndex)) { - $robotsFileContent .= "\nSitemap: " . $this->getSitemapFileName($this->sitemapFullURL); - } - file_put_contents($this->basePath . $this->robotsFileName, $robotsFileContent); - } else { - $sampleRobotsFile = $sampleRobotsFile . "\n\nSitemap: " . $this->sitemapFullURL; - if (!isset($this->sitemapIndex)) { - $sampleRobotsFile .= "\nSitemap: " . $this->getSitemapFileName($this->sitemapFullURL); - } - file_put_contents($this->basePath . $this->robotsFileName, $sampleRobotsFile); - } - } - - /** - * Will inform search engines about newly created sitemaps. - * Google, Ask, Bing and Yahoo will be noticed. - * If You don't pass yahooAppId, Yahoo still will be informed, - * but this method can be used once per day. If You will do this often, - * message that limit was exceeded will be returned from Yahoo. - * @param string $yahooAppId Your site Yahoo appid. - * @return array of messages and http codes from each search engine - * @access public - * @throws \BadMethodCallException - */ - public function submitSitemap($yahooAppId = null) - { - if (!isset($this->sitemaps)) { - throw new \BadMethodCallException("To submit sitemap, call createSitemap function first."); - } - if (!extension_loaded('curl')) { - throw new \BadMethodCallException("cURL library is needed to do submission."); - } - $searchEngines = $this->searchEngines; - $searchEngines[0] = isset($yahooAppId) ? - str_replace("USERID", $yahooAppId, $searchEngines[0][0]) : - $searchEngines[0][1]; - $result = array(); - for ($i = 0; $i < count($searchEngines); $i++) { - $submitSite = curl_init($searchEngines[$i] . htmlspecialchars($this->sitemapFullURL, ENT_QUOTES, 'UTF-8')); - curl_setopt($submitSite, CURLOPT_RETURNTRANSFER, true); - $responseContent = curl_exec($submitSite); - $response = curl_getinfo($submitSite); - $submitSiteShort = array_reverse(explode(".", parse_url($searchEngines[$i], PHP_URL_HOST))); - $result[] = array( - "site" => $submitSiteShort[1] . "." . $submitSiteShort[0], - "fullsite" => $searchEngines[$i] . htmlspecialchars($this->sitemapFullURL, ENT_QUOTES, 'UTF-8'), - "http_code" => $response['http_code'], - "message" => str_replace("\n", " ", strip_tags($responseContent)) - ); - } - return $result; - } - - - /** - * Returns array of URLs - * - * Converts internal SplFixedArray to array - * @return array - */ - public function getUrls() - { - $urls = $this->urls->toArray(); - - /** - * @var int $key - * @var \SplFixedArray $urlSplArr - */ - foreach ($urls as $key => $urlSplArr) { - if (!is_null($urlSplArr)) { - $urlArr = $urlSplArr->toArray(); - $url = []; - foreach ($urlArr as $paramIndex => $paramValue) { - switch ($paramIndex) { - case static::URL_PARAM_LOC: - $url['loc'] = $paramValue; - break; - case static::URL_PARAM_CHANGEFREQ: - $url['changefreq'] = $paramValue; - break; - case static::URL_PARAM_LASTMOD: - $url['lastmod'] = $paramValue; - break; - case static::URL_PARAM_PRIORITY: - $url['priority'] = $paramValue; - break; - default: - break; - } - } - $urls[$key] = $url; - } - } - - return $urls; - } - - public function countUrls() - { - return $this->urls->getSize(); - } -} diff --git a/core/class/autoload.php b/core/class/autoload.php index a9d16ed4..ac507c05 100755 --- a/core/class/autoload.php +++ b/core/class/autoload.php @@ -4,7 +4,9 @@ class autoload { public static function autoloader () { require_once 'core/class/helper.class.php'; require_once 'core/class/template.class.php'; - require_once 'core/class/SitemapGenerator.class.php'; + require_once 'core/class/sitemap/Runtime.class.php'; + require_once 'core/class/sitemap/FileSystem.class.php'; + require_once 'core/class/sitemap/SitemapGenerator.class.php'; require_once 'core/class/phpmailer/PHPMailer.class.php'; require_once 'core/class/phpmailer/Exception.class.php'; require_once 'core/class/phpmailer/SMTP.class.php'; diff --git a/core/class/helper.class.php b/core/class/helper.class.php index 34332ccd..503ea941 100755 --- a/core/class/helper.class.php +++ b/core/class/helper.class.php @@ -161,7 +161,7 @@ class helper { if (array_key_exists('UPDATE', $class_constants)) { $update = $value::UPDATE; } else { - $update = true; + $update = '0.0'; } // Constante DELETE if (array_key_exists('DELETE', $class_constants)) { @@ -170,13 +170,13 @@ class helper { $delete = true; } // Constante DATADIRECTORY - if ( array_key_exists('DATADIRECTORY', $class_constants) + if ( array_key_exists('DATADIRECTORY', $class_constants) && $class_constants['DATADIRECTORY'] !== [] && is_array($class_constants['DATADIRECTORY']) ) { $dataDirectory = $value::DATADIRECTORY; } else { - $dataDirectory = ['fr/module.json']; + $dataDirectory = []; } // Affection $modules [$value] = [ diff --git a/core/class/sitemap/FileSystem.class.php b/core/class/sitemap/FileSystem.class.php new file mode 100755 index 00000000..261102c6 --- /dev/null +++ b/core/class/sitemap/FileSystem.class.php @@ -0,0 +1,36 @@ +\n") + private $sitemapUrlCount = 0; + private $generatedFiles = []; + + /** + * @param string $baseURL You site URL + * @param string $basePath Relative path where sitemap and robots should be stored. + * @param FileSystem|null $fs + * @param Runtime|null $runtime + */ + public function __construct(string $baseURL, string $basePath = "", FileSystem $fs = null, Runtime $runtime = null) + { + $this->urls = []; + $this->baseURL = rtrim($baseURL, '/'); + + if ($fs === null) { + $this->fs = new FileSystem(); + } else { + $this->fs = $fs; + } + + if ($runtime === null) { + $this->runtime = new Runtime(); + } else { + $this->runtime = $runtime; + } + + if ($this->runtime->is_writable($basePath) === false) { + throw new InvalidArgumentException( + sprintf('the provided basePath (%s) should be a writable directory,', $basePath) . + ' please check its existence and permissions' + ); + } + if (strlen($basePath) > 0 && substr($basePath, -1) != DIRECTORY_SEPARATOR) { + $basePath = $basePath . DIRECTORY_SEPARATOR; + } + $this->basePath = $basePath; + + $this->xmlWriter = $this->createXmlWriter(); + $this->flushedSitemapFilenameFormat = sprintf("sm-%%d-%d.xml", time()); + } + + private function createXmlWriter(): XMLWriter + { + $w = new XMLWriter(); + $w->openMemory(); + $w->setIndent(true); + return $w; + } + + /** + * @param string $filename + * @return SitemapGenerator + */ + public function setSitemapFilename(string $filename = ''): SitemapGenerator + { + if (strlen($filename) === 0) { + throw new InvalidArgumentException('sitemap filename should not be empty'); + } + if (pathinfo($filename, PATHINFO_EXTENSION) !== 'xml') { + throw new InvalidArgumentException('sitemap filename should have *.xml extension'); + } + $this->sitemapFileName = $filename; + return $this; + } + + /** + * @param string $filename + * @return $this + */ + public function setSitemapIndexFilename(string $filename = ''): SitemapGenerator + { + if (strlen($filename) === 0) { + throw new InvalidArgumentException('filename should not be empty'); + } + $this->sitemapIndexFileName = $filename; + return $this; + } + + /** + * @param string $filename + * @return $this + */ + public function setRobotsFileName(string $filename): SitemapGenerator + { + if (strlen($filename) === 0) { + throw new InvalidArgumentException('filename should not be empty'); + } + $this->robotsFileName = $filename; + return $this; + } + + /** + * @param int $value + * @return $this + */ + public function setMaxUrlsPerSitemap(int $value): SitemapGenerator + { + if ($value < 1 || self::MAX_URLS_PER_SITEMAP < $value) { + throw new OutOfRangeException( + sprintf('value %d is out of range 1-%d', $value, self::MAX_URLS_PER_SITEMAP) + ); + } + $this->maxUrlsPerSitemap = $value; + return $this; + } + + public function enableCompression(): SitemapGenerator + { + $this->isCompressionEnabled = true; + return $this; + } + + public function disableCompression(): SitemapGenerator + { + $this->isCompressionEnabled = false; + return $this; + } + + public function isCompressionEnabled(): bool + { + return $this->isCompressionEnabled; + } + + public function validate( + string $path, + DateTime $lastModified = null, + string $changeFrequency = null, + float $priority = null, + array $alternates = null, + array $extensions = []) + { + if (!(1 <= mb_strlen($path) && mb_strlen($path) <= self::MAX_URL_LEN)) { + throw new InvalidArgumentException( + sprintf("The urlPath argument length must be between 1 and %d.", self::MAX_URL_LEN) + ); + } + if ($changeFrequency !== null && !in_array($changeFrequency, $this->validChangefreqValues)) { + throw new InvalidArgumentException( + 'The change frequency argument should be one of: %s' . implode(',', $this->validChangefreqValues) + ); + } + if ($priority !== null && !in_array($priority, $this->validPriorities)) { + throw new InvalidArgumentException("Priority argument should be a float number in the range [0.0..1.0]"); + } + if ($extensions !== null && isset($extensions['google_video'])) { + GoogleVideoExtension::validate($this->baseURL . $path, $extensions['google_video']); + } + } + + /** + * Add url components. + * Instead of storing all urls in the memory, the generator will flush sets of added urls + * to the temporary files created on your disk. + * The file format is 'sm-{index}-{timestamp}.xml' + * @param string $path + * @param DateTime|null $lastModified + * @param string|null $changeFrequency + * @param float|null $priority + * @param array|null $alternates + * @param array $extensions + * @return $this + */ + public function addURL( + string $path, + DateTime $lastModified = null, + string $changeFrequency = null, + float $priority = null, + array $alternates = null, + array $extensions = [] + ): SitemapGenerator + { + $this->validate($path, $lastModified, $changeFrequency, $priority, $alternates, $extensions); + + if ($this->totalUrlCount >= self::TOTAL_MAX_URLS) { + throw new OutOfRangeException( + sprintf("Max url limit reached (%d)", self::TOTAL_MAX_URLS) + ); + } + if ($this->isSitemapStarted === false) { + $this->writeSitemapStart(); + } + + $this->writeSitemapUrl($this->baseURL . $path, $lastModified, $changeFrequency, $priority, $alternates, $extensions); + + if ($this->totalUrlCount % 1000 === 0 || $this->sitemapUrlCount >= $this->maxUrlsPerSitemap) { + $this->flushWriter(); + } + + if ($this->sitemapUrlCount === $this->maxUrlsPerSitemap) { + $this->writeSitemapEnd(); + } + + return $this; + } + + private function writeSitemapStart() + { + $this->xmlWriter->startDocument("1.0", "UTF-8"); + $this->xmlWriter->writeComment(sprintf('generator-class="%s"', get_class($this))); + $this->xmlWriter->writeComment(sprintf('generator-version="%s"', $this->classVersion)); + $this->xmlWriter->writeComment(sprintf('generated-on="%s"', date('c'))); + $this->xmlWriter->startElement('urlset'); + $this->xmlWriter->writeAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9'); + $this->xmlWriter->writeAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml'); + $this->xmlWriter->writeAttribute('xmlns:video', 'http://www.google.com/schemas/sitemap-video/1.1'); + $this->xmlWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $this->xmlWriter->writeAttribute('xsi:schemaLocation', 'http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd'); + $this->isSitemapStarted = true; + } + + private function writeSitemapUrl($loc, $lastModified, $changeFrequency, $priority, $alternates, $extensions) + { + $this->xmlWriter->startElement('url'); + $this->xmlWriter->writeElement('loc', htmlspecialchars($loc, ENT_QUOTES)); + + if ($lastModified !== null) { + $this->xmlWriter->writeElement('lastmod', $lastModified->format(DateTime::ATOM)); + } + + if ($changeFrequency !== null) { + $this->xmlWriter->writeElement('changefreq', $changeFrequency); + } + + if ($priority !== null) { + $this->xmlWriter->writeElement('priority', number_format($priority, 1, ".", "")); + } + + if (is_array($alternates) && count($alternates) > 0) { + foreach ($alternates as $alternate) { + if (is_array($alternate) && isset($alternate['hreflang']) && isset($alternate['href'])) { + $this->xmlWriter->startElement('xhtml:link'); + $this->xmlWriter->writeAttribute('rel', 'alternate'); + $this->xmlWriter->writeAttribute('hreflang', $alternate['hreflang']); + $this->xmlWriter->writeAttribute('href', $alternate['href']); + $this->xmlWriter->endElement(); + } + } + } + + foreach ($extensions as $extName => $extFields) { + if ($extName === 'google_video') { + GoogleVideoExtension::writeVideoTag($this->xmlWriter, $loc, $extFields); + } + } + + $this->xmlWriter->endElement(); // url + $this->sitemapUrlCount++; + $this->totalUrlCount++; + } + + private function flushWriter() + { + $targetSitemapFilepath = $this->basePath . sprintf($this->flushedSitemapFilenameFormat, $this->flushedSitemapCounter); + $flushedString = $this->xmlWriter->outputMemory(true); + $flushedStringLen = mb_strlen($flushedString); + + if ($flushedStringLen === 0) { + return; + } + + $this->flushedSitemapSize += $flushedStringLen; + + if ($this->flushedSitemapSize > self::MAX_FILE_SIZE - $this->urlsetClosingTagLen) { + $this->writeSitemapEnd(); + $this->writeSitemapStart(); + } + $this->fs->file_put_contents($targetSitemapFilepath, $flushedString, FILE_APPEND); + } + + private function writeSitemapEnd() + { + $targetSitemapFilepath = $this->basePath . sprintf($this->flushedSitemapFilenameFormat, $this->flushedSitemapCounter); + $this->xmlWriter->endElement(); // urlset + $this->xmlWriter->endDocument(); + $this->fs->file_put_contents($targetSitemapFilepath, $this->xmlWriter->flush(true), FILE_APPEND); + $this->isSitemapStarted = false; + $this->flushedSitemaps[] = $targetSitemapFilepath; + $this->flushedSitemapCounter++; + $this->sitemapUrlCount = 0; + } + + /** + * Flush all stored urls from memory to the disk and close all necessary tags. + */ + public function flush() + { + $this->flushWriter(); + if ($this->isSitemapStarted) { + $this->writeSitemapEnd(); + } + } + + /** + * Move flushed files to their final location. Compress if necessary. + */ + public function finalize() + { + $this->generatedFiles = []; + + if (count($this->flushedSitemaps) === 1) { + $targetSitemapFilename = $this->sitemapFileName; + if ($this->isCompressionEnabled) { + $targetSitemapFilename .= '.gz'; + } + + $targetSitemapFilepath = $this->basePath . $targetSitemapFilename; + + if ($this->isCompressionEnabled) { + $this->fs->copy($this->flushedSitemaps[0], 'compress.zlib://' . $targetSitemapFilepath); + $this->fs->unlink($this->flushedSitemaps[0]); + } else { + $this->fs->rename($this->flushedSitemaps[0], $targetSitemapFilepath); + } + $this->generatedFiles['sitemaps_location'] = [$targetSitemapFilepath]; + $this->generatedFiles['sitemaps_index_url'] = $this->baseURL . '/' . $targetSitemapFilename; + } else if (count($this->flushedSitemaps) > 1) { + $ext = '.' . pathinfo($this->sitemapFileName, PATHINFO_EXTENSION); + $targetExt = $ext; + if ($this->isCompressionEnabled) { + $targetExt .= '.gz'; + } + + $sitemapsUrls = []; + $targetSitemapFilepaths = []; + foreach ($this->flushedSitemaps as $i => $flushedSitemap) { + $targetSitemapFilename = str_replace($ext, ($i + 1) . $targetExt, $this->sitemapFileName); + $targetSitemapFilepath = $this->basePath . $targetSitemapFilename; + + if ($this->isCompressionEnabled) { + $this->fs->copy($flushedSitemap, 'compress.zlib://' . $targetSitemapFilepath); + $this->fs->unlink($flushedSitemap); + } else { + $this->fs->rename($flushedSitemap, $targetSitemapFilepath); + } + $sitemapsUrls[] = htmlspecialchars($this->baseURL . '/' . $targetSitemapFilename, ENT_QUOTES); + $targetSitemapFilepaths[] = $targetSitemapFilepath; + } + + $targetSitemapIndexFilepath = $this->basePath . $this->sitemapIndexFileName; + $this->createSitemapIndex($sitemapsUrls, $targetSitemapIndexFilepath); + $this->generatedFiles['sitemaps_location'] = $targetSitemapFilepaths; + $this->generatedFiles['sitemaps_index_location'] = $targetSitemapIndexFilepath; + $this->generatedFiles['sitemaps_index_url'] = $this->baseURL . '/' . $this->sitemapIndexFileName; + } else { + throw new RuntimeException('failed to finalize, please add urls and flush first'); + } + } + + private function createSitemapIndex($sitemapsUrls, $sitemapIndexFileName) + { + $this->xmlWriter->flush(true); + $this->writeSitemapIndexStart(); + foreach ($sitemapsUrls as $sitemapsUrl) { + $this->writeSitemapIndexUrl($sitemapsUrl); + } + $this->writeSitemapIndexEnd(); + $this->fs->file_put_contents( + $sitemapIndexFileName, + $this->xmlWriter->flush(true), + FILE_APPEND + ); + } + + private function writeSitemapIndexStart() + { + $this->xmlWriter->startDocument("1.0", "UTF-8"); + $this->xmlWriter->writeComment(sprintf('generator-class="%s"', get_class($this))); + $this->xmlWriter->writeComment(sprintf('generator-version="%s"', $this->classVersion)); + $this->xmlWriter->writeComment(sprintf('generated-on="%s"', date('c'))); + $this->xmlWriter->startElement('sitemapindex'); + $this->xmlWriter->writeAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9'); + $this->xmlWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $this->xmlWriter->writeAttribute('xsi:schemaLocation', 'http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd'); + } + + private function writeSitemapIndexUrl($url) + { + $this->xmlWriter->startElement('sitemap'); + $this->xmlWriter->writeElement('loc', htmlspecialchars($url, ENT_QUOTES)); + $this->xmlWriter->writeElement('lastmod', date('c')); + $this->xmlWriter->endElement(); // sitemap + } + + private function writeSitemapIndexEnd() + { + $this->xmlWriter->endElement(); // sitemapindex + $this->xmlWriter->endDocument(); + } + + /** + * @return array Array of previously generated files + */ + public function getGeneratedFiles(): array + { + return $this->generatedFiles; + } + + /** + * Will inform search engines about newly created sitemaps. + * Google, Ask, Bing and Yahoo will be noticed. + * If You don't pass yahooAppId, Yahoo still will be informed, + * but this method can be used once per day. If You will do this often, + * message that limit was exceeded will be returned from Yahoo. + * @param string $yahooAppId Your site Yahoo appid. + * @return array of messages and http codes from each search engine + * @access public + * @throws BadMethodCallException + */ + public function submitSitemap($yahooAppId = null): array + { + if (count($this->generatedFiles) === 0) { + throw new BadMethodCallException("To update robots.txt, call finalize() first."); + } + if (!$this->runtime->extension_loaded('curl')) { + throw new BadMethodCallException("cURL extension is needed to do submission."); + } + $searchEngines = $this->searchEngines; + $searchEngines[0] = isset($yahooAppId) ? + str_replace("USERID", $yahooAppId, $searchEngines[0][0]) : + $searchEngines[0][1]; + $result = []; + for ($i = 0; $i < count($searchEngines); $i++) { + $submitUrl = $searchEngines[$i] . htmlspecialchars($this->generatedFiles['sitemaps_index_url'], ENT_QUOTES); + $submitSite = $this->runtime->curl_init($submitUrl); + $this->runtime->curl_setopt($submitSite, CURLOPT_RETURNTRANSFER, true); + $responseContent = $this->runtime->curl_exec($submitSite); + $response = $this->runtime->curl_getinfo($submitSite); + $submitSiteShort = array_reverse(explode(".", parse_url($searchEngines[$i], PHP_URL_HOST))); + $result[] = [ + "site" => $submitSiteShort[1] . "." . $submitSiteShort[0], + "fullsite" => $submitUrl, + "http_code" => $response['http_code'], + "message" => str_replace("\n", " ", strip_tags($responseContent)), + ]; + } + return $result; + } + + /** + * Adds sitemap url to robots.txt file located in basePath. + * If robots.txt file exists, + * the function will append sitemap url to file. + * If robots.txt does not exist, + * the function will create new robots.txt file with sample content and sitemap url. + * @access public + * @throws BadMethodCallException + * @throws RuntimeException + */ + public function updateRobots(): SitemapGenerator + { + if (count($this->generatedFiles) === 0) { + throw new BadMethodCallException("To update robots.txt, call finalize() first."); + } + + $robotsFilePath = $this->basePath . $this->robotsFileName; + + $robotsFileContent = $this->createNewRobotsContentFromFile($robotsFilePath); + + $this->fs->file_put_contents($robotsFilePath, $robotsFileContent); + + return $this; + } + + /** + * @param $filepath + * @return string + */ + private function createNewRobotsContentFromFile($filepath): string + { + if ($this->fs->file_exists($filepath)) { + $robotsFileContent = ""; + $robotsFile = explode(PHP_EOL, $this->fs->file_get_contents($filepath)); + foreach ($robotsFile as $key => $value) { + if (substr($value, 0, 8) == 'Sitemap:') { + unset($robotsFile[$key]); + } else { + $robotsFileContent .= $value . PHP_EOL; + } + } + } else { + $robotsFileContent = $this->getSampleRobotsContent(); + } + + $robotsFileContent .= "Sitemap: {$this->generatedFiles['sitemaps_index_url']}"; + + return $robotsFileContent; + } + + /** + * @return string + * @access private + */ + private function getSampleRobotsContent(): string + { + return implode(PHP_EOL, $this->sampleRobotsLines) . PHP_EOL; + } +} diff --git a/core/core.php b/core/core.php index 9f0377bb..ab0d32a5 100755 --- a/core/core.php +++ b/core/core.php @@ -774,22 +774,26 @@ class common { $timezone = $this->getData(['config','timezone']); - $sitemap = new \Icamys\SitemapGenerator\SitemapGenerator(helper::baseurl()); + $outputDir = getcwd(); + + $sitemap = new \Icamys\SitemapGenerator\SitemapGenerator(helper::baseurl(false),$outputDir); // will create also compressed (gzipped) sitemap - $sitemap->createGZipFile = true; + $sitemap->enableCompression(); // determine how many urls should be put into one file // according to standard protocol 50000 is maximum value (see http://www.sitemaps.org/protocol.html) - $sitemap->maxURLsPerSitemap = 50000; + $sitemap->setMaxUrlsPerSitemap(50000); // sitemap file name - $sitemap->sitemapFileName = "sitemap.xml"; + $sitemap->setSitemapFileName("sitemap.xml"); + + // Set the sitemap index file name + $sitemap->setSitemapIndexFileName("sitemap-index.xml"); $datetime = new DateTime(date('c')); $datetime->format(DateTime::ATOM); // Updated ISO8601 - // sitemap index file name - $sitemap->sitemapIndexFileName = "sitemap-index.xml"; + foreach($this->getHierarchy(null, null, null) as $parentPageId => $childrenPageIds) { // Exclure les barres et les pages non publiques et les pages masquées if ($this->getData(['page',$parentPageId,'group']) !== 0 || @@ -831,11 +835,17 @@ class common { } - // generating internally a sitemap - $sitemap->createSitemap(); + // Flush all stored urls from memory to the disk and close all necessary tags. + $sitemap->flush(); - // writing early generated sitemap to file - $sitemap->writeSitemap(); + // Move flushed files to their final location. Compress if the option is enabled. + $sitemap->finalize(); + + // Update robots.txt file in output directory or create a new one + $sitemap->updateRobots(); + + // Submit your sitemaps to Google, Yahoo, Bing and Ask.com + $sitemap->submitSitemap(); return(file_exists('sitemap.xml')); @@ -1631,6 +1641,16 @@ class common { } + // Version 10.4.05 + if ($this->getData(['core', 'dataVersion']) < 10405) { + + // Mise à jour forcée des thèmes + unlink (self::DATA_DIR . 'admin.css'); + unlink (self::DATA_DIR . 'theme.css'); + + $this->setData(['core', 'dataVersion', 10405]); + } + // Version 11.0.00 if ($this->getData(['core', 'dataVersion']) < 11000) { @@ -1651,11 +1671,9 @@ class common { $this->setData(['config','translate','pt', false ]); $this->setData(['core', 'dataVersion', 11000]); - } - /** - * mettre à jour defaultdata - */ + + } } } @@ -1741,14 +1759,14 @@ class core extends common { $css .= 'body{font-family:"' . str_replace('+', ' ', $this->getData(['theme', 'text', 'font'])) . '",sans-serif}'; if($themeBodyImage = $this->getData(['theme', 'body', 'image'])) { // Image dans html pour éviter les déformations. - $css .= 'html{background-image:url("../file/source/' . $themeBodyImage . '");background-position:' . $this->getData(['theme', 'body', 'imagePosition']) . ';background-attachment:' . $this->getData(['theme', 'body', 'imageAttachment']) . ';background-size:' . $this->getData(['theme', 'body', 'imageSize']) . ';background-repeat:' . $this->getData(['theme', 'body', 'imageRepeat']) . '}'; + $css .= 'html, .mce-menu.mce-in.mce-animate {background-image:url("../file/source/' . $themeBodyImage . '");background-position:' . $this->getData(['theme', 'body', 'imagePosition']) . ';background-attachment:' . $this->getData(['theme', 'body', 'imageAttachment']) . ';background-size:' . $this->getData(['theme', 'body', 'imageSize']) . ';background-repeat:' . $this->getData(['theme', 'body', 'imageRepeat']) . '}'; // Couleur du body transparente - $css .= 'body{background-color: rgba(0,0,0,0)}'; + $css .= 'body, .mce-menu.mce-in.mce-animate{background-color: rgba(0,0,0,0)}'; } else { // Pas d'image couleur du body - $css .= 'html{background-color:' . $colors['normal'] . ';}'; + $css .= 'html, .mce-menu.mce-in.mce-animate{background-color:' . $colors['normal'] . ';}'; // Même couleur dans le fond de l'éditeur - $css .= 'div.mce-edit-area{background-color:' . $colors['normal'] . ' !important}'; + $css .= 'div.mce-edit-area {background-color:' . $colors['normal'] . ' !important}'; } // Icône BacktoTop $css .= '#backToTop {background-color:' .$this->getData(['theme', 'body', 'toTopbackgroundColor']). ';color:'.$this->getData(['theme', 'body', 'toTopColor']).';}'; @@ -1756,7 +1774,7 @@ class core extends common { $colors = helper::colorVariants($this->getData(['theme', 'text', 'linkColor'])); $css .= 'a{color:' . $colors['normal'] . '}'; // Couleurs de site dans TinyMCe - $css .= 'div.mce-edit-area{font-family:"' . str_replace('+', ' ', $this->getData(['theme', 'text', 'font'])) . '",sans-serif}'; + $css .= 'div.mce-edit-area {font-family:"' . str_replace('+', ' ', $this->getData(['theme', 'text', 'font'])) . '",sans-serif}'; // Site dans TinyMCE $css .= '.editorWysiwyg {background-color:' . $this->getData(['theme', 'site', 'backgroundColor']) . ';}'; //$css .= 'a:hover:not(.inputFile, button){color:' . $colors['darken'] . '}'; @@ -1769,8 +1787,23 @@ 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: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;}' : ''; + // Marge supplémentaire lorsque le pied de page est fixe + if ( $this->getData(['theme', 'footer', 'fixed']) === true && + $this->getData(['theme', 'footer', 'position']) === 'body') { + //$css .= '@media (min-width: 769px) { #site {margin-bottom: ' . ((str_replace ('px', '', $this->getData(['theme', 'footer', 'height']) ) * 2 ) + 31 ) . 'px}}'; + //$css .= '@media (max-width: 768px) { #site {margin-bottom: ' . ((str_replace ('px', '', $this->getData(['theme', 'footer', 'height']) ) * 2 ) + 93 ) . 'px}}'; + $marginBottomLarge = ((str_replace ('px', '', $this->getData(['theme', 'footer', 'height']) ) * 2 ) + 31 ) . 'px'; + $marginBottomSmall = ((str_replace ('px', '', $this->getData(['theme', 'footer', 'height']) ) * 2 ) + 93 ) . 'px'; + } else { + $marginBottomSmall = $margin; + $marginBottomLarge = $margin; + } + $css .= $this->getData(['theme', 'site', 'width']) === '100%' + ? '@media (min-width: 769px) {#site{margin:0 auto 0 ' . $marginBottomLarge . ' !important;}}@media (max-width: 768px) {#site{margin:0 auto 0 ' . $marginBottomSmall . ' !important;}}#site.light{margin:5% 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;}' + : '@media (min-width: 769px) {#site{margin: ' . $margin . ' auto ' . $marginBottomLarge . ' auto !important;}}@media (max-width: 768px) {#site{margin: ' . $margin . ' auto ' . $marginBottomSmall . ' auto !important;}}#site.light{margin: 5% 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;}'; $colors = helper::colorVariants($this->getData(['theme', 'button', 'backgroundColor'])); $css .= '.speechBubble,.button,.button:hover,button[type=\'submit\'],.pagination a,.pagination a:hover,input[type=\'checkbox\']:checked + label:before,input[type=\'radio\']:checked + label:before,.helpContent{background-color:' . $colors['normal'] . ';color:' . $colors['text'] . '}'; @@ -1858,22 +1891,16 @@ class core extends common { } $css .= 'footer span, #footerText > p {color:' . $this->getData(['theme', 'footer', 'textColor']) . ';font-family:"' . str_replace('+', ' ', $this->getData(['theme', 'footer', 'font'])) . '",sans-serif;font-weight:' . $this->getData(['theme', 'footer', 'fontWeight']) . ';font-size:' . $this->getData(['theme', 'footer', 'fontSize']) . ';text-transform:' . $this->getData(['theme', 'footer', 'textTransform']) . '}'; - $css .= 'footer{background-color:' . $colors['normal'] . ';color:' . $this->getData(['theme', 'footer', 'textColor']) . '}'; + $css .= 'footer {background-color:' . $colors['normal'] . ';color:' . $this->getData(['theme', 'footer', 'textColor']) . '}'; $css .= 'footer a{color:' . $this->getData(['theme', 'footer', 'textColor']) . '}'; $css .= 'footer #footersite > div {margin:' . $this->getData(['theme', 'footer', 'height']) . ' 0}'; - $css .= 'footer #footerbody > div {margin:' . $this->getData(['theme', 'footer', 'height']) . ' 0}'; + $css .= 'footer #footerbody > div {margin:' . $this->getData(['theme', 'footer', 'height']) . ' 0}'; + $css .= '@media (max-width: 768px) {footer #footerbody > div { padding: 2px }}'; $css .= '#footerSocials{text-align:' . $this->getData(['theme', 'footer', 'socialsAlign']) . '}'; $css .= '#footerText > p {text-align:' . $this->getData(['theme', 'footer', 'textAlign']) . '}'; $css .= '#footerCopyright{text-align:' . $this->getData(['theme', 'footer', 'copyrightAlign']) . '}'; - // Marge supplémentaire lorsque le pied de page est fixe - if ( $this->getData(['theme', 'footer', 'fixed']) === true && - $this->getData(['theme', 'footer', 'position']) === 'body') { - $css .= "@media (min-width: 769px) { #site {margin-bottom: 100px;} }"; - $css .= "@media (max-width: 768px) { #site {margin-bottom: 150px;} }"; - } - // Enregistre la personnalisation file_put_contents(self::DATA_DIR.'theme.css', $css); // Effacer le cache pour tenir compte de la couleur de fond TinyMCE @@ -1891,7 +1918,7 @@ class core extends common { $colors = helper::colorVariants($this->getData(['admin','backgroundColor'])); $css .= '#site{background-color:' . $colors['normal']. ';}'; $css .= '.row > div {font:' . $this->getData(['admin','fontSize']) . ' "' . $this->getData(['admin','fontText']) . '", sans-serif;}'; - $css .= 'body h1, h2, h3, h4, h5, h6 {font-family:' . $this->getData(['admin','fontTitle' ]) . ', sans-serif;color:' . $this->getData(['admin','colorTitle' ]) . ';}'; + $css .= 'body h1, h2, h3, h4 a, h5, h6 {font-family:' . $this->getData(['admin','fontTitle' ]) . ', sans-serif;color:' . $this->getData(['admin','colorTitle' ]) . ';}'; $css .= 'body:not(.editorWysiwyg),span .zwiico-help {color:' . $this->getData(['admin','colorText']) . ';}'; $colors = helper::colorVariants($this->getData(['admin','backgroundColorButton'])); $css .= 'input[type="checkbox"]:checked + label::before,.speechBubble{background-color:' . $colors['normal'] . ';color:' . $colors['text'] . ';}'; diff --git a/core/layout/common.css b/core/layout/common.css index 39df31ef..723f3995 100755 --- a/core/layout/common.css +++ b/core/layout/common.css @@ -45,11 +45,11 @@ body { body { margin: 0px; } - +/* #site { margin: 0px auto; } - +*/ body>header { margin: 0px 0px; } @@ -477,12 +477,13 @@ td>.col12 { #site { overflow: hidden; } - +/* Dans theme.css @media (min-width:768px) { #site { margin: 20px auto; } } +*/ /* Bannière */ @@ -952,9 +953,9 @@ footer #footerSocials .zwiico-github:hover { /* Remonter en haut */ #backToTop { position: fixed; - z-index: 30; + z-index: 50; right: 30px; - bottom: 50px; + bottom: 100px; padding: 13px 16px 16px; /* Paramétré dans le thème (9.2.21) diff --git a/core/module/addon/addon.php b/core/module/addon/addon.php index b8d5f379..a1521575 100644 --- a/core/module/addon/addon.php +++ b/core/module/addon/addon.php @@ -117,10 +117,10 @@ class addon extends common { 'value' => template::ico('download') ]) : '', - is_array($infoModules[$key]['dataDirectory']) && implode(', ',array_keys($inPages,$key)) !== '' + is_array($infoModules[$key]['dataDirectory']) && implode(', ',array_keys($inPages,$key)) === '' ? template::button('moduleExport' . $key, [ 'class' => 'buttonBlue', - 'href' => helper::baseUrl(). $this->getUrl(0) . '/import/' . $key,// appel de fonction vaut exécution, utiliser un paramètre + 'href' => helper::baseUrl(). $this->getUrl(0) . '/import/' . $key.'/' . $_SESSION['csrf'],// appel de fonction vaut exécution, utiliser un paramètre 'value' => template::ico('upload') ]) : '' @@ -159,7 +159,8 @@ class addon extends common { // Lecture de la version et de la validation d'update du module pour validation de la mise à jour // Pour une version <= version installée l'utilisateur doit cocher 'Mise à jour forcée' $version = '0.0'; - $update = false; + $update = '0.0'; + $valUpdate = false; $file = file_get_contents( $moduleDir.'/'.$moduleName.'/'.$moduleName.'.php'); $file = str_replace(' ','',$file); $file = str_replace("\t",'',$file); @@ -171,13 +172,12 @@ class addon extends common { } $pos1 = strpos($file, 'constUPDATE'); if( $pos1 !== false){ - $posdeb = strpos($file, "=", $pos1); - $posend = strpos($file, ";", $posdeb + 1); - $strUpdate = substr($file, $posdeb + 1, $posend - $posdeb - 1); - if( strpos( $strUpdate,"true",0) !== false){ - $update = true; - } + $posdeb = strpos($file, "'", $pos1); + $posend = strpos($file, "'", $posdeb + 1); + $update = substr($file, $posdeb + 1, $posend - $posdeb - 1); } + // Si version actuelle >= version indiquée dans UPDATE la mise à jour est validée + if( $infoModules[$moduleName]['update'] >= $update ) $valUpdate = true; // Module déjà installé ? $moduleInstal = false; @@ -193,7 +193,7 @@ class addon extends common { $valInstalVersion = floatval( $infoModules[$moduleName]['version'] ); $newVersion = false; if( $valNewVersion > $valInstalVersion ) $newVersion = true; - $validMaj = $update && ( $newVersion || $checkValidMaj); + $validMaj = $valUpdate && ( $newVersion || $checkValidMaj); // Nouvelle installation ou mise à jour du module if( ! $moduleInstal || $validMaj ){ @@ -215,8 +215,13 @@ class addon extends common { else{ $notification = ' Version détectée '.$version.' < à celle installée '.$infoModules[$moduleName]['version']; } - if( $update === false){ - $notification = ' Mise à jour par ce procédé interdite par le concepteur du module'; + if( $valUpdate === false){ + if( $infoModules[$moduleName]['update'] === $update ){ + $notification = ' Mise à jour par ce procédé interdite par le concepteur du module'; + } + else{ + $notification = ' Mise à jour par ce procédé interdite, votre version est trop ancienne'; + } } } } @@ -289,40 +294,33 @@ class addon extends common { $inPages = helper::arrayCollumn($this->getData(['page']),'moduleId', 'SORT_DESC'); // Parcourir les pages utilisant le module foreach (array_keys($inPages,$this->getUrl(2)) as $pageId) { - foreach ($infoModules[$this->getUrl(2)]['dataDirectory'] as $moduleId) { - // Export des pages hébergeant le module - $pageContent[$pageId] = $this->getData(['page',$pageId]); - /** - * Données module.json ? - */ - if (strpos($moduleId,'module.json')) { - // Création de l'arborescence des langues - // Pas de nom dossier de langue - dossier par défaut - $t = explode ('/',$moduleId); - if ( is_array($t)) { - $lang = 'fr'; - } else { - $lang = $t[0]; + // Export des pages hébergeant le module + $pageContent[$pageId] = $this->getData(['page',$pageId]); + // Export de fr/module.json + $moduleId = 'fr/module.json'; + // Création de l'arborescence des langues + // Pas de nom dossier de langue - dossier par défaut + $t = explode ('/',$moduleId); + if ( is_array($t)) { + $lang = 'fr'; + } else { + $lang = $t[0]; + } + // Créer le dossier si inexistant + if (!is_dir($tmpFolder . '/' . $lang)) { + mkdir ($tmpFolder . '/' . $lang); + } + // Sauvegarde si données non vides + $tmpData [$pageId] = $this->getData(['module',$pageId ]); + if ($tmpData [$pageId] !== null) { + file_put_contents($tmpFolder . '/' . $moduleId, json_encode($tmpData)); + } + // Export des données localisées dans des dossiers + foreach ($infoModules[$this->getUrl(2)]['dataDirectory'] as $dirId) { + if ( file_exists(self::DATA_DIR . '/' . $dirId) + && !file_exists($tmpFolder . '/' . $dirId ) ) { + $this->custom_copy ( self::DATA_DIR . '/' . $dirId, $tmpFolder . '/' . $dirId ); } - // Créer le dossier si inexistant - if (!is_dir($tmpFolder . '/' . $lang)) { - mkdir ($tmpFolder . '/' . $lang); - } - // Sauvegarde si données non vides - $tmpData [$pageId] = $this->getData(['module',$pageId ]); - if ($tmpData [$pageId] !== null) { - file_put_contents($tmpFolder . '/' . $moduleId, json_encode($tmpData)); - } - } else { - /** - * Données dans un json personnalisé, le sauvegarder - * Dossier non localisé - */ - if ( file_exists(self::DATA_DIR . '/' . $moduleId) - && !file_exists($tmpFolder . '/' . $moduleId ) ) { - $this->custom_copy ( self::DATA_DIR . '/' . $moduleId, $tmpFolder . '/' . $moduleId ); - } - } } } // Enregistrement des pages dans le dossier de langue identique à module @@ -360,26 +358,86 @@ class addon extends common { * Importer des données d'un module externes ou interne à module.json */ public function import(){ - // Soumission du formulaire - if($this->isPost()) { - // Récupérer le fichier et le décompacter - $zipFilename = $this->getInput('addonImportFile', helper::FILTER_STRING_SHORT, true); - $tempFolder = uniqid(); - mkdir (self::TEMP_DIR . $tempFolder); - echo $zipFilename; - $zip = new ZipArchive(); - if ($zip->open(self::FILE_DIR . 'source/' . $zipFilename) === TRUE) { - $zip->extractTo(self::TEMP_DIR . $tempFolder ); - } - - // Supprimer le dossier temporaire même si le thème est invalide - //$this->removeDir(self::TEMP_DIR . $tempFolder); - $zip->close(); + // Jeton incorrect + if ($this->getUrl(3) !== $_SESSION['csrf']) { + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'addon', + 'state' => false, + 'notification' => 'Action non autorisée' + ]); + } + else{ + // Soumission du formulaire + if($this->isPost()) { + // Récupérer le fichier et le décompacter + $zipFilename = $this->getInput('addonImportFile', helper::FILTER_STRING_SHORT, true); + $tempFolder = uniqid(); + mkdir (self::TEMP_DIR . $tempFolder); + $zip = new ZipArchive(); + if ($zip->open(self::FILE_DIR . 'source/' . $zipFilename) === TRUE) { + $zip->extractTo(self::TEMP_DIR . $tempFolder ); + } + // Import des données localisées page.json et module.json + // Pour chaque dossier localisé + $dataTarget = array(); + $dataSource = array(); + // Liste des pages de même nom dans l'archive et le site + $list = ''; + foreach (self::$i18nList as $key=>$value) { + // Les Pages et les modules + foreach (['page','module'] as $fileTarget){ + if (file_exists(self::TEMP_DIR . $tempFolder . '/' .$key . '/' . $fileTarget . '.json')) { + // Le dossier de langue existe + // faire la fusion + $dataSource = json_decode(file_get_contents(self::TEMP_DIR . $tempFolder . '/' .$key . '/' . $fileTarget . '.json'), true); + // Des pages de même nom que celles de l'archive existent + if( $fileTarget === 'page' ){ + foreach( $dataSource as $keydataSource=>$valuedataSource ){ + foreach( $this->getData(['page']) as $keypage=>$valuepage ){ + if( $keydataSource === $keypage){ + $list === '' ? $list .= ' '.$this->getData(['page', $keypage, 'title']) : $list .= ', '.$this->getData(['page', $keypage, 'title']); + } + } + } + } + $dataTarget = json_decode(file_get_contents(self::DATA_DIR . $key . '/' . $fileTarget . '.json'), true); + $data [$fileTarget] = array_merge($dataTarget[$fileTarget], $dataSource); + if( $list === ''){ + file_put_contents(self::DATA_DIR . '/' .$key . '/' . $fileTarget . '.json', json_encode( $data ,JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT|LOCK_EX) ); + } + // Supprimer les fichiers importés + unlink (self::TEMP_DIR . $tempFolder . '/' .$key . '/' . $fileTarget . '.json'); + } + } + } + + // Import des fichiers placés ailleurs que dans les dossiers localisés. + $this->custom_copy (self::TEMP_DIR . $tempFolder,self::DATA_DIR ); + + // Supprimer le dossier temporaire + $this->removeDir(self::TEMP_DIR . $tempFolder); + $zip->close(); + if( $list !== '' ){ + $success = false; + strpos( $list, ',') === false ? $notification = 'Import impossible la page suivante doit être renommée :'.$list : $notification = 'Import impossible les pages suivantes doivent être renommées :'.$list; + } + else{ + $success = true; + $notification = 'Import réussi'; + } + // Valeurs en sortie + $this->addOutput([ + 'redirect' => helper::baseUrl() . 'addon', + 'state' => $success, + 'notification' => $notification + ]); + } + // Valeurs en sortie + $this->addOutput([ + 'title' => 'Importer des données de module', + 'view' => 'import' + ]); } - // Valeurs en sortie - $this->addOutput([ - 'title' => 'Importer des données de module', - 'view' => 'import' - ]); } } diff --git a/core/module/config/view/index/index.php b/core/module/config/view/index/index.php index 42b8b8ae..f7c50a18 100755 --- a/core/module/config/view/index/index.php +++ b/core/module/config/view/index/index.php @@ -90,7 +90,7 @@ 'Aucune'] , helper::arrayCollumn($pages, 'title', 'SORT_ASC') ) , [ 'label' => 'Recherche dans le site', 'selected' => $this->getData(['locale', 'searchPageId']), - 'help' => 'Sélectionner la page "Recherche" ou une page contenant le module "Recherche" permet d\'activer un lien dans le pied de page. ' + 'help' => 'Sélectionner la page "Recherche" ou une page contenant le module "Recherche". Une option du pied de page ajoute un lien discret vers cette page.' ]); ?> diff --git a/module/blog/blog.php b/module/blog/blog.php index a7cbe478..560086b3 100755 --- a/module/blog/blog.php +++ b/module/blog/blog.php @@ -18,7 +18,7 @@ class blog extends common { const VERSION = '4.4'; const REALNAME = 'Blog'; const DELETE = true; - const UPDATE = true; + const UPDATE = '0.0'; const DATADIRECTORY = []; // Contenu localisé inclus par défaut (page.json et module.json) const EDIT_OWNER = 'owner'; @@ -763,4 +763,3 @@ class blog extends common { } } } - diff --git a/module/form/form.php b/module/form/form.php index 21daf0bc..77d416d8 100755 --- a/module/form/form.php +++ b/module/form/form.php @@ -19,7 +19,7 @@ class form extends common { const VERSION = '2.8'; const REALNAME = 'Formulaire'; const DELETE = true; - const UPDATE = true; + const UPDATE = '0.0'; const DATADIRECTORY = []; // Contenu localisé inclus par défaut (page.json et module.json) public static $actions = [ @@ -290,7 +290,7 @@ class form extends common { if( $this->getData(['module', $this->getUrl(0), 'config', 'captcha']) // AND $this->getInput('formcaptcha', helper::FILTER_INT) !== $this->getInput('formcaptchaFirstNumber', helper::FILTER_INT) + $this->getInput('formcaptchaSecondNumber', helper::FILTER_INT)) - AND password_verify($this->getInput('formCaptcha', helper::FILTER_INT), $this->getInput('formCaptchaResult') ) === false ) + AND password_verify($this->getInput('formCaptcha', helper::FILTER_INT), $this->getInput('formCaptchaResult') ) === false ) { self::$inputNotices['formCaptcha'] = 'Incorrect'; diff --git a/module/gallery/gallery.php b/module/gallery/gallery.php index 53c2b091..9434387f 100755 --- a/module/gallery/gallery.php +++ b/module/gallery/gallery.php @@ -20,7 +20,7 @@ class gallery extends common { const VERSION = '2.6'; const REALNAME = 'Galerie'; const DELETE = true; - const UPDATE = true; + const UPDATE = '0.0'; const DATADIRECTORY = []; // Contenu localisé inclus par défaut (page.json et module.json) const SORT_ASC = 'SORT_ASC'; @@ -698,4 +698,4 @@ class galleriesHelper extends helper { } return $dirContent; } -} \ No newline at end of file +} diff --git a/module/news/news.php b/module/news/news.php index f0685aab..6675ec37 100755 --- a/module/news/news.php +++ b/module/news/news.php @@ -18,7 +18,7 @@ class news extends common { const VERSION = '2.1'; const REALNAME = 'Actualités'; const DELETE = true; - const UPDATE = true; + const UPDATE = '0.0'; const DATADIRECTORY = []; // Contenu localisé inclus par défaut (page.json et module.json) public static $actions = [ @@ -334,4 +334,4 @@ class news extends common { return $this->getData(['user', $userId, 'firstname']); } } -} \ No newline at end of file +} diff --git a/module/redirection/redirection.php b/module/redirection/redirection.php index 58c4e1b7..0d1a704d 100755 --- a/module/redirection/redirection.php +++ b/module/redirection/redirection.php @@ -18,7 +18,7 @@ class redirection extends common { const VERSION = '1.5'; const REALNAME = 'Redirection'; const DELETE = true; - const UPDATE = true; + const UPDATE = '0.0'; const DATADIRECTORY = []; // Contenu localisé inclus par défaut (page.json et module.json) public static $actions = [ @@ -76,4 +76,4 @@ class redirection extends common { ]); } } -} \ No newline at end of file +} diff --git a/module/search/search.php b/module/search/search.php index a8b0cd67..c258ddd5 100755 --- a/module/search/search.php +++ b/module/search/search.php @@ -21,7 +21,7 @@ class search extends common { const VERSION = '1.3'; const REALNAME = 'Recherche'; const DELETE = true; - const UPDATE = true; + const UPDATE = '0.0'; const DATADIRECTORY = []; // Contenu localisé inclus par défaut (page.json et module.json) public static $actions = [