Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
Sitemap.php
Go to the documentation of this file.
1 <?php
6 namespace Magento\Sitemap\Model;
7 
16 
33 class Sitemap extends \Magento\Framework\Model\AbstractModel implements \Magento\Framework\DataObject\IdentityInterface
34 {
35  const OPEN_TAG_KEY = 'start';
36 
37  const CLOSE_TAG_KEY = 'end';
38 
39  const INDEX_FILE_PREFIX = 'sitemap';
40 
41  const TYPE_INDEX = 'sitemap';
42 
43  const TYPE_URL = 'url';
44 
48  const LAST_MOD_MIN_VAL = '0000-01-01 00:00:00';
49 
55  protected $_filePath;
56 
62  protected $_sitemapItems = [];
63 
69  protected $_sitemapIncrement = 0;
70 
76  protected $_tags = [];
77 
83  protected $_lineCount = 0;
84 
90  protected $_fileSize = 0;
91 
97  private $_crlf = ["win" => "\r\n", "unix" => "\n", "mac" => "\r"];
98 
102  protected $_directory;
103 
107  protected $_stream;
108 
114  protected $_sitemapData;
115 
119  protected $_escaper;
120 
124  protected $_categoryFactory;
125 
129  protected $_productFactory;
130 
134  protected $_cmsFactory;
135 
139  protected $_dateModel;
140 
144  protected $_storeManager;
145 
149  protected $_request;
150 
154  protected $dateTime;
155 
162  protected $_cacheTag = true;
163 
169  private $itemProvider;
170 
176  private $configReader;
177 
183  private $sitemapItemFactory;
184 
190  private $lastModMinTsVal;
191 
216  public function __construct(
217  \Magento\Framework\Model\Context $context,
218  \Magento\Framework\Registry $registry,
219  \Magento\Framework\Escaper $escaper,
220  \Magento\Sitemap\Helper\Data $sitemapData,
221  \Magento\Framework\Filesystem $filesystem,
222  \Magento\Sitemap\Model\ResourceModel\Catalog\CategoryFactory $categoryFactory,
223  \Magento\Sitemap\Model\ResourceModel\Catalog\ProductFactory $productFactory,
224  \Magento\Sitemap\Model\ResourceModel\Cms\PageFactory $cmsFactory,
225  \Magento\Framework\Stdlib\DateTime\DateTime $modelDate,
226  \Magento\Store\Model\StoreManagerInterface $storeManager,
227  \Magento\Framework\App\RequestInterface $request,
228  \Magento\Framework\Stdlib\DateTime $dateTime,
229  \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
230  \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
231  array $data = [],
232  \Magento\Config\Model\Config\Reader\Source\Deployed\DocumentRoot $documentRoot = null,
233  ItemProviderInterface $itemProvider = null,
234  SitemapConfigReaderInterface $configReader = null,
235  \Magento\Sitemap\Model\SitemapItemInterfaceFactory $sitemapItemFactory = null
236  ) {
237  $this->_escaper = $escaper;
238  $this->_sitemapData = $sitemapData;
239  $documentRoot = $documentRoot ?: ObjectManager::getInstance()->get(DocumentRoot::class);
240  $this->_directory = $filesystem->getDirectoryWrite($documentRoot->getPath());
241  $this->_categoryFactory = $categoryFactory;
242  $this->_productFactory = $productFactory;
243  $this->_cmsFactory = $cmsFactory;
244  $this->_dateModel = $modelDate;
245  $this->_storeManager = $storeManager;
246  $this->_request = $request;
247  $this->dateTime = $dateTime;
248  $this->itemProvider = $itemProvider ?: ObjectManager::getInstance()->get(ItemProviderInterface::class);
249  $this->configReader = $configReader ?: ObjectManager::getInstance()->get(SitemapConfigReaderInterface::class);
250  $this->sitemapItemFactory = $sitemapItemFactory ?: ObjectManager::getInstance()->get(
251  \Magento\Sitemap\Model\SitemapItemInterfaceFactory::class
252  );
253  parent::__construct($context, $registry, $resource, $resourceCollection, $data);
254  }
255 
261  protected function _construct()
262  {
263  $this->_init(SitemapResource::class);
264  }
265 
272  protected function _getStream()
273  {
274  if ($this->_stream) {
275  return $this->_stream;
276  } else {
277  throw new LocalizedException(__('File handler unreachable'));
278  }
279  }
280 
289  public function addSitemapItem(DataObject $sitemapItem)
290  {
291  $this->_sitemapItems[] = $sitemapItem;
292 
293  return $this;
294  }
295 
303  public function collectSitemapItems()
304  {
307  $storeId = $this->getStoreId();
308 
309  $this->addSitemapItem(new DataObject(
310  [
311  'changefreq' => $helper->getCategoryChangefreq($storeId),
312  'priority' => $helper->getCategoryPriority($storeId),
313  'collection' => $this->_categoryFactory->create()->getCollection($storeId),
314  ]
315  ));
316 
317  $this->addSitemapItem(new DataObject(
318  [
319  'changefreq' => $helper->getProductChangefreq($storeId),
320  'priority' => $helper->getProductPriority($storeId),
321  'collection' => $this->_productFactory->create()->getCollection($storeId),
322  ]
323  ));
324 
325  $this->addSitemapItem(new DataObject(
326  [
327  'changefreq' => $helper->getPageChangefreq($storeId),
328  'priority' => $helper->getPagePriority($storeId),
329  'collection' => $this->_cmsFactory->create()->getCollection($storeId),
330  ]
331  ));
332  }
333 
339  protected function _initSitemapItems()
340  {
341  $sitemapItems = $this->itemProvider->getItems($this->getStoreId());
342  $mappedItems = $this->mapToSitemapItem();
343  $this->_sitemapItems = array_merge($sitemapItems, $mappedItems);
344 
345  $this->_tags = [
346  self::TYPE_INDEX => [
347  self::OPEN_TAG_KEY => '<?xml version="1.0" encoding="UTF-8"?>' .
348  PHP_EOL .
349  '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' .
350  PHP_EOL,
351  self::CLOSE_TAG_KEY => '</sitemapindex>',
352  ],
353  self::TYPE_URL => [
354  self::OPEN_TAG_KEY => '<?xml version="1.0" encoding="UTF-8"?>' .
355  PHP_EOL .
356  '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"' .
357  ' xmlns:content="http://www.google.com/schemas/sitemap-content/1.0"' .
358  ' xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">' .
359  PHP_EOL,
360  self::CLOSE_TAG_KEY => '</urlset>',
361  ],
362  ];
363  }
364 
371  public function beforeSave()
372  {
373  $path = $this->getSitemapPath();
374 
378  if ($path && preg_match('#\.\.[\\\/]#', $path)) {
379  throw new LocalizedException(__('Please define a correct path.'));
380  }
384  if (!$this->_directory->isExist($path)) {
385  throw new LocalizedException(
386  __(
387  'Please create the specified folder "%1" before saving the sitemap.',
388  $this->_escaper->escapeHtml($this->getSitemapPath())
389  )
390  );
391  }
392 
393  if (!$this->_directory->isWritable($path)) {
394  throw new LocalizedException(
395  __('Please make sure that "%1" is writable by the web-server.', $this->getSitemapPath())
396  );
397  }
401  if (!preg_match('#^[a-zA-Z0-9_\.]+$#', $this->getSitemapFilename())) {
402  throw new LocalizedException(
403  __(
404  'Please use only letters (a-z or A-Z), numbers (0-9) or underscores (_) in the filename.'
405  . ' No spaces or other characters are allowed.'
406  )
407  );
408  }
409  if (!preg_match('#\.xml$#', $this->getSitemapFilename())) {
410  $this->setSitemapFilename($this->getSitemapFilename() . '.xml');
411  }
412 
413  $this->setSitemapPath(rtrim(str_replace(str_replace('\\', '/', $this->_getBaseDir()), '', $path), '/') . '/');
414 
415  return parent::beforeSave();
416  }
417 
425  public function generateXml()
426  {
427  $this->_initSitemapItems();
428 
430  foreach ($this->_sitemapItems as $item) {
431  $xml = $this->_getSitemapRow(
432  $item->getUrl(),
433  $item->getUpdatedAt(),
434  $item->getChangeFrequency(),
435  $item->getPriority(),
436  $item->getImages()
437  );
438 
439  if ($this->_isSplitRequired($xml) && $this->_sitemapIncrement > 0) {
440  $this->_finalizeSitemap();
441  }
442 
443  if (!$this->_fileSize) {
444  $this->_createSitemap();
445  }
446 
447  $this->_writeSitemapRow($xml);
448  // Increase counters
449  $this->_lineCount++;
450  $this->_fileSize += strlen($xml);
451  }
452 
453  $this->_finalizeSitemap();
454 
455  if ($this->_sitemapIncrement == 1) {
456  // In case when only one increment file was created use it as default sitemap
457  $path = rtrim(
458  $this->getSitemapPath(),
459  '/'
460  ) . '/' . $this->_getCurrentSitemapFilename(
461  $this->_sitemapIncrement
462  );
463  $destination = rtrim($this->getSitemapPath(), '/') . '/' . $this->getSitemapFilename();
464 
465  $this->_directory->renameFile($path, $destination);
466  } else {
467  // Otherwise create index file with list of generated sitemaps
468  $this->_createSitemapIndex();
469  }
470 
471  $this->setSitemapTime($this->_dateModel->gmtDate('Y-m-d H:i:s'));
472  $this->save();
473 
474  return $this;
475  }
476 
482  protected function _createSitemapIndex()
483  {
484  $this->_createSitemap($this->getSitemapFilename(), self::TYPE_INDEX);
485  for ($i = 1; $i <= $this->_sitemapIncrement; $i++) {
486  $xml = $this->_getSitemapIndexRow($this->_getCurrentSitemapFilename($i), $this->_getCurrentDateTime());
487  $this->_writeSitemapRow($xml);
488  }
489  $this->_finalizeSitemap(self::TYPE_INDEX);
490  }
491 
497  protected function _getCurrentDateTime()
498  {
499  return (new \DateTime())->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT);
500  }
501 
508  protected function _isSplitRequired($row)
509  {
510  $storeId = $this->getStoreId();
511  if ($this->_lineCount + 1 > $this->configReader->getMaximumLinesNumber($storeId)) {
512  return true;
513  }
514 
515  if ($this->_fileSize + strlen($row) > $this->configReader->getMaximumFileSize($storeId)) {
516  return true;
517  }
518 
519  return false;
520  }
521 
537  protected function _getSitemapRow($url, $lastmod = null, $changefreq = null, $priority = null, $images = null)
538  {
539  $url = $this->_getUrl($url);
540  $row = '<loc>' . htmlspecialchars($url) . '</loc>';
541  if ($lastmod) {
542  $row .= '<lastmod>' . $this->_getFormattedLastmodDate($lastmod) . '</lastmod>';
543  }
544  if ($changefreq) {
545  $row .= '<changefreq>' . $changefreq . '</changefreq>';
546  }
547  if ($priority) {
548  $row .= sprintf('<priority>%.1f</priority>', $priority);
549  }
550  if ($images) {
551  // Add Images to sitemap
552  foreach ($images->getCollection() as $image) {
553  $row .= '<image:image>';
554  $row .= '<image:loc>' . htmlspecialchars($image->getUrl()) . '</image:loc>';
555  $row .= '<image:title>' . htmlspecialchars($images->getTitle()) . '</image:title>';
556  if ($image->getCaption()) {
557  $row .= '<image:caption>' . htmlspecialchars($image->getCaption()) . '</image:caption>';
558  }
559  $row .= '</image:image>';
560  }
561  // Add PageMap image for Google web search
562  $row .= '<PageMap xmlns="http://www.google.com/schemas/sitemap-pagemap/1.0"><DataObject type="thumbnail">';
563  $row .= '<Attribute name="name" value="' . htmlspecialchars($images->getTitle()) . '"/>';
564  $row .= '<Attribute name="src" value="' . htmlspecialchars($images->getThumbnail()) . '"/>';
565  $row .= '</DataObject></PageMap>';
566  }
567 
568  return '<url>' . $row . '</url>';
569  }
570 
578  protected function _getSitemapIndexRow($sitemapFilename, $lastmod = null)
579  {
580  $url = $this->getSitemapUrl($this->getSitemapPath(), $sitemapFilename);
581  $row = '<loc>' . htmlspecialchars($url) . '</loc>';
582  if ($lastmod) {
583  $row .= '<lastmod>' . $this->_getFormattedLastmodDate($lastmod) . '</lastmod>';
584  }
585 
586  return '<sitemap>' . $row . '</sitemap>';
587  }
588 
597  protected function _createSitemap($fileName = null, $type = self::TYPE_URL)
598  {
599  if (!$fileName) {
600  $this->_sitemapIncrement++;
601  $fileName = $this->_getCurrentSitemapFilename($this->_sitemapIncrement);
602  }
603 
604  $path = rtrim($this->getSitemapPath(), '/') . '/' . $fileName;
605  $this->_stream = $this->_directory->openFile($path);
606 
607  $fileHeader = sprintf($this->_tags[$type][self::OPEN_TAG_KEY], $type);
608  $this->_stream->write($fileHeader);
609  $this->_fileSize = strlen($fileHeader . sprintf($this->_tags[$type][self::CLOSE_TAG_KEY], $type));
610  }
611 
618  protected function _writeSitemapRow($row)
619  {
620  $this->_getStream()->write($row . PHP_EOL);
621  }
622 
629  protected function _finalizeSitemap($type = self::TYPE_URL)
630  {
631  if ($this->_stream) {
632  $this->_stream->write(sprintf($this->_tags[$type][self::CLOSE_TAG_KEY], $type));
633  $this->_stream->close();
634  }
635 
636  // Reset all counters
637  $this->_lineCount = 0;
638  $this->_fileSize = 0;
639  }
640 
647  protected function _getCurrentSitemapFilename($index)
648  {
649  return str_replace('.xml', '', $this->getSitemapFilename()) . '-' . $this->getStoreId() . '-' . $index . '.xml';
650  }
651 
657  protected function _getBaseDir()
658  {
659  return $this->_directory->getAbsolutePath();
660  }
661 
668  protected function _getStoreBaseUrl($type = UrlInterface::URL_TYPE_LINK)
669  {
671  $store = $this->_storeManager->getStore($this->getStoreId());
672  $isSecure = $store->isUrlSecure();
673  return rtrim($store->getBaseUrl($type, $isSecure), '/') . '/';
674  }
675 
684  {
685  return $this->_getStoreBaseUrl($type) . ltrim($url, '/');
686  }
687 
696  protected function _getMediaUrl($url)
697  {
698  return $this->_getUrl($url, UrlInterface::URL_TYPE_MEDIA);
699  }
700 
707  protected function _getFormattedLastmodDate($date)
708  {
709  if ($this->lastModMinTsVal === null) {
710  $this->lastModMinTsVal = strtotime(self::LAST_MOD_MIN_VAL);
711  }
712  $timestamp = max(strtotime($date), $this->lastModMinTsVal);
713  return date('c', $timestamp);
714  }
715 
721  protected function _getDocumentRoot()
722  {
723  return realpath($this->_request->getServer('DOCUMENT_ROOT'));
724  }
725 
731  protected function _getStoreBaseDomain()
732  {
733  $storeParsedUrl = parse_url($this->_getStoreBaseUrl());
734  $url = $storeParsedUrl['scheme'] . '://' . $storeParsedUrl['host'];
735 
736  $documentRoot = trim(str_replace('\\', '/', $this->_getDocumentRoot()), '/');
737  $baseDir = trim(str_replace('\\', '/', $this->_getBaseDir()), '/');
738 
739  if (strpos($baseDir, $documentRoot) === 0) {
740  //case when basedir is in document root
741  $installationFolder = trim(str_replace($documentRoot, '', $baseDir), '/');
742  $storeDomain = rtrim($url . '/' . $installationFolder, '/');
743  } else {
744  //case when documentRoot contains symlink to basedir
745  $url = $this->_getStoreBaseUrl(UrlInterface::URL_TYPE_WEB);
746  $storeDomain = rtrim($url, '/');
747  }
748 
749  return $storeDomain;
750  }
751 
759  public function getSitemapUrl($sitemapPath, $sitemapFileName)
760  {
761  return $this->_getStoreBaseDomain() . str_replace('//', '/', $sitemapPath . '/' . $sitemapFileName);
762  }
763 
771  protected function _isEnabledSubmissionRobots()
772  {
773  $storeId = $this->getStoreId();
774  return (bool)$this->configReader->getEnableSubmissionRobots($storeId);
775  }
776 
785  protected function _addSitemapToRobotsTxt($sitemapFileName)
786  {
787  $robotsSitemapLine = 'Sitemap: ' . $this->getSitemapUrl($this->getSitemapPath(), $sitemapFileName);
788 
789  $filename = 'robots.txt';
790  $content = '';
791  if ($this->_directory->isExist($filename)) {
792  $content = $this->_directory->readFile($filename);
793  }
794 
795  if (strpos($content, $robotsSitemapLine) === false) {
796  if (!empty($content)) {
797  $content .= $this->_findNewLinesDelimiter($content);
798  }
799  $content .= $robotsSitemapLine;
800  }
801 
802  $this->_directory->writeFile($filename, $content);
803  }
804 
811  private function _findNewLinesDelimiter($text)
812  {
813  foreach ($this->_crlf as $delimiter) {
814  if (strpos($text, $delimiter) !== false) {
815  return $delimiter;
816  }
817  }
818 
819  return PHP_EOL;
820  }
821 
827  private function mapToSitemapItem()
828  {
829  $items = [];
830 
831  foreach ($this->_sitemapItems as $data) {
832  foreach ($data->getCollection() as $item) {
833  $items[] = $this->sitemapItemFactory->create([
834  'url' => $item->getUrl(),
835  'updatedAt' => $item->getUpdatedAt(),
836  'images' => $item->getImages(),
837  'priority' => $data->getPriority(),
838  'changeFrequency' => $data->getChangeFrequency(),
839  ]);
840  }
841  }
842 
843  return $items;
844  }
845 
852  public function getIdentities()
853  {
854  return [
855  Value::CACHE_TAG . '_' . $this->getStoreId(),
856  ];
857  }
858 }
$helper
Definition: iframe.phtml:13
_createSitemap($fileName=null, $type=self::TYPE_URL)
Definition: Sitemap.php:597
$baseDir
Definition: autoload.php:9
_getUrl($url, $type=UrlInterface::URL_TYPE_LINK)
Definition: Sitemap.php:683
$storeManager
__()
Definition: __.php:13
endifif( $block->getLastPageNum()>1)( 'Page') ?></strong >< ul class $text
Definition: pager.phtml:43
$resource
Definition: bulk.php:12
$type
Definition: item.phtml:13
$fileName
Definition: translate.phtml:15
_getSitemapIndexRow($sitemapFilename, $lastmod=null)
Definition: Sitemap.php:578
__construct(\Magento\Framework\Model\Context $context, \Magento\Framework\Registry $registry, \Magento\Framework\Escaper $escaper, \Magento\Sitemap\Helper\Data $sitemapData, \Magento\Framework\Filesystem $filesystem, \Magento\Sitemap\Model\ResourceModel\Catalog\CategoryFactory $categoryFactory, \Magento\Sitemap\Model\ResourceModel\Catalog\ProductFactory $productFactory, \Magento\Sitemap\Model\ResourceModel\Cms\PageFactory $cmsFactory, \Magento\Framework\Stdlib\DateTime\DateTime $modelDate, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Framework\App\RequestInterface $request, \Magento\Framework\Stdlib\DateTime $dateTime, \Magento\Framework\Model\ResourceModel\AbstractResource $resource=null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection=null, array $data=[], \Magento\Config\Model\Config\Reader\Source\Deployed\DocumentRoot $documentRoot=null, ItemProviderInterface $itemProvider=null, SitemapConfigReaderInterface $configReader=null, \Magento\Sitemap\Model\SitemapItemInterfaceFactory $sitemapItemFactory=null)
Definition: Sitemap.php:216
addSitemapItem(DataObject $sitemapItem)
Definition: Sitemap.php:289
_addSitemapToRobotsTxt($sitemapFileName)
Definition: Sitemap.php:785
getSitemapUrl($sitemapPath, $sitemapFileName)
Definition: Sitemap.php:759
_finalizeSitemap($type=self::TYPE_URL)
Definition: Sitemap.php:629
$filesystem
$i
Definition: gallery.phtml:31
$index
Definition: list.phtml:44
_getSitemapRow($url, $lastmod=null, $changefreq=null, $priority=null, $images=null)
Definition: Sitemap.php:537
$items