РАЗРАБОТКА НА БИТРИКС

Создание файла автогенерации sitemap.xml для выполнения на cron

Подписывайтесь на канал для bitrix-разработчиков в Telegram!
В данном кейсе будет рассмотрено создание скрипта для обновления sitemap.xml по крону.

Описание задачи

XML-карта сайта — файл с информацией для поисковых систем о страницах, которые необходимо проиндексировать. Другими словами, карта сайта — список всех страниц в формате XML, доступных для сканирования поисковым роботом.
Актуальность данного файла достаточно важна для SEO, но Bitrix не предполагает его поддержание в актуальном состоянии. Кроме того, зачастую необходимо добавить новые ссылки для генерации или применить дополнительные скрипты (например модули SEO-фильтра или SEO-поиска).

Исходные данные

  1. Настройки карты сайта (Маркетинг->Поисковая оптимизация->Настройка sitemap.xml)
  2. Настройки карты сайта модуля умного фильтра (Сотбит->SEO умного фильтра->Генерация карты сайта)
  3. Настройки карты сайта модуля умного поиска (Сотбит->SEO умного поиска->Генерация карты сайта)
  4. Страницы линий, ЧПУ которых формируются на основании свойства
  5. Требования к карте сайта от SEO-специалиста

Решение

Генерация основного файла
Метод основывается на стандартном функционале генерации карты сайта, пользуется настройками из админки:
Маркетинг->Поисковая оптимизация->Настройка sitemap.xml
Генерация файла при запуске из административной панели происходит при помощи файла /bitrix/modules/seo/admin/seo_sitemap_run.php , берем его за основу.
Можно взять уже готовый файл:
Примечание: В файле явно указан id карты сайта для генерации, можно сделать через передачу параметра с крон запросом.
Генерация карты сайта модуля умный поиск и умный фильтр
Делаем по тому же принципу.
Находим файлы генерации и копируем в конец изначального
/bitrix/modules/sotbit.seosearch/admin/sotbit.seosearch_sitemap_run.php
/bitrix/modules/sotbit.seometa/admin/sotbit.seometa_sitemap_run.php
Примечание: проверить при запуске, будут дубли подключения классов.
Добавление своего файла генерации
Добавляем еще один шаг в генерацию

$arValueSteps = array(
         'init'         => 0,
         'init1'        => 1,
         'files'        => 40,
         'iblock_index' => 50,
         'iblock'       => 60,
         'forum_index'  => 70,
         'forum'        => 80,
         'linii'        => 90,
         'index'        => 100,
    );
Прописываем генерацию

else if($v < $arValueSteps['linii']) {
   $NS['time_start'] = microtime(true);
   if(Main\Loader::includeModule('iblock')) {
       $arLiniiList = array();
       $dbLiniiResult = CIBlockElement::GetList(
               array('ID' => 'ASC'),
               array(
                'IBLOCK_ID'  => \Oneway\Constants::IBLOCK_CATALOG_LANDING,
                'ACTIVE'     => 'Y',
               ),
               false,
               false,
               array('ID', 'TIMESTAMP_X', 'DETAIL_PAGE_URL','CODE','PROPERTY_SEGMENT')
               );                 
                            
       $fileName= str_replace('files', 'linii', $arSitemap['SETTINGS']['FILENAME_FILES']);
       $sitemapFile = new SitemapRuntime($PID, $fileName, $arSitemapSettings);        
       while($arElement = $dbLiniiResult->fetch()){
           $arElement['CODE'] = $arElement['PROPERTY_SEGMENT_VALUE'];
           $elementLastmod = MakeTimeStamp($arElement['TIMESTAMP_X']);
           $url = \CIBlock::ReplaceDetailUrl($arElement['DETAIL_PAGE_URL'], $arElement, false, "E");
           $sitemapFile->addIBlockEntry($url, $elementLastmod);
       }
       if($sitemapFile->isNotEmpty()) {
			if($sitemapFile->isCurrentPartNotEmpty()) { $sitemapFile->finish();  }
			else {$sitemapFile->delete(); }

			if(!is_array($NS['XML_FILES']))
			$NS['XML_FILES'] = array();
			$NS['XML_FILES'] = array_merge($NS['XML_FILES'], $sitemapFile->getNameList());
		}
	}
}
Рекомендации по созданию от SEO-специалиста (введение priority, changefreq и деление на более мелкие файлы)
1. Для главной страницы, разделов/подразделов и товаров необходимо добавить тег приоритета <priority>
  • для главной страницы значение - 1
  • для разделов/подразделов - 0,8
  • для страниц товаров - 0,7
2. Для главной страницы, разделов/подразделов и товаров необходимо добавить тег частоты обхода <changefreq> - значение weekly
3. Если количество товаров больше 1000, необходимо вынести их в отдельную карту сайта
Пример:

