src/WebBundle/Controller/SeoController.php line 125

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace WebBundle\Controller;
  4. use DOMDocument;
  5. use DOMElement;
  6. use FlexApp\Service\AdsFilters\FilterAdsFilesDataService;
  7. use RuntimeException;
  8. use Symfony\Component\HttpFoundation\Request;
  9. use Symfony\Component\HttpFoundation\Response;
  10. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  11. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  12. use WebBundle\Entity\Vacancy;
  13. use WebBundle\Helper\App;
  14. use WebBundle\Helper\LocaleHelper;
  15. use WebBundle\Helper\StrHelper;
  16. use WebBundle\Helper\VacancyHelper;
  17. use WebBundle\Repository\ListCountryRepository;
  18. use WebBundle\Service\SeoService;
  19. use WebBundle\Service\SitemapGenerator;
  20. /**
  21.  * Контроллер для SEO-задач (sitemap, robots, html-sitemap и т.д.).
  22.  */
  23. class SeoController extends ExtendedController
  24. {
  25.     /** @required */
  26.     public SeoService $seoService;
  27.     /** @required */
  28.     public SitemapGenerator $sitemapGenerator;
  29.     private FilterAdsFilesDataService $filterAdsFilesDataService;
  30.     public function __construct(FilterAdsFilesDataService $filterAdsFilesDataService)
  31.     {
  32.         parent::__construct();
  33.         $this->filterAdsFilesDataService $filterAdsFilesDataService;
  34.     }
  35.     /**
  36.      * Главная страница для всех sitemap-файлов (sitemapindex).
  37.      * Генерирует ссылку на каждый sitemap для доступных локалей (один маршрут на локаль).
  38.      *
  39.      * @return Response XML-ответ с индексом sitemap-файлов.
  40.      */
  41.     public function indexAction(): Response
  42.     {
  43.         $content $this->filterAdsFilesDataService->getFileContents('var/sitemap/sitemap.xml');
  44.         return new Response($contentResponse::HTTP_OK, [
  45.             'Content-Type' => 'application/xml; charset=UTF-8',
  46.         ]);
  47.     }
  48.     /**
  49.      * Возвращает XML-содержимое sitemap для указанной локали.
  50.      *
  51.      * @param string $_locale Локаль, например, 'da-dk'.
  52.      *
  53.      * @return Response XML-ответ с содержимым sitemap.
  54.      */
  55.     public function siteMapXmlTEAction(string $_locale): Response
  56.     {
  57.         try {
  58.             $filePath $this->sitemapGenerator->generateSitemapSinglePath($_locale);
  59.             $content $this->filterAdsFilesDataService->getFileContents($filePath);
  60.         } catch (RuntimeException $e) {
  61.             throw new NotFoundHttpException(sprintf(
  62.                 'Sitemap файл не найден для локали "%s". Детали: %s',
  63.                 $_locale,
  64.                 $e->getMessage()
  65.             ));
  66.         }
  67.         return new Response($contentResponse::HTTP_OK, [
  68.             'Content-Type' => 'application/xml; charset=UTF-8',
  69.         ]);
  70.     }
  71.     /**
  72.      * Обрабатывает запросы к частичным Sitemap-файлам.
  73.      *
  74.      * Этот метод возвращает содержимое определённого файла Sitemap в зависимости от локали и номера части.
  75.      *
  76.      * @param string $_locale Локаль, например, 'da-dk'
  77.      * @param int $part Номер части Sitemap, например, 1
  78.      *
  79.      * @return Response Ответ с содержимым Sitemap-файла
  80.      */
  81.     public function sitemapPartAction(string $_localeint $part): Response
  82.     {
  83.         if ($part 1) {
  84.             throw new NotFoundHttpException('Номер части Sitemap должен быть положительным целым числом.');
  85.         }
  86.         try {
  87.             $filePath $this->sitemapGenerator->generateSitemapPath($_locale$part);
  88.             $content $this->filterAdsFilesDataService->getFileContents($filePath);
  89.         } catch (RuntimeException $e) {
  90.             throw new NotFoundHttpException(sprintf(
  91.                 'Файл Sitemap не найден: sitemap.%s_part%d.xml. Детали: %s',
  92.                 $_locale,
  93.                 $part,
  94.                 $e->getMessage()
  95.             ));
  96.         }
  97.         return new Response($contentResponse::HTTP_OK, [
  98.             'Content-Type' => 'application/xml; charset=UTF-8',
  99.         ]);
  100.     }
  101.     /**
  102.      * Генерирует файл robots.txt.
  103.      *
  104.      * @param Request $request HTTP-запрос.
  105.      *
  106.      * @return Response Ответ с содержимым robots.txt.
  107.      */
  108.     public function robotsTxtTEAction(Request $request): Response
  109.     {
  110.         if (App::getContainer()->getParameter('site') != 'tile.expert' && $request->get('debug') == null) {
  111.             $response $this->render(
  112.                 '@Web/Seo/robots.txt.twig',
  113.                 [
  114.                     'disallow' => false,
  115.                 ]
  116.             );
  117.         } else {
  118.             $list = [
  119.                 'disallow' => $this->getDisallowUrlPatterns(),
  120.             ];
  121.             foreach (LocaleHelper::getListCode() as $lc) {
  122.                 foreach (App::getCountryList() as $code => $country) {
  123.                     if ($code != 'en' && !in_array($lc$country['localesArr'])) {
  124.                         $list['full'][] = '/' $lc '-' $code '$';
  125.                         $list['full'][] = '/' $lc '-' $code '/';
  126.                     }
  127.                 }
  128.                 $list['full'][] = '/' $lc '-en';
  129.                 $list['full'][] = '/' $lc '_es-cn';
  130.                 $list['full'][] = '/' $lc '-es-cn';
  131.             }
  132.             $response $this->render(
  133.                 '@Web/Seo/robots.txt.twig',
  134.                 [
  135.                     'host' => 'tile.expert',
  136.                     'list' => $list,
  137.                     'locales' => LocaleHelper::getListCode(),
  138.                     'countries' => array_keys(App::getCountryList()),
  139.                     'allows' => $this->getAllowsUrlPatterns(),
  140.                 ]
  141.             );
  142.         }
  143.         $response->headers->set('Content-Type''text/plain');
  144.         return $response;
  145.     }
  146.     /**
  147.      * Генерирует HTML sitemap-индекс.
  148.      *
  149.      * @return Response Ответ с содержимым HTML sitemap.
  150.      */
  151.     public function sitemapHtmlIndex(): Response
  152.     {
  153.         $dom = new DOMDocument();
  154.         $head $dom->createElement('head');
  155.         $body $dom->createElement('body');
  156.         $dom->appendChild($head);
  157.         $dom->appendChild($body);
  158.         foreach (LocaleHelper::getListCodeAvailable() as $lc) {
  159.             foreach (App::getCountryList() as $code => $country) {
  160.                 if (
  161.                     empty($code)
  162.                     || ($code === 'en' && $lc === 'en')
  163.                     || ($code !== 'en' && !in_array($lc$country['localesArr']))
  164.                     || (!in_array($codeSitemapGenerator::COUNTRIES_EN) && $lc === 'en')
  165.                 ) {
  166.                     continue;
  167.                 }
  168.                 $code strtolower($code);
  169.                 if (in_array($codeSitemapGenerator::ONLY_NATIVE_LANG_AVAILABLE)) {
  170.                     $lang $code;
  171.                 } else {
  172.                     $lang $lc;
  173.                 }
  174.                 $l LocaleHelper::buildLocaleCountry($lc$lang$code);
  175.                 if ($l === 'en-fi') {
  176.                     continue;
  177.                 }
  178.                 $loc $dom->createElement('a'"sitemap.$l.html");
  179.                 $loc->setAttribute('href'$this->link('seo_sitemap_html', ['_locale' => $l]));
  180.                 $div $dom->createElement('div');
  181.                 $div->appendChild($loc);
  182.                 $body->appendChild($div);
  183.             }
  184.         }
  185.         $response = new Response($dom->saveHTML());
  186.         $response->headers->set('Content-Type''text/html');
  187.         return $response;
  188.     }
  189.     /**
  190.      * Генерирует HTML sitemap для указанной локали.
  191.      *
  192.      * @param string $_locale Локаль, например, 'da-dk'.
  193.      *
  194.      * @return Response Ответ с содержимым HTML sitemap.
  195.      */
  196.     public function sitemapHtmlTEAction(string $_locale): Response
  197.     {
  198.         $oTranslator App::getTranslator();
  199.         $locale explode('-'$_locale);
  200.         if (!empty($locale[1]) && in_array($locale[1], SitemapGenerator::ONLY_NATIVE_LANG_AVAILABLE)) {
  201.             if ($locale[0] !== $locale[1]) {
  202.                 $l $locale[1] . '-' $locale[1];
  203.                 return $this->redirect($this->link('seo_sitemap_html', ['_locale' => $l]));
  204.             }
  205.         }
  206.         $dom = new DOMDocument();
  207.         $head $dom->createElement('head');
  208.         $body $dom->createElement('body');
  209.         $dom->appendChild($head);
  210.         $dom->appendChild($body);
  211.         if (
  212.             !in_array($locale[0], App::getCountryList()[App::getCurCountry()]['localesArr']) ||
  213.             (!empty($locale[1]) && $locale[0] == 'en' && !in_array($locale[1], SitemapGenerator::COUNTRIES_EN))
  214.         ) {
  215.             throw $this->createNotFoundException('Not found sitemap');
  216.         }
  217.         // главная
  218.         $body $this->siteMapHtmlElem(
  219.             $dom,
  220.             $body,
  221.             $this->link('app_home', ['_locale' => $_locale]),
  222.             $oTranslator->trans('home_page')
  223.         );
  224.         // публикации
  225.         $blogs $this->seoService->getBlogs($locale[0], $locale[1] ?? null);
  226.         foreach ($blogs as $blog) {
  227.             $body $this->siteMapHtmlElem(
  228.                 $dom,
  229.                 $body,
  230.                 $this->link('app_publication_single', ['id' => $blog['url'], '_locale' => $_locale]),
  231.                 $blog['title']
  232.             );
  233.         }
  234.         // коллекции
  235.         $collections $this->seoService->getCollections();
  236.         foreach ($collections as $collection) {
  237.             if ($collection['f_url'] == 'testing-factory') {
  238.                 continue;
  239.             }
  240.             $body $this->siteMapHtmlElem(
  241.                 $dom,
  242.                 $body,
  243.                 $this->link(
  244.                     'app_collection',
  245.                     [
  246.                         'collectionUrl' => $collection['c_url'],
  247.                         'factoryUrl' => $collection['f_id'] == 12
  248.                             StrHelper::toLower($collection['f_url'])
  249.                             : $collection['f_url'],
  250.                         '_locale' => $_locale,
  251.                     ]
  252.                 ),
  253.                 $collection['c_name']
  254.             );
  255.         }
  256.         // фильтры
  257.         $filters $this->seoService->getFilters($locale[0]);
  258.         foreach ($filters as $filter) {
  259.             if (
  260.                 in_array($filter['url'], [
  261.                 'testing-factory',
  262.                 'cataloge',
  263.                 'kakelkatalog',
  264.                 'katalog',
  265.                 'catalogo',
  266.                 ])
  267.             ) {
  268.                 continue;
  269.             }
  270.             $body $this->siteMapHtmlElem(
  271.                 $dom,
  272.                 $body,
  273.                 $this->link('app_catalog', ['key' => $filter['url'], '_locale' => $_locale]),
  274.                 $oTranslator->trans($filter['leftMenu'])
  275.             );
  276.         }
  277.         // статика
  278.         $statics $this->seoService->getStatics($locale[0]);
  279.         foreach ($statics as $static) {
  280.             if ($static['url'] === '_test') {
  281.                 continue;
  282.             }
  283.             $body $this->siteMapHtmlElem(
  284.                 $dom,
  285.                 $body,
  286.                 $this->link('app_page', ['url' => $static['url'], '_locale' => $_locale]),
  287.                 $static['title'][$locale[0]]
  288.             );
  289.         }
  290.         $response = new Response($dom->saveHTML());
  291.         $response->headers->set('Content-Type''text/html');
  292.         return $response;
  293.     }
  294.     /**
  295.      * Вспомогательный метод для HTML sitemap.
  296.      */
  297.     protected function siteMapHtmlElem(
  298.         DOMDocument $siteMap,
  299.         DOMElement $urlSet,
  300.         string $link,
  301.         string $name null
  302.     ): DOMElement {
  303.         $div $siteMap->createElement('div');
  304.         $a $siteMap->createElement('a'$name htmlspecialchars($name) : $link);
  305.         $a->setAttribute('href'$link);
  306.         $div->appendChild($a);
  307.         $urlSet->appendChild($div);
  308.         return $urlSet;
  309.     }
  310.     /**
  311.      * Генерирует индекс sitemap для вакансий.
  312.      *
  313.      * @return Response XML-ответ с индексом sitemap-файлов вакансий.
  314.      */
  315.     public function siteMapXmlJobIndexAction(): Response
  316.     {
  317.         $siteMap = new DOMDocument('1.0''UTF-8');
  318.         $siteMap->formatOutput true;
  319.         $sitemapIndex $siteMap->createElementNS(
  320.             "http://www.sitemaps.org/schemas/sitemap/0.9",
  321.             'sitemapindex'
  322.         );
  323.         $sitemapIndex->setAttribute('xmlns''http://www.sitemaps.org/schemas/sitemap/0.9');
  324.         $locales VacancyHelper::getAviableLocales();
  325.         foreach ($locales as $lc) {
  326.             foreach (App::getCountryList() as $code => $country) {
  327.                 if (
  328.                     empty($code) ||
  329.                     ($code == 'en' && $lc == 'en') ||
  330.                     ($code != 'en' && !in_array($lc$country['localesArr'])) ||
  331.                     (!in_array($codeSitemapGenerator::COUNTRIES_EN) && $lc == 'en')
  332.                 ) {
  333.                     continue;
  334.                 }
  335.                 $code strtolower($code);
  336.                 if (in_array($codeSitemapGenerator::ONLY_NATIVE_LANG_AVAILABLE)) {
  337.                     $lang $code;
  338.                 } else {
  339.                     $lang $lc;
  340.                 }
  341.                 $l LocaleHelper::buildLocaleCountry($lc$lang$code);
  342.                 $loc $siteMap->createElement('loc'$this->link('app_jobs_seo_sitemap', ['_locale' => $l]));
  343.                 $lastmod $siteMap->createElement('lastmod'date('c'));
  344.                 $sitemap $siteMap->createElement('sitemap');
  345.                 $sitemap->appendChild($loc);
  346.                 $sitemap->appendChild($lastmod);
  347.                 $sitemapIndex->appendChild($sitemap);
  348.             }
  349.         }
  350.         $siteMap->appendChild($sitemapIndex);
  351.         $response = new Response($siteMap->saveXML());
  352.         $response->headers->set('Content-Type''application/xml');
  353.         return $response;
  354.     }
  355.     /**
  356.      * Генерирует XML sitemap для вакансии указанной локали.
  357.      *
  358.      * @param string $_locale Локаль, например, 'da-dk'.
  359.      *
  360.      * @return Response XML-ответ с содержимым sitemap вакансий.
  361.      */
  362.     public function siteMapXmlJobAction(string $_locale): Response
  363.     {
  364.         $locale explode('-'$_locale);
  365.         if (!empty($locale[1]) && in_array($locale[1], SitemapGenerator::ONLY_NATIVE_LANG_AVAILABLE)) {
  366.             if ($locale[0] != $locale[1]) {
  367.                 $l $locale[1] . '-' $locale[1];
  368.                 return $this->redirect(
  369.                     $this->generateUrl('seo_sitemap', ['_locale' => $l]),
  370.                     UrlGeneratorInterface::ABSOLUTE_URL
  371.                 );
  372.             }
  373.         }
  374.         $siteMap = new DOMDocument('1.0''UTF-8');
  375.         $siteMap->formatOutput true;
  376.         $urlSet $siteMap->createElementNS("http://www.sitemaps.org/schemas/sitemap/0.9"'urlset');
  377.         $urlSet->setAttribute('xmlns:xsi''http://www.w3.org/2001/XMLSchema-instance');
  378.         $urlSet->setAttribute(
  379.             'xsi:schemaLocation',
  380.             'http://www.sitemaps.org/schemas/sitemap/0.9 '
  381.             'http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd'
  382.         );
  383.         $code StrHelper::toUpper($locale[1] ?? $locale[0]);
  384.         if (
  385.             !in_array($locale[0], App::getCountryList()[$locale[1] ?? App::getCurCountry()]['localesArr'] ?? [])
  386.             || (!empty($locale[1]) && $locale[0] === 'en' && !in_array($locale[1], SitemapGenerator::COUNTRIES_EN))
  387.         ) {
  388.             throw $this->createNotFoundException('Not found site map');
  389.         }
  390.         /** @var ListCountryRepository $countryRepo */
  391.         $countryRepo $this->getDoctrine()->getRepository('WebBundle:ListCountry');
  392.         $country $countryRepo->getCountry($code);
  393.         $priority = ($country && explode('|'$country->getLocale())[0] === $locale[0])
  394.             ? '1.0'
  395.             '0.5';
  396.         $localeUp StrHelper::ucFirst($locale[0]);
  397.         $hide 'hide' $localeUp;
  398.         // Показать только активные вакансии
  399.         $items $this->getDoctrine()
  400.             ->getRepository(Vacancy::class)
  401.             ->findBy(
  402.                 [
  403.                     'status' => true,
  404.                     $hide => false,
  405.                 ],
  406.                 [
  407.                     'subject' => 'asc',
  408.                 ]
  409.             );
  410.         /** @var Vacancy $item */
  411.         foreach ($items as $item) {
  412.             $urlSet $this->siteMapXmlElem(
  413.                 $siteMap,
  414.                 $urlSet,
  415.                 $this->link('app_job_show', ['_locale' => $_locale'slug' => $item->getSubjectTranslit()]),
  416.                 $priority
  417.             );
  418.         }
  419.         // Неактивные вакансии с меньшим приоритетом
  420.         $priority '0.5';
  421.         $items $this->getDoctrine()
  422.             ->getRepository(Vacancy::class)
  423.             ->findBy(
  424.                 [
  425.                     'status' => false,
  426.                     $hide => false,
  427.                 ],
  428.                 [
  429.                     'subject' => 'asc',
  430.                 ]
  431.             );
  432.         foreach ($items as $item) {
  433.             $urlSet $this->siteMapXmlElem(
  434.                 $siteMap,
  435.                 $urlSet,
  436.                 $this->link('app_job_show', ['_locale' => $_locale'slug' => $item->getSubjectTranslit()]),
  437.                 $priority
  438.             );
  439.         }
  440.         $siteMap->appendChild($urlSet);
  441.         $response = new Response($siteMap->saveXML());
  442.         $response->headers->set('Content-Type''application/xml');
  443.         return $response;
  444.     }
  445.     /**
  446.      * Генерирует файл robots.txt для вакансий.
  447.      *
  448.      * @param Request $request HTTP-запрос.
  449.      *
  450.      * @return Response Ответ с содержимым robots.txt для вакансий.
  451.      */
  452.     public function robotsTxtJobsAction(Request $request): Response
  453.     {
  454.         if ((bool) $_SERVER['APP_DEBUG']) {
  455.             $response $this->render(
  456.                 '@Web/Seo/robots.txt.twig',
  457.                 [
  458.                     'host' => App::getContainer()->getParameter('jobs_subdomain') . '.' App::getContainer()->getParameter('site'),
  459.                     'list' => null,
  460.                     'disallow' => true,
  461.                 ]
  462.             );
  463.         } else {
  464.             $list = [
  465.                 'full' => [
  466.                     '/json/',
  467.                     '/ru/full/questionnaire/',
  468.                     '/ru/cv/modal',
  469.                     '/en-us/full/questionnaire/',
  470.                     '/en-us/cv/modal',
  471.                     '/interview/',
  472.                     '/tmp/_files/cv/',
  473.                     '/userdirs/vacancy/',
  474.                     '/ru/comments/create',
  475.                     '/ru/comment-form/',
  476.                     '/ru/comment-block/',
  477.                     '/en-us/comments/create',
  478.                     '/en-us/comment-form/',
  479.                     '/en-us/comment-block/',
  480.                 ],
  481.             ];
  482.             $response $this->render(
  483.                 '@Web/Seo/robots.txt.twig',
  484.                 [
  485.                     'host' => App::getContainer()->getParameter('jobs_subdomain') . '.' App::getContainer()->getParameter('site'),
  486.                     'list' => $list,
  487.                     'locales' => LocaleHelper::getListCode(),
  488.                     'countries' => array_keys(App::getCountryList()),
  489.                     'allows' => [],
  490.                 ]
  491.             );
  492.         }
  493.         $response->headers->set('Content-Type''text/plain');
  494.         return $response;
  495.     }
  496.     /**
  497.      * Вспомогательная логика для робота (Disallow и т.д.).
  498.      *
  499.      * @return array Список шаблонов URL для Disallow в robots.txt.
  500.      */
  501.     private function getDisallowUrlPatterns(): array
  502.     {
  503.         return [
  504.             '*/z_*',
  505.             '*/rasprodazha*',
  506.             '*/sale*',
  507.             '*/svendita*',
  508.             '*/docs/improving-site*',
  509.             '*/docs/payment-usca*',
  510.             '*/docs/payment-eact*',
  511.             '*/docs/payment-eu*',
  512.             '*/docs/payment-new*',
  513.             '*/catalogue/coll/*',
  514.             '*/catalogue/release-now*',
  515.             '*/catalogue/release-before-2017*',
  516.             '*/catalogue/release-2017*',
  517.             '*/catalogue/release-2018*',
  518.             '*/catalogue/release-2019*',
  519.             '*/catalogue/cevisama-2017*',
  520.             '*/catalogue/cevisama-2018*',
  521.             '*/catalogue/cevisama-2019*',
  522.             '*/catalogue/cevisama-2020*',
  523.             '*/catalogue/cersaie-2017*',
  524.             '*/catalogue/cersaie-2018*',
  525.             '*/catalogue/cersaie-2019*',
  526.             '*/catalogue/cersaie-2020*',
  527.             '*/catalogue/cersaie-2021*',
  528.             '*/catalogue/cersaie-2022*',
  529.             '*/catalogue/no-exhibitions-2020*',
  530.             '*/catalogue/no-exhibitions-2021*',
  531.             '*/catalogue/coverings-2018*',
  532.             '*/catalogue/coverings-2019*',
  533.             '*/catalogue/cataloge*',
  534.             '/blog/20-',
  535.             '*/rebajas*',
  536.             '*/destockage*',
  537.             '*/ausverkauf*',
  538.             '*/wyprzedaz*',
  539.             '*/opruiming-tegels*',
  540.             '*/alennusmyynti*',
  541.             '*/best-price*',
  542.             '*/miglior-prezzo*',
  543.             '*/mejor-precio*',
  544.             '*/meilleur-prix*',
  545.             '*/besten-preis*',
  546.             '*/najlepsza-cena*',
  547.             '*/beste-prijs*',
  548.             '*/paras-hinta*',
  549.             '*/basta-pris*',
  550.             '*/likvidaciya*',
  551.             '*/clearance*',
  552.             '*/liquidazione*',
  553.             '*/liquidacion*',
  554.             '*/liquidation*',
  555.             '*/liquidierung*',
  556.             '*/likwidacja*',
  557.             '*/opruiming*',
  558.             '*/poistomyynti*',
  559.             '*/top-20-mesyaca*',
  560.             '*/top-20-month*',
  561.             '*/kuukauden-top-20-lista*',
  562.             '*/manadens-topp-20*',
  563.             '*/manedens-20-mest-populære*',
  564.             '*/top-20-do-mes*',
  565.             '*tube6*',
  566.         ];
  567.     }
  568.     /**
  569.      * Вспомогательная логика для робота (Allow и т.д.).
  570.      *
  571.      * @return array Список шаблонов URL для Allow в robots.txt.
  572.      */
  573.     private function getAllowsUrlPatterns(): array
  574.     {
  575.         return [];
  576.     }
  577.     /**
  578.      * Генерирует абсолютную ссылку, заменяя & на &amp; для корректного отображения в XML.
  579.      *
  580.      * @param string $route Имя маршрута.
  581.      * @param array $params Параметры маршрута.
  582.      *
  583.      * @return string Абсолютная ссылка с заменёнными амперсандами.
  584.      */
  585.     public function link(string $route$params = []): string
  586.     {
  587.         return str_replace(
  588.             '&',
  589.             '&amp;',
  590.             $this->generateUrl($route$paramsUrlGeneratorInterface::ABSOLUTE_URL)
  591.         );
  592.     }
  593.     /**
  594.      * Вспомогательный метод для формирования <url> в sitemap Jobs.
  595.      *
  596.      * @param DOMDocument $siteMap DOM-документ sitemap.
  597.      * @param DOMElement $urlSet Элемент <urlset> в документе.
  598.      * @param string $link Абсолютная ссылка.
  599.      * @param string $priority Приоритет URL (по умолчанию '0.5').
  600.      * @param string $changefreq Частота изменений URL (по умолчанию 'daily').
  601.      * @param string $date Дата последнего изменения (по умолчанию текущая дата).
  602.      *
  603.      * @return DOMElement Обновлённый элемент <urlset>.
  604.      */
  605.     protected function siteMapXmlElem(
  606.         DOMDocument $siteMap,
  607.         DOMElement $urlSet,
  608.         string $link,
  609.         string $priority '0.5',
  610.         string $changefreq 'daily',
  611.         string $date ''
  612.     ): DOMElement {
  613.         $url $siteMap->createElement('url');
  614.         $loc $siteMap->createElement('loc'$link);
  615.         $url->appendChild($loc);
  616.         if (empty($date)) {
  617.             $date date('c');
  618.         }
  619.         $lastmod $siteMap->createElement('lastmod'$date);
  620.         $freq $siteMap->createElement('changefreq'$changefreq);
  621.         $prio $siteMap->createElement('priority'$priority);
  622.         $url->appendChild($lastmod);
  623.         $url->appendChild($freq);
  624.         $url->appendChild($prio);
  625.         $urlSet->appendChild($url);
  626.         return $urlSet;
  627.     }
  628. }