revert sitemapGenerator v5

This commit is contained in:
Fred Tempez 2025-01-06 18:07:42 +01:00
parent 978bfb2cdc
commit 8a07bf69a7
7 changed files with 276 additions and 557 deletions

View File

@ -7,11 +7,7 @@ class autoload {
require_once 'core/class/helper.class.php'; require_once 'core/class/helper.class.php';
require_once 'core/class/template.class.php'; require_once 'core/class/template.class.php';
require_once 'core/class/layout.class.php'; require_once 'core/class/layout.class.php';
require_once 'core/class/sitemap/IConfig.class.php';
require_once 'core/class/sitemap/Config.class.php';
require_once 'core/class/sitemap/IRuntime.class.php';
require_once 'core/class/sitemap/Runtime.class.php'; require_once 'core/class/sitemap/Runtime.class.php';
require_once 'core/class/sitemap/IFileSystem.class.php';
require_once 'core/class/sitemap/FileSystem.class.php'; require_once 'core/class/sitemap/FileSystem.class.php';
require_once 'core/class/sitemap/SitemapGenerator.class.php'; require_once 'core/class/sitemap/SitemapGenerator.class.php';
require_once 'core/class/phpmailer/PHPMailer.class.php'; require_once 'core/class/phpmailer/PHPMailer.class.php';

View File

@ -1,115 +0,0 @@
<?php
namespace Icamys\SitemapGenerator;
class Config implements IConfig
{
/**
* @var string URL of the website.
* It is used as a prefix to the paths added to sitemap using addURL() method.
*/
private string $baseURL = "";
/**
* @var string URL of the sitemap file.
*/
private string $sitemapIndexURL = "";
/**
* @var string Path to the directory where the sitemap and robots files will be saved.
*/
private string $saveDirectory = "";
private IFileSystem|null $fs;
private IRuntime|null $runtime;
public function __construct()
{
$this->fs = null;
$this->runtime = null;
}
/**
* @return string
*/
public function getBaseURL(): string
{
return $this->baseURL;
}
/**
* @param string $baseURL
* @return Config
*/
public function setBaseURL(string $baseURL): Config
{
$this->baseURL = $baseURL;
return $this;
}
/**
* @return string
*/
public function getSaveDirectory(): string
{
return $this->saveDirectory;
}
/**
* @param string $saveDirectory
* @return Config
*/
public function setSaveDirectory(string $saveDirectory): Config
{
$this->saveDirectory = $saveDirectory;
return $this;
}
/**
* @return IFileSystem|null
*/
public function getFS(): IFileSystem|null
{
return $this->fs;
}
/**
* @param IFileSystem|null $fs
* @return Config
*/
public function setFS(IFileSystem|null $fs): Config
{
$this->fs = $fs;
return $this;
}
/**
* @return IRuntime|null
*/
public function getRuntime(): IRuntime|null
{
return $this->runtime;
}
/**
* @param IRuntime|null $runtime
* @return Config
*/
public function setRuntime(IRuntime|null $runtime): Config
{
$this->runtime = $runtime;
return $this;
}
public function getSitemapIndexURL(): string
{
return $this->sitemapIndexURL;
}
public function setSitemapIndexURL(string $sitemapIndexURL): Config
{
$this->sitemapIndexURL = $sitemapIndexURL;
return $this;
}
}

View File

@ -1,31 +0,0 @@
<?php
namespace Icamys\SitemapGenerator;
interface IConfig
{
/**
* @return string Base URL of the website.
*/
public function getBaseURL(): string;
/**
* @return string URL of the sitemap file.
*/
public function getSitemapIndexURL(): string;
/**
* @return string Path to the directory where the sitemap and robots files will be saved.
*/
public function getSaveDirectory(): string;
/**
* @return IFileSystem|null File system abstraction.
*/
public function getFS(): IFileSystem|null;
/**
* @return IRuntime|null Runtime abstraction.
*/
public function getRuntime(): IRuntime|null;
}

View File

@ -1,18 +0,0 @@
<?php
namespace Icamys\SitemapGenerator;
interface IFileSystem
{
public function file_get_contents(string $filepath): bool|string;
public function file_put_contents(string $filepath, mixed $content, int $flags = 0): bool|int;
public function file_exists(string $filepath): bool;
public function rename(string $oldName, string $newName): bool;
public function copy(string $source, string $destination): bool;
public function unlink(string $filepath): bool;
}

View File

@ -1,22 +0,0 @@
<?php
namespace Icamys\SitemapGenerator;
use CurlHandle;
interface IRuntime
{
public function extension_loaded(string $extname);
public function is_writable(string $filepath);
public function curl_init(?string $url);
public function curl_setopt(CurlHandle $handle, int $option, mixed $value);
public function curl_exec(CurlHandle $handle);
public function curl_getinfo(CurlHandle $handle, ?int $option = null): mixed;
public function curl_error(CurlHandle $handle);
}

View File

@ -4,12 +4,10 @@ namespace Icamys\SitemapGenerator;
use BadMethodCallException; use BadMethodCallException;
use DateTime; use DateTime;
use Icamys\SitemapGenerator\Extensions\GoogleImageExtension;
use Icamys\SitemapGenerator\Extensions\GoogleVideoExtension; use Icamys\SitemapGenerator\Extensions\GoogleVideoExtension;
use InvalidArgumentException; use InvalidArgumentException;
use OutOfRangeException; use OutOfRangeException;
use RuntimeException; use RuntimeException;
use UnexpectedValueException;
use XMLWriter; use XMLWriter;
/** /**
@ -52,24 +50,19 @@ class SitemapGenerator
* @var string * @var string
* @access public * @access public
*/ */
private string $robotsFileName = "robots.txt"; private $robotsFileName = "robots.txt";
/** /**
* Name of sitemap file * Name of sitemap file
* @var string * @var string
* @access public * @access public
*/ */
private string $sitemapFileName = "sitemap.xml"; private $sitemapFileName = "sitemap.xml";
/** /**
* Name of sitemap index file * Name of sitemap index file
* @var string * @var string
* @access public * @access public
*/ */
private string $sitemapIndexFileName = "sitemap-index.xml"; private $sitemapIndexFileName = "sitemap-index.xml";
/**
* Sitemap Stylesheet link.
* @var string
*/
private string $sitemapStylesheetLink = "";
/** /**
* Quantity of URLs per single sitemap file. * Quantity of URLs per single sitemap file.
* If Your links are very long, sitemap file can be bigger than 10MB, * If Your links are very long, sitemap file can be bigger than 10MB,
@ -77,7 +70,7 @@ class SitemapGenerator
* @var int * @var int
* @access public * @access public
*/ */
private int $maxURLsPerSitemap = self::MAX_URLS_PER_SITEMAP; private $maxUrlsPerSitemap = self::MAX_URLS_PER_SITEMAP;
/** /**
* If true, two sitemap files (.xml and .xml.gz) will be created and added to robots.txt. * If true, two sitemap files (.xml and .xml.gz) will be created and added to robots.txt.
* If true, .gz file will be submitted to search engines. * If true, .gz file will be submitted to search engines.
@ -86,21 +79,14 @@ class SitemapGenerator
* @var bool * @var bool
* @access public * @access public
*/ */
private bool $isCompressionEnabled = false; private $isCompressionEnabled = false;
/** /**
* URL to Your site. * URL to Your site.
* Script will use it to send sitemaps to search engines. * Script will use it to send sitemaps to search engines.
* @var string * @var string
* @access private * @access private
*/ */
private string $baseURL; private $baseURL;
/**
* URL to sitemap file(s).
* Script will use it to reference sitemap files in robots.txt and sitemap index.
* @var string
* @access private
*/
private string $sitemapIndexURL;
/** /**
* Base path. Relative to script location. * Base path. Relative to script location.
* Use this if Your sitemap and robots files should be stored in other * Use this if Your sitemap and robots files should be stored in other
@ -108,33 +94,56 @@ class SitemapGenerator
* @var string * @var string
* @access private * @access private
*/ */
private string $saveDirectory; private $basePath;
/** /**
* Version of this class * Version of this class
* @var string * @var string
* @access private * @access private
*/ */
private string $classVersion = "5.0.0"; private $classVersion = "4.3.1";
/** /**
* Search engines URLs * Search engines URLs
* @var array of strings * @var array of strings
* @access private * @access private
*/ */
private array $searchEngines = [ private $searchEngines = [
"https://webmaster.yandex.ru/ping?sitemap=", [
"http://search.yahooapis.com/SiteExplorerService/V1/updateNotification?appid=USERID&url=",
"http://search.yahooapis.com/SiteExplorerService/V1/ping?sitemap=",
],
"http://www.google.com/ping?sitemap=",
"http://submissions.ask.com/ping?sitemap=",
"http://www.bing.com/ping?sitemap=",
"http://www.webmaster.yandex.ru/ping?sitemap=",
]; ];
/**
* Array with urls
* @var array
* @access private
*/
private $urls;
/** /**
* Lines for robots.txt file that are written if file does not exist * Lines for robots.txt file that are written if file does not exist
* @var array * @var array
*/ */
private array $sampleRobotsLines = [ private $sampleRobotsLines = [
"User-agent: *", "User-agent: *",
"Disallow: /",
"User-agent: Googlebot",
"Allow: /", "Allow: /",
"User-agent: bingbot",
"Allow: /",
"User-agent: Slurp",
"Allow: /",
"User-agent: DuckDuckBot",
"Allow: /",
"User-agent: Baiduspider",
"Allow: /"
]; ];
/** /**
* @var array list of valid changefreq values according to the spec * @var array list of valid changefreq values according to the spec
*/ */
private array $validChangefreqValues = [ private $validChangefreqValues = [
'always', 'always',
'hourly', 'hourly',
'daily', 'daily',
@ -146,7 +155,7 @@ class SitemapGenerator
/** /**
* @var float[] list of valid priority values according to the spec * @var float[] list of valid priority values according to the spec
*/ */
private array $validPriorities = [ private $validPriorities = [
0.0, 0.0,
0.1, 0.1,
0.2, 0.2,
@ -160,98 +169,89 @@ class SitemapGenerator
1.0, 1.0,
]; ];
/** /**
* @var IFileSystem object used to communicate with file system * @var FileSystem object used to communicate with file system
*/ */
private IFileSystem $fs; private $fs;
/** /**
* @var IRuntime object used to communicate with runtime * @var Runtime object used to communicate with runtime
*/ */
private IRuntime $runtime; private $runtime;
/** /**
* @var XMLWriter Used for writing xml to files * @var XMLWriter Used for writing xml to files
*/ */
private XMLWriter $xmlWriter; private $xmlWriter;
/** /**
* @var string * @var string
*/ */
private string $flushedSitemapFilenameFormat; private $flushedSitemapFilenameFormat;
/** /**
* @var int * @var int
*/ */
private int $flushedSitemapSize = 0; private $flushedSitemapSize = 0;
/** /**
* @var int * @var int
*/ */
private int $flushedSitemapCounter = 0; private $flushedSitemapCounter = 0;
/** /**
* @var array * @var array
*/ */
private array $flushedSitemaps = []; private $flushedSitemaps = [];
/** /**
* @var bool * @var bool
*/ */
private bool $isSitemapStarted = false; private $isSitemapStarted = false;
/** /**
* @var int * @var int
*/ */
private int $totalURLCount = 0; private $totalUrlCount = 0;
/** /**
* @var int * @var int
*/ */
private int $urlsetClosingTagLen = 10; // strlen("</urlset>\n") private $urlsetClosingTagLen = 10; // strlen("</urlset>\n")
private int $sitemapURLCount = 0; private $sitemapUrlCount = 0;
private array $generatedFiles = []; private $generatedFiles = [];
/** /**
* @param IConfig $config Configuration object. * @param string $baseURL You site URL
* @throws InvalidArgumentException * @param string $basePath Relative path where sitemap and robots should be stored.
* @param FileSystem|null $fs
* @param Runtime|null $runtime
*/ */
public function __construct(IConfig $config) public function __construct(string $baseURL, string $basePath = "", FileSystem $fs = null, Runtime $runtime = null)
{ {
if ($config->getBaseURL() === '') { $this->urls = [];
throw new InvalidArgumentException('baseURL config parameter is required'); $this->baseURL = rtrim($baseURL, '/');
}
$this->baseURL = rtrim($config->getBaseURL(), '/'); if ($fs === null) {
$this->sitemapIndexURL = rtrim($config->getBaseURL(), '/');
if ($config->getSitemapIndexURL()) {
$this->sitemapIndexURL = rtrim($config->getSitemapIndexURL(), '/');
}
$configFS = $config->getFS();
if ($configFS === null) {
$this->fs = new FileSystem(); $this->fs = new FileSystem();
} else { } else {
$this->fs = $configFS; $this->fs = $fs;
} }
$configRuntime = $config->getRuntime(); if ($runtime === null) {
if ($configRuntime === null) {
$this->runtime = new Runtime(); $this->runtime = new Runtime();
} else { } else {
$this->runtime = $configRuntime; $this->runtime = $runtime;
} }
if ($this->runtime->is_writable($config->getSaveDirectory()) === false) { if ($this->runtime->is_writable($basePath) === false) {
throw new InvalidArgumentException( throw new InvalidArgumentException(
sprintf('the provided basePath (%s) should be a writable directory,', $config->getSaveDirectory()) . sprintf('the provided basePath (%s) should be a writable directory,', $basePath) .
' please check its existence and permissions' ' please check its existence and permissions'
); );
} }
if (strlen($basePath) > 0 && substr($basePath, -1) != DIRECTORY_SEPARATOR) {
$this->saveDirectory = $config->getSaveDirectory(); $basePath = $basePath . DIRECTORY_SEPARATOR;
if (strlen($this->saveDirectory) > 0 && substr($this->saveDirectory, -1) != DIRECTORY_SEPARATOR) {
$this->saveDirectory = $this->saveDirectory . DIRECTORY_SEPARATOR;
} }
$this->basePath = $basePath;
$this->xmlWriter = $this->createXmlWriter(); $this->xmlWriter = $this->createXmlWriter();
$this->flushedSitemapFilenameFormat = sprintf("sm-%%d-%d.xml", time()); $this->flushedSitemapFilenameFormat = sprintf("sm-%%d-%d.xml", time());
@ -267,10 +267,7 @@ class SitemapGenerator
/** /**
* @param string $filename * @param string $filename
*
* @return SitemapGenerator * @return SitemapGenerator
*
* @throws InvalidArgumentException
*/ */
public function setSitemapFilename(string $filename = ''): SitemapGenerator public function setSitemapFilename(string $filename = ''): SitemapGenerator
{ {
@ -284,26 +281,9 @@ class SitemapGenerator
return $this; return $this;
} }
/**
* @param string $path
* @return SitemapGenerator
* @throws InvalidArgumentException
*/
public function setSitemapStylesheet(string $path): SitemapGenerator
{
if (strlen($path) === 0) {
throw new InvalidArgumentException('sitemap stylesheet path should not be empty');
}
$this->sitemapStylesheetLink = $path;
return $this;
}
/** /**
* @param string $filename * @param string $filename
*
* @return $this * @return $this
*
* @throws InvalidArgumentException
*/ */
public function setSitemapIndexFilename(string $filename = ''): SitemapGenerator public function setSitemapIndexFilename(string $filename = ''): SitemapGenerator
{ {
@ -317,7 +297,6 @@ class SitemapGenerator
/** /**
* @param string $filename * @param string $filename
* @return $this * @return $this
* @throws InvalidArgumentException
*/ */
public function setRobotsFileName(string $filename): SitemapGenerator public function setRobotsFileName(string $filename): SitemapGenerator
{ {
@ -331,16 +310,15 @@ class SitemapGenerator
/** /**
* @param int $value * @param int $value
* @return $this * @return $this
* @throws OutOfRangeException
*/ */
public function setMaxURLsPerSitemap(int $value): SitemapGenerator public function setMaxUrlsPerSitemap(int $value): SitemapGenerator
{ {
if ($value < 1 || self::MAX_URLS_PER_SITEMAP < $value) { if ($value < 1 || self::MAX_URLS_PER_SITEMAP < $value) {
throw new OutOfRangeException( throw new OutOfRangeException(
sprintf('value %d is out of range 1-%d', $value, self::MAX_URLS_PER_SITEMAP) sprintf('value %d is out of range 1-%d', $value, self::MAX_URLS_PER_SITEMAP)
); );
} }
$this->maxURLsPerSitemap = $value; $this->maxUrlsPerSitemap = $value;
return $this; return $this;
} }
@ -361,19 +339,13 @@ class SitemapGenerator
return $this->isCompressionEnabled; return $this->isCompressionEnabled;
} }
/**
* @param string $path
* @param string|null $changeFrequency
* @param float|null $priority
* @param array $extensions
* @return void
* @throws InvalidArgumentException
*/
public function validate( public function validate(
string $path, string $path,
?string $changeFrequency = null, DateTime $lastModified = null,
?float $priority = null, string $changeFrequency = null,
array $extensions = []): void float $priority = null,
array $alternates = null,
array $extensions = [])
{ {
if (!(1 <= mb_strlen($path) && mb_strlen($path) <= self::MAX_URL_LEN)) { if (!(1 <= mb_strlen($path) && mb_strlen($path) <= self::MAX_URL_LEN)) {
throw new InvalidArgumentException( throw new InvalidArgumentException(
@ -388,14 +360,8 @@ class SitemapGenerator
if ($priority !== null && !in_array($priority, $this->validPriorities)) { 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]"); throw new InvalidArgumentException("Priority argument should be a float number in the range [0.0..1.0]");
} }
if (count($extensions) > 0) { if ($extensions !== null && isset($extensions['google_video'])) {
if (isset($extensions['google_video'])) { GoogleVideoExtension::validate($this->baseURL . $path, $extensions['google_video']);
GoogleVideoExtension::validate($this->baseURL . $path, $extensions['google_video']);
}
if (isset($extensions['google_image'])) {
GoogleImageExtension::validateEntryFields($extensions['google_image']);
}
} }
} }
@ -411,22 +377,19 @@ class SitemapGenerator
* @param array|null $alternates * @param array|null $alternates
* @param array $extensions * @param array $extensions
* @return $this * @return $this
* @throws OutOfRangeException
* @throws UnexpectedValueException
* @throws InvalidArgumentException
*/ */
public function addURL( public function addURL(
string $path, string $path,
?DateTime $lastModified = null, DateTime $lastModified = null,
?string $changeFrequency = null, string $changeFrequency = null,
?float $priority = null, float $priority = null,
?array $alternates = null, array $alternates = null,
array $extensions = [] array $extensions = []
): SitemapGenerator ): SitemapGenerator
{ {
$this->validate($path, $changeFrequency, $priority, $extensions); $this->validate($path, $lastModified, $changeFrequency, $priority, $alternates, $extensions);
if ($this->totalURLCount >= self::TOTAL_MAX_URLS) { if ($this->totalUrlCount >= self::TOTAL_MAX_URLS) {
throw new OutOfRangeException( throw new OutOfRangeException(
sprintf("Max url limit reached (%d)", self::TOTAL_MAX_URLS) sprintf("Max url limit reached (%d)", self::TOTAL_MAX_URLS)
); );
@ -437,24 +400,20 @@ class SitemapGenerator
$this->writeSitemapUrl($this->baseURL . $path, $lastModified, $changeFrequency, $priority, $alternates, $extensions); $this->writeSitemapUrl($this->baseURL . $path, $lastModified, $changeFrequency, $priority, $alternates, $extensions);
if ($this->totalURLCount % 1000 === 0 || $this->sitemapURLCount >= $this->maxURLsPerSitemap) { if ($this->totalUrlCount % 1000 === 0 || $this->sitemapUrlCount >= $this->maxUrlsPerSitemap) {
$this->flushWriter(); $this->flushWriter();
} }
if ($this->sitemapURLCount === $this->maxURLsPerSitemap) { if ($this->sitemapUrlCount === $this->maxUrlsPerSitemap) {
$this->writeSitemapEnd(); $this->writeSitemapEnd();
} }
return $this; return $this;
} }
protected function writeSitemapStart(): void private function writeSitemapStart()
{ {
$this->xmlWriter->startDocument("1.0", "UTF-8"); $this->xmlWriter->startDocument("1.0", "UTF-8");
if ($this->sitemapStylesheetLink != "") {
$this->xmlWriter->writePi('xml-stylesheet',
sprintf('type="text/xsl" href="%s"', $this->sitemapStylesheetLink));
}
$this->xmlWriter->writeComment(sprintf('generator-class="%s"', get_class($this))); $this->xmlWriter->writeComment(sprintf('generator-class="%s"', get_class($this)));
$this->xmlWriter->writeComment(sprintf('generator-version="%s"', $this->classVersion)); $this->xmlWriter->writeComment(sprintf('generator-version="%s"', $this->classVersion));
$this->xmlWriter->writeComment(sprintf('generated-on="%s"', date('c'))); $this->xmlWriter->writeComment(sprintf('generated-on="%s"', date('c')));
@ -462,48 +421,15 @@ class SitemapGenerator
$this->xmlWriter->writeAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9'); $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: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:video', 'http://www.google.com/schemas/sitemap-video/1.1');
$this->xmlWriter->writeAttribute('xmlns:image', 'http://www.google.com/schemas/sitemap-image/1.1');
$this->xmlWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); $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->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; $this->isSitemapStarted = true;
} }
/** private function writeSitemapUrl($loc, $lastModified, $changeFrequency, $priority, $alternates, $extensions)
* @param string $url
* @return string
* @throws UnexpectedValueException
*/
private function encodeEscapeURL(string $url): string
{ {
// In-place encoding only on non-ASCII characters, like browsers do.
$encoded = preg_replace_callback('/[^\x20-\x7f]/', function ($match) {
return urlencode($match[0]);
}, $url);
if (!is_string($encoded)) {
throw new UnexpectedValueException('Failed to encode URL');
}
return htmlspecialchars($encoded, ENT_QUOTES, 'UTF-8');
}
/**
* @param string $loc
* @param DateTime|null $lastModified
* @param string|null $changeFrequency
* @param float|null $priority
* @param array|null $alternates
* @param array $extensions
* @throws UnexpectedValueException
*/
private function writeSitemapUrl(
string $loc,
?DateTime $lastModified = null,
?string $changeFrequency = null,
?float $priority = null,
?array $alternates = null,
array $extensions = []
): void {
$this->xmlWriter->startElement('url'); $this->xmlWriter->startElement('url');
$this->xmlWriter->writeElement('loc', $this->encodeEscapeURL($loc)); $this->xmlWriter->writeElement('loc', htmlspecialchars($loc, ENT_QUOTES));
if ($lastModified !== null) { if ($lastModified !== null) {
$this->xmlWriter->writeElement('lastmod', $lastModified->format(DateTime::ATOM)); $this->xmlWriter->writeElement('lastmod', $lastModified->format(DateTime::ATOM));
@ -533,20 +459,17 @@ class SitemapGenerator
if ($extName === 'google_video') { if ($extName === 'google_video') {
GoogleVideoExtension::writeVideoTag($this->xmlWriter, $loc, $extFields); GoogleVideoExtension::writeVideoTag($this->xmlWriter, $loc, $extFields);
} }
if ($extName === 'google_image') {
GoogleImageExtension::writeImageTag($this->xmlWriter, $extFields);
}
} }
$this->xmlWriter->endElement(); // url $this->xmlWriter->endElement(); // url
$this->sitemapURLCount++; $this->sitemapUrlCount++;
$this->totalURLCount++; $this->totalUrlCount++;
} }
private function flushWriter(): void private function flushWriter()
{ {
$targetSitemapFilepath = $this->saveDirectory . sprintf($this->flushedSitemapFilenameFormat, $this->flushedSitemapCounter); $targetSitemapFilepath = $this->basePath . sprintf($this->flushedSitemapFilenameFormat, $this->flushedSitemapCounter);
$flushedString = $this->xmlWriter->outputMemory(); $flushedString = $this->xmlWriter->outputMemory(true);
$flushedStringLen = mb_strlen($flushedString); $flushedStringLen = mb_strlen($flushedString);
if ($flushedStringLen === 0) { if ($flushedStringLen === 0) {
@ -562,23 +485,22 @@ class SitemapGenerator
$this->fs->file_put_contents($targetSitemapFilepath, $flushedString, FILE_APPEND); $this->fs->file_put_contents($targetSitemapFilepath, $flushedString, FILE_APPEND);
} }
private function writeSitemapEnd(): void private function writeSitemapEnd()
{ {
$targetSitemapFilepath = $this->saveDirectory . sprintf($this->flushedSitemapFilenameFormat, $this->flushedSitemapCounter); $targetSitemapFilepath = $this->basePath . sprintf($this->flushedSitemapFilenameFormat, $this->flushedSitemapCounter);
$this->xmlWriter->endElement(); // urlset $this->xmlWriter->endElement(); // urlset
$this->xmlWriter->endDocument(); $this->xmlWriter->endDocument();
$this->fs->file_put_contents($targetSitemapFilepath, $this->xmlWriter->flush(), FILE_APPEND); $this->fs->file_put_contents($targetSitemapFilepath, $this->xmlWriter->flush(true), FILE_APPEND);
$this->isSitemapStarted = false; $this->isSitemapStarted = false;
$this->flushedSitemaps[] = $targetSitemapFilepath; $this->flushedSitemaps[] = $targetSitemapFilepath;
$this->flushedSitemapCounter++; $this->flushedSitemapCounter++;
$this->sitemapURLCount = 0; $this->sitemapUrlCount = 0;
$this->flushedSitemapSize = 0;
} }
/** /**
* Flush all stored urls from memory to the disk and close all necessary tags. * Flush all stored urls from memory to the disk and close all necessary tags.
*/ */
public function flush(): void public function flush()
{ {
$this->flushWriter(); $this->flushWriter();
if ($this->isSitemapStarted) { if ($this->isSitemapStarted) {
@ -588,9 +510,8 @@ class SitemapGenerator
/** /**
* Move flushed files to their final location. Compress if necessary. * Move flushed files to their final location. Compress if necessary.
* @throws RuntimeException
*/ */
public function finalize(): void public function finalize()
{ {
$this->generatedFiles = []; $this->generatedFiles = [];
@ -600,7 +521,7 @@ class SitemapGenerator
$targetSitemapFilename .= '.gz'; $targetSitemapFilename .= '.gz';
} }
$targetSitemapFilepath = $this->saveDirectory . $targetSitemapFilename; $targetSitemapFilepath = $this->basePath . $targetSitemapFilename;
if ($this->isCompressionEnabled) { if ($this->isCompressionEnabled) {
$this->fs->copy($this->flushedSitemaps[0], 'compress.zlib://' . $targetSitemapFilepath); $this->fs->copy($this->flushedSitemaps[0], 'compress.zlib://' . $targetSitemapFilepath);
@ -609,7 +530,7 @@ class SitemapGenerator
$this->fs->rename($this->flushedSitemaps[0], $targetSitemapFilepath); $this->fs->rename($this->flushedSitemaps[0], $targetSitemapFilepath);
} }
$this->generatedFiles['sitemaps_location'] = [$targetSitemapFilepath]; $this->generatedFiles['sitemaps_location'] = [$targetSitemapFilepath];
$this->generatedFiles['sitemaps_index_url'] = $this->sitemapIndexURL . '/' . $targetSitemapFilename; $this->generatedFiles['sitemaps_index_url'] = $this->baseURL . '/' . $targetSitemapFilename;
} else if (count($this->flushedSitemaps) > 1) { } else if (count($this->flushedSitemaps) > 1) {
$ext = '.' . pathinfo($this->sitemapFileName, PATHINFO_EXTENSION); $ext = '.' . pathinfo($this->sitemapFileName, PATHINFO_EXTENSION);
$targetExt = $ext; $targetExt = $ext;
@ -620,8 +541,8 @@ class SitemapGenerator
$sitemapsUrls = []; $sitemapsUrls = [];
$targetSitemapFilepaths = []; $targetSitemapFilepaths = [];
foreach ($this->flushedSitemaps as $i => $flushedSitemap) { foreach ($this->flushedSitemaps as $i => $flushedSitemap) {
$targetSitemapFilename = str_replace($ext, ((int)$i + 1) . $targetExt, $this->sitemapFileName); $targetSitemapFilename = str_replace($ext, ($i + 1) . $targetExt, $this->sitemapFileName);
$targetSitemapFilepath = $this->saveDirectory . $targetSitemapFilename; $targetSitemapFilepath = $this->basePath . $targetSitemapFilename;
if ($this->isCompressionEnabled) { if ($this->isCompressionEnabled) {
$this->fs->copy($flushedSitemap, 'compress.zlib://' . $targetSitemapFilepath); $this->fs->copy($flushedSitemap, 'compress.zlib://' . $targetSitemapFilepath);
@ -629,24 +550,23 @@ class SitemapGenerator
} else { } else {
$this->fs->rename($flushedSitemap, $targetSitemapFilepath); $this->fs->rename($flushedSitemap, $targetSitemapFilepath);
} }
$sitemapsUrls[] = htmlspecialchars( $sitemapsUrls[] = htmlspecialchars($this->baseURL . '/' . $targetSitemapFilename, ENT_QUOTES);
$this->sitemapIndexURL . '/' . $targetSitemapFilename, ENT_QUOTES);
$targetSitemapFilepaths[] = $targetSitemapFilepath; $targetSitemapFilepaths[] = $targetSitemapFilepath;
} }
$targetSitemapIndexFilepath = $this->saveDirectory . $this->sitemapIndexFileName; $targetSitemapIndexFilepath = $this->basePath . $this->sitemapIndexFileName;
$this->createSitemapIndex($sitemapsUrls, $targetSitemapIndexFilepath); $this->createSitemapIndex($sitemapsUrls, $targetSitemapIndexFilepath);
$this->generatedFiles['sitemaps_location'] = $targetSitemapFilepaths; $this->generatedFiles['sitemaps_location'] = $targetSitemapFilepaths;
$this->generatedFiles['sitemaps_index_location'] = $targetSitemapIndexFilepath; $this->generatedFiles['sitemaps_index_location'] = $targetSitemapIndexFilepath;
$this->generatedFiles['sitemaps_index_url'] = $this->sitemapIndexURL . '/' . $this->sitemapIndexFileName; $this->generatedFiles['sitemaps_index_url'] = $this->baseURL . '/' . $this->sitemapIndexFileName;
} else { } else {
throw new RuntimeException('failed to finalize, please add urls and flush first'); throw new RuntimeException('failed to finalize, please add urls and flush first');
} }
} }
private function createSitemapIndex(array $sitemapsUrls, string $sitemapIndexFileName): void private function createSitemapIndex($sitemapsUrls, $sitemapIndexFileName)
{ {
$this->xmlWriter->flush(); $this->xmlWriter->flush(true);
$this->writeSitemapIndexStart(); $this->writeSitemapIndexStart();
foreach ($sitemapsUrls as $sitemapsUrl) { foreach ($sitemapsUrls as $sitemapsUrl) {
$this->writeSitemapIndexUrl($sitemapsUrl); $this->writeSitemapIndexUrl($sitemapsUrl);
@ -654,11 +574,12 @@ class SitemapGenerator
$this->writeSitemapIndexEnd(); $this->writeSitemapIndexEnd();
$this->fs->file_put_contents( $this->fs->file_put_contents(
$sitemapIndexFileName, $sitemapIndexFileName,
$this->xmlWriter->flush(), $this->xmlWriter->flush(true),
FILE_APPEND
); );
} }
protected function writeSitemapIndexStart(): void private function writeSitemapIndexStart()
{ {
$this->xmlWriter->startDocument("1.0", "UTF-8"); $this->xmlWriter->startDocument("1.0", "UTF-8");
$this->xmlWriter->writeComment(sprintf('generator-class="%s"', get_class($this))); $this->xmlWriter->writeComment(sprintf('generator-class="%s"', get_class($this)));
@ -670,19 +591,15 @@ class SitemapGenerator
$this->xmlWriter->writeAttribute('xsi:schemaLocation', 'http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd'); $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)
* @param string $url
* @throws UnexpectedValueException
*/
private function writeSitemapIndexUrl(string $url): void
{ {
$this->xmlWriter->startElement('sitemap'); $this->xmlWriter->startElement('sitemap');
$this->xmlWriter->writeElement('loc', $this->encodeEscapeURL($url)); $this->xmlWriter->writeElement('loc', htmlspecialchars($url, ENT_QUOTES));
$this->xmlWriter->writeElement('lastmod', date('c')); $this->xmlWriter->writeElement('lastmod', date('c'));
$this->xmlWriter->endElement(); // sitemap $this->xmlWriter->endElement(); // sitemap
} }
private function writeSitemapIndexEnd(): void private function writeSitemapIndexEnd()
{ {
$this->xmlWriter->endElement(); // sitemapindex $this->xmlWriter->endElement(); // sitemapindex
$this->xmlWriter->endDocument(); $this->xmlWriter->endDocument();
@ -698,40 +615,34 @@ class SitemapGenerator
/** /**
* Will inform search engines about newly created sitemaps. * Will inform search engines about newly created sitemaps.
* Google and Yandex will be notified. * 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 * @return array of messages and http codes from each search engine
* @access public * @access public
* @throws BadMethodCallException * @throws BadMethodCallException
*/ */
public function submitSitemap(): array public function submitSitemap($yahooAppId = null): array
{ {
if (count($this->generatedFiles) === 0) { if (count($this->generatedFiles) === 0) {
throw new BadMethodCallException("To update robots.txt, call finalize() first."); throw new BadMethodCallException("To update robots.txt, call finalize() first.");
} }
if (!$this->runtime->extension_loaded('curl')) { if (!$this->runtime->extension_loaded('curl')) {
throw new BadMethodCallException("curl extension is needed to do submission."); throw new BadMethodCallException("cURL extension is needed to do submission.");
} }
$searchEngines = $this->searchEngines; $searchEngines = $this->searchEngines;
$searchEngines[0] = isset($yahooAppId) ?
str_replace("USERID", $yahooAppId, $searchEngines[0][0]) :
$searchEngines[0][1];
$result = []; $result = [];
for ($i = 0; $i < count($searchEngines); $i++) { for ($i = 0; $i < count($searchEngines); $i++) {
$submitUrl = $searchEngines[$i] . htmlspecialchars($this->generatedFiles['sitemaps_index_url'], ENT_QUOTES); $submitUrl = $searchEngines[$i] . htmlspecialchars($this->generatedFiles['sitemaps_index_url'], ENT_QUOTES);
$curlResource = $this->runtime->curl_init($submitUrl); $submitSite = $this->runtime->curl_init($submitUrl);
if (is_bool($curlResource) && !$curlResource) { $this->runtime->curl_setopt($submitSite, CURLOPT_RETURNTRANSFER, true);
throw new RuntimeException("failed to execute curl_init for url " . $submitUrl); $responseContent = $this->runtime->curl_exec($submitSite);
} $response = $this->runtime->curl_getinfo($submitSite);
if (!$this->runtime->curl_setopt($curlResource, CURLOPT_RETURNTRANSFER, true)) {
throw new RuntimeException(
"failed to set curl option CURLOPT_RETURNTRANSFER to true, error: "
. $this->runtime->curl_error($curlResource)
);
}
$responseContent = $this->runtime->curl_exec($curlResource);
if (is_bool($responseContent) && !$responseContent) {
throw new RuntimeException(
"failed to run curl_exec, error: " . $this->runtime->curl_error($curlResource)
);
}
$response = $this->runtime->curl_getinfo($curlResource);
$submitSiteShort = array_reverse(explode(".", parse_url($searchEngines[$i], PHP_URL_HOST))); $submitSiteShort = array_reverse(explode(".", parse_url($searchEngines[$i], PHP_URL_HOST)));
$result[] = [ $result[] = [
"site" => $submitSiteShort[1] . "." . $submitSiteShort[0], "site" => $submitSiteShort[1] . "." . $submitSiteShort[0],
@ -759,7 +670,7 @@ class SitemapGenerator
throw new BadMethodCallException("To update robots.txt, call finalize() first."); throw new BadMethodCallException("To update robots.txt, call finalize() first.");
} }
$robotsFilePath = $this->saveDirectory . $this->robotsFileName; $robotsFilePath = $this->basePath . $this->robotsFileName;
$robotsFileContent = $this->createNewRobotsContentFromFile($robotsFilePath); $robotsFileContent = $this->createNewRobotsContentFromFile($robotsFilePath);
@ -769,38 +680,28 @@ class SitemapGenerator
} }
/** /**
* @param string $filepath * @param $filepath
* @return string * @return string
* @throws RuntimeException
*/ */
private function createNewRobotsContentFromFile(string $filepath): string private function createNewRobotsContentFromFile($filepath): string
{ {
if ($this->fs->file_exists($filepath)) { if ($this->fs->file_exists($filepath)) {
$existingContent = $this->fs->file_get_contents($filepath); $robotsFileContent = "";
// if $existingContent is bool and false, it means that file exists but is not readable $robotsFile = explode(PHP_EOL, $this->fs->file_get_contents($filepath));
if (is_bool($existingContent) && !$existingContent) { foreach ($robotsFile as $key => $value) {
throw new RuntimeException("Failed to read existing robots.txt file: $filepath"); if (substr($value, 0, 8) == 'Sitemap:') {
} unset($robotsFile[$key]);
if (is_string($existingContent)) {
$contentLines = explode(PHP_EOL, $existingContent);
} else {
$contentLines = [];
}
$newContent = "";
foreach ($contentLines as $key => $line) {
if (str_starts_with($line, 'Sitemap:')) {
unset($contentLines[$key]);
} else { } else {
$newContent .= $line . PHP_EOL; $robotsFileContent .= $value . PHP_EOL;
} }
} }
} else { } else {
$newContent = $this->getSampleRobotsContent(); $robotsFileContent = $this->getSampleRobotsContent();
} }
$newContent .= "Sitemap: {$this->generatedFiles['sitemaps_index_url']}"; $robotsFileContent .= "Sitemap: {$this->generatedFiles['sitemaps_index_url']}";
return $newContent; return $robotsFileContent;
} }
/** /**

View File

@ -201,7 +201,22 @@ class common
// Descripteur de données Entrées / Sorties // Descripteur de données Entrées / Sorties
// Liste ici tous les fichiers de données // Liste ici tous les fichiers de données
public $dataFiles = []; public $dataFiles = [
'admin' => null,
'blacklist' => null,
'config' => null,
'core' => null,
'course' => null,
'font' => null,
'module' => null,
'page' => null,
'theme' => null,
'user' => null,
'language' => null,
'profil' => null,
'enrolment' => null,
'category' => null,
];
private $configFiles = [ private $configFiles = [
'admin' => '', 'admin' => '',
@ -1105,133 +1120,126 @@ class common
*/ */
public function updateSitemap() public function updateSitemap()
{ {
// Le drapeau prend true quand au moins une page est trouvée // Le drapeau prend true quand au moins une page est trouvée
$flag = false; $flag = false;
// Rafraîchit la liste des pages après une modification de pageId notamment
$this->buildHierarchy();
// Actualise la liste des pages pour TinyMCE
$this->tinyMcePages();
//require_once 'core/vendor/sitemap/SitemapGenerator.php';
$timezone = $this->getData(['config', 'timezone']);
$outputDir = getcwd();
$sitemap = new \Icamys\SitemapGenerator\SitemapGenerator(helper::baseurl(false), $outputDir);
// will create also compressed (gzipped) sitemap : option buguée
// $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->setMaxUrlsPerSitemap(50000);
// sitemap file name
$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
foreach ($this->getHierarchy() as $parentPageId => $childrenPageIds) {
// Exclure les barres et les pages non publiques et les pages masquées
if (
$this->getData(['page', $parentPageId, 'group']) !== 0 ||
$this->getData(['page', $parentPageId, 'block']) === 'bar'
) {
continue;
}
// Page désactivée, traiter les sous-pages sans prendre en compte la page parente.
if ($this->getData(['page', $parentPageId, 'disable']) !== true) {
// Cas de la page d'accueil ne pas dupliquer l'URL
$pageId = ($parentPageId !== $this->homePageId()) ? $parentPageId : '';
$sitemap->addUrl('/' . $pageId, $datetime);
$flag = true;
}
// Articles du blog
if (
$this->getData(['page', $parentPageId, 'moduleId']) === 'blog'
&& !empty($this->getData(['module', $parentPageId]))
&& $this->getData(['module', $parentPageId, 'posts'])
) {
foreach ($this->getData(['module', $parentPageId, 'posts']) as $articleId => $article) {
if ($this->getData(['module', $parentPageId, 'posts', $articleId, 'state']) === true) {
$date = $this->getData(['module', $parentPageId, 'posts', $articleId, 'publishedOn']);
$sitemap->addUrl('/' . $parentPageId . '/' . $articleId, DateTime::createFromFormat('U', $date));
$flag = true;
}
}
}
// Sous-pages
foreach ($childrenPageIds as $childKey) {
if ($this->getData(['page', $childKey, 'group']) !== 0 || $this->getData(['page', $childKey, 'disable']) === true) {
continue;
}
// Cas de la page d'accueil ne pas dupliquer l'URL
$pageId = ($childKey !== $this->homePageId()) ? $childKey : '';
$sitemap->addUrl('/' . $childKey, $datetime);
$flag = true;
// La sous-page est un blog
if (
$this->getData(['page', $childKey, 'moduleId']) === 'blog' &&
!empty($this->getData(['module', $childKey]))
) {
foreach ($this->getData(['module', $childKey, 'posts']) as $articleId => $article) {
if ($this->getData(['module', $childKey, 'posts', $articleId, 'state']) === true) {
$date = $this->getData(['module', $childKey, 'posts', $articleId, 'publishedOn']);
$sitemap->addUrl('/' . $childKey . '/' . $articleId, DateTime::createFromFormat('U', $date));
$flag = true;
}
}
}
}
}
if ($flag === false) {
return false;
}
// Flush all stored urls from memory to the disk and close all necessary tags.
$sitemap->flush();
// Move flushed files to their final location. Compress if the option is enabled.
$sitemap->finalize();
// Update robots.txt file in output directory
if ($this->getData(['config', 'seo', 'robots']) === true) {
if (file_exists('robots.txt')) {
unlink('robots.txt');
}
$sitemap->updateRobots();
} else {
file_put_contents('robots.txt', 'User-agent: *' . PHP_EOL . 'Disallow: /');
}
// Submit your sitemaps to Google, Yahoo, Bing and Ask.com
if (empty($this->getData(['config', 'proxyType']) . $this->getData(['config', 'proxyUrl']) . ':' . $this->getData(['config', 'proxyPort']))) {
$sitemap->submitSitemap();
}
return (file_exists('sitemap.xml') && file_exists('robots.txt'));
}
// Rafraîchit la liste des pages après une modification de pageId notamment
$this->buildHierarchy();
// Actualise la liste des pages pour TinyMCE
$this->tinyMcePages();
//require_once 'core/vendor/sitemap/SitemapGenerator.php';
$timezone = $this->getData(['config', 'timezone']);
$config = new \Icamys\SitemapGenerator\Config();
// Your site URL.
$config->setBaseURL(helper::baseurl(false));
// // OPTIONAL. Setting the current working directory to be output directory
$config->setSaveDirectory(sys_get_temp_dir());
$sitemap = new \Icamys\SitemapGenerator\SitemapGenerator($config);
// Create a compressed sitemap
$sitemap->enableCompression();
// will create also compressed (gzipped) sitemap : option buguée
// $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->setMaxUrlsPerSitemap(50000);
// sitemap file name
$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
foreach ($this->getHierarchy() as $parentPageId => $childrenPageIds) {
// Exclure les barres et les pages non publiques et les pages masquées
if (
$this->getData(['page', $parentPageId, 'group']) !== 0 ||
$this->getData(['page', $parentPageId, 'block']) === 'bar'
) {
continue;
}
// Page désactivée, traiter les sous-pages sans prendre en compte la page parente.
if ($this->getData(['page', $parentPageId, 'disable']) !== true) {
// Cas de la page d'accueil ne pas dupliquer l'URL
$pageId = ($parentPageId !== $this->getData(['locale', 'homePageId'])) ? $parentPageId : '';
$sitemap->addUrl('/' . $pageId, $datetime);
$flag = true;
}
// Articles du blog
if (
$this->getData(['page', $parentPageId, 'moduleId']) === 'blog'
&& !empty($this->getData(['module', $parentPageId]))
&& $this->getData(['module', $parentPageId, 'posts'])
) {
foreach ($this->getData(['module', $parentPageId, 'posts']) as $articleId => $article) {
if ($this->getData(['module', $parentPageId, 'posts', $articleId, 'state']) === true) {
$date = $this->getData(['module', $parentPageId, 'posts', $articleId, 'publishedOn']);
$sitemap->addUrl('/' . $parentPageId . '/' . $articleId, DateTime::createFromFormat('U', $date));
}
}
}
// Sous-pages
foreach ($childrenPageIds as $childKey) {
if ($this->getData(['page', $childKey, 'group']) !== 0 || $this->getData(['page', $childKey, 'disable']) === true) {
continue;
}
// Cas de la page d'accueil ne pas dupliquer l'URL
$pageId = ($childKey !== $this->getData(['locale', 'homePageId'])) ? $childKey : '';
$sitemap->addUrl('/' . $childKey, $datetime);
$flag = true;
// La sous-page est un blog
if (
$this->getData(['page', $childKey, 'moduleId']) === 'blog' &&
!empty($this->getData(['module', $childKey]))
) {
foreach ($this->getData(['module', $childKey, 'posts']) as $articleId => $article) {
if ($this->getData(['module', $childKey, 'posts', $articleId, 'state']) === true) {
$date = $this->getData(['module', $childKey, 'posts', $articleId, 'publishedOn']);
$sitemap->addUrl('/' . $childKey . '/' . $articleId, new DateTime("@{$date}", new DateTimeZone($timezone)));
}
}
}
}
}
if ($flag === false) {
return false;
}
// Flush all stored urls from memory to the disk and close all necessary tags.
$sitemap->flush();
// Move flushed files to their final location. Compress if the option is enabled.
$sitemap->finalize();
// Update robots.txt file in output directory
if ($this->getData(['config', 'seo', 'robots']) === true) {
if (file_exists('robots.txt')) {
unlink('robots.txt');
}
$sitemap->updateRobots();
} else {
$this->secure_file_put_contents('robots.txt', 'User-agent: *' . PHP_EOL . 'Disallow: /');
}
// Submit your sitemaps to Google, Yahoo, Bing and Ask.com
if (empty($this->getData(['config', 'proxyType']) . $this->getData(['config', 'proxyUrl']) . ':' . $this->getData(['config', 'proxyPort']))) {
$sitemap->submitSitemap();
}
return (file_exists('sitemap.xml') && file_exists('robots.txt'));
}
/* /*
* Création d'une miniature * Création d'une miniature
* Fonction utilisée lors de la mise à jour d'une version 9 à une version 10 * Fonction utilisée lors de la mise à jour d'une version 9 à une version 10