<?xml version="1.0" encoding="UTF-8"?>
http://www.sitemaps.org/schemas/sitemap/0.9">http://www.sitemaps.org/schemas/sitemap/0.9](http://www.sitemaps.org/schemas/sitemap/0.9)">
<url>
[http://www.example.com/page1.html](http://www.example.com/page1.html%3C/loc%3E)
<lastmod>2005-01-01</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
...
</urlset>

Реализация

В начале файла прописали массив правил приоритета

$priority=array(
                'MAIN'=>1,
                CATALOG_IB=>array('E'=>'0.7','S'=>'0.8')
            );
где CATALOG_IB - ID инфоблока к элементам и разделам которого необходимо установить приоритет
Переопределяем класс и подключаем необходимые новые

use Bitrix\Seo\SitemapRuntime as BaseSitemapRuntime;
use Bitrix\Main\Text\Converter;
use Bitrix\Main\IO\File;
Создаем дочерний класс, чтобы переопределить функции

class SitemapRuntime extends BaseSitemapRuntime
{
	/*новая переменная для записи в карту*/
    const ENTRY_TPL_PRIORITY = '<url><loc>%s</loc><lastmod>%s</lastmod><changefreq>weekly</changefreq><priority>%s</priority></url>';
	/*новая переменная ограничения размера файла*/
    const MAX_SIZE = 200000;
	/*функция определения размера*/
    protected function isSplitNeeded()
    {
        return $this->isExists() && $this->getSize() >= self::MAX_SIZE;
    }
   
	/*функция записи в файл, если страница с priority то записываем правила по новому шаблону*/
    public function addEntry($entry)
    {
        if ($this->isSplitNeeded())
        {
            $this->split();
            $this->addEntry($entry);
        }
        elseif(empty($entry['PRIORITY']))
        {
            if (!$this->partChanged)
            {
                $this->addHeader();
            }

            $this->putContents(
                sprintf(
                    self::ENTRY_TPL,
                    Converter::getXmlConverter()->encode($entry['XML_LOC']),
                    Converter::getXmlConverter()->encode($entry['XML_LASTMOD'])
                ), self::APPEND
            );
        }
        else
        {
            if (!$this->partChanged)
            {
                $this->addHeader();
            }
          
            $this->putContents(
                sprintf(
                    self::ENTRY_TPL_PRIORITY,
                    Converter::getXmlConverter()->encode($entry['XML_LOC']),
                    Converter::getXmlConverter()->encode($entry['XML_LASTMOD']),
                    Converter::getXmlConverter()->encode($entry['PRIORITY'])
                ), self::APPEND
            );
        }
    }
	/*добавление ссылки инфоблока в карту, передача информации о приоритете*/
    public function addIBlockEntry($url, $modifiedDate,$priority='')
    {
        $this->addEntry(array(
            'XML_LOC' => $this->settings['PROTOCOL'].'://'.\CBXPunycode::toASCII($this->settings['DOMAIN'], $e = null).$url,
            'XML_LASTMOD' => date('c', $modifiedDate - \CTimeZone::getOffset()),
            'PRIORITY'=>$priority,
        ));
    }
	/*добавление ссылки файла в карту, передача информации о приоритете*/
    public function addFileEntry(File $f,$priority='')
    {
        if($f->isExists() && !$f->isSystem())
        {
            $this->addEntry(array(
                'XML_LOC' => $this->settings['PROTOCOL'].'://'.\CBXPunycode::toASCII($this->settings['DOMAIN'], $e = null).$this->getFileUrl($f),
                'XML_LASTMOD' => date('c', $f->getModificationTime()),
                'PRIORITY'=>$priority,
            ));
        }
    }
}
Дополняем function seoSitemapGetFilesData для передачи данных о главной странице

global $priority; //передаем правила заданные в начале
Вместо

if(preg_match($arSitemap['SETTINGS']['FILE_MASK_REGEXP'], $dir['FILE'])) {
	$f = new IO\File($dir['DATA']['PATH'], $arSitemap['SITE_ID']);
	$sitemapFile->addFileEntry($f);
	$NS['files_count']++;
}
Прописываем

if(preg_match($arSitemap['SETTINGS']['FILE_MASK_REGEXP'], $dir['FILE'])) {
	$f = new IO\File($dir['DATA']['PATH'], $arSitemap['SITE_ID']);
	$priorityFile='';

	if($dirKey==='/index.php' && !empty($priority['MAIN'])){
		$priorityFile=$priority['MAIN'];

	}
	$sitemapFile->addFileEntry($f,$priorityFile);
	$NS['files_count']++;
}
Добавляем в генерацию ссылок инфоблока.
Ищем добавление в карту категорий и элементов и заменяем

/*элементы*/
$sitemapFile->addIBlockEntry($url, $elementLastmod);
/*категории*/
$sitemapFile->addIBlockEntry($url, $sectionLastmod);

/*для приоритета элементов*/
$priorityElement=($priority[$iblockId]['E']?$priority[$iblockId]['E']:'');
$sitemapFile->addIBlockEntry($url, $elementLastmod,$priorityElement);

/*для приоритета категорий*/
$priorityElement=($priority[$iblockId]['S']?$priority[$iblockId]['S']:'');
$sitemapFile->addIBlockEntry($url, $sectionLastmod,$priorityElement);
Настраиваем крон файл.

Результат

  1. Настройки генерации сохраняются в админке сайта.
  2. Объединили 3 генерации в один файл, если генерировать через административную часть, то нужно генерировать в 3-х разных местах.
  3. Настроили дополнительный файл генерации.
  4. Карта сайта и файл генерируются автоматически каждый день, что уменьшает вероятность попадания в индекс деактивированных или удаленных страниц.

Выводы

Настроено автоматическое обновление карты сайта с сохранением настроек в админке и возможностью включения собственного функционала.
Преимуществом формирования карты сайты подобным образом является то, что SEO-специалист по-прежнему может настраивать параметры формирования карты в административной панели, не нужно обновлять файл вручную и помнить про его актуализацию. Также есть возможность добавления собственного кода в результирующий файл, если сайт имеет нестандартные страницы.
Подписывайтесь на канал для bitrix-разработчиков в Telegram!

Рекомендованные статьи