From bb3f0d40e8d4f4f132e558d9b47b846b535759fb Mon Sep 17 00:00:00 2001 From: Mohammed Date: Fri, 24 May 2024 03:46:42 +0100 Subject: [PATCH] generate sitemap.xml per siteAccess && inject the multilingual and multinational site annotations tag. --- .../bundle/Controller/SitemapController.php | 321 ++++++++++++++---- .../bundle/Core/Helper/SiteMapHelper.php | 173 ++++++++++ .../SEOBundle/bundle/Core/MetaNameSchema.php | 6 + .../bundle/Core/Sitemap/QueryFactory.php | 22 +- .../DependencyInjection/Configuration.php | 6 +- .../NovaeZSEOExtension.php | 3 + .../Resources/config/default_settings.yml | 6 +- .../config/ibexa_locale_conversion.yml | 15 + .../bundle/Resources/config/services.yml | 8 +- components/SEOBundle/documentation/USAGE.md | 42 +++ composer.json | 3 +- 11 files changed, 528 insertions(+), 77 deletions(-) create mode 100644 components/SEOBundle/bundle/Core/Helper/SiteMapHelper.php create mode 100644 components/SEOBundle/bundle/Resources/config/ibexa_locale_conversion.yml diff --git a/components/SEOBundle/bundle/Controller/SitemapController.php b/components/SEOBundle/bundle/Controller/SitemapController.php index a27581e85..807dbbf04 100644 --- a/components/SEOBundle/bundle/Controller/SitemapController.php +++ b/components/SEOBundle/bundle/Controller/SitemapController.php @@ -16,16 +16,19 @@ use DOMDocument; use DOMElement; use Ibexa\Bundle\Core\Controller; +use Ibexa\Contracts\Core\Repository\Repository; use Ibexa\Contracts\Core\Repository\Values\Content\Location; -use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchHit; use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchResult; use Ibexa\Contracts\Core\Variation\VariationHandler; use Ibexa\Core\Helper\FieldHelper; use Ibexa\Core\MVC\Symfony\Routing\UrlAliasRouter; +use Novactive\Bundle\eZSEOBundle\Core\Helper\SiteMapHelper; use Novactive\Bundle\eZSEOBundle\Core\Sitemap\QueryFactory; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\RouterInterface; +use Throwable; class SitemapController extends Controller { @@ -35,6 +38,15 @@ class SitemapController extends Controller /** @var VariationHandler */ protected $imageVariationService; + /** @var RouterInterface */ + protected $router; + + /** @var SiteMapHelper */ + protected $siteMapHelper; + + /** @var Repository */ + private $repository; + /** * How many in a Sitemap. * @@ -42,10 +54,18 @@ class SitemapController extends Controller */ public const PACKET_MAX = 1000; - public function __construct(FieldHelper $fieldHelper, VariationHandler $imageVariationService) - { + public function __construct( + FieldHelper $fieldHelper, + VariationHandler $imageVariationService, + RouterInterface $router, + SiteMapHelper $siteMapHelper, + Repository $repository + ) { $this->fieldHelper = $fieldHelper; $this->imageVariationService = $imageVariationService; + $this->router = $router; + $this->siteMapHelper = $siteMapHelper; + $this->repository = $repository; } /** @@ -53,33 +73,41 @@ public function __construct(FieldHelper $fieldHelper, VariationHandler $imageVar */ public function indexAction(QueryFactory $queryFactory): Response { - $searchService = $this->getRepository()->getSearchService(); - $query = $queryFactory(); - $query->limit = 0; - $resultCount = $searchService->findLocations($query)->totalCount; - + $isMultisiteAccess = $this->getConfigResolver() + ->getParameter('multi_siteaccess_sitemap', 'nova_ezseo') ?? false; // Dom Doc $sitemap = new DOMDocument('1.0', 'UTF-8'); $sitemap->formatOutput = true; - - // create an index if we are greater than th PACKET_MAX - if ($resultCount > static::PACKET_MAX) { + // Create an index for multi site + if ($isMultisiteAccess) { $root = $sitemap->createElement('sitemapindex'); $root->setAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9'); $sitemap->appendChild($root); - $this->fillSitemapIndex($sitemap, $resultCount, $root); + $this->fillSitemapMultiSiteIndex($sitemap, $root, $queryFactory); } else { - // if we are less or equal than the PACKET_SIZE, redo the search with no limit and list directly the urlmap - $query->limit = $resultCount; - $results = $searchService->findLocations($query); - $root = $sitemap->createElement('urlset'); - $root->setAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9'); - $root->setAttribute('xmlns:image', 'http://www.google.com/schemas/sitemap-image/1.1'); - $this->fillSitemap($sitemap, $root, $results); - $sitemap->appendChild($root); - } + $searchService = $this->getRepository()->getSearchService(); + $query = $queryFactory(); + $query->limit = 0; + $resultCount = $searchService->findLocations($query)->totalCount; + // create an index if we are greater than th PACKET_MAX + if ($resultCount > static::PACKET_MAX) { + $root = $sitemap->createElement('sitemapindex'); + $root->setAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9'); + $sitemap->appendChild($root); + $this->fillSitemapIndex($sitemap, $resultCount, $root); + } else { + // if we are less or equal than the PACKET_SIZE, redo the search with no limit and list directly the urlmap + $query->limit = $resultCount; + $results = $searchService->findLocations($query); + $root = $sitemap->createElement('urlset'); + $root->setAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9'); + $root->setAttribute('xmlns:image', 'http://www.google.com/schemas/sitemap-image/1.1'); + $this->fillSitemap($sitemap, $root, $results); + $sitemap->appendChild($root); + } + } $response = new Response($sitemap->saveXML(), 200, ['Content-type' => 'text/xml']); $response->setSharedMaxAge(86400); @@ -93,19 +121,27 @@ public function indexAction(QueryFactory $queryFactory): Response */ public function pageAction(QueryFactory $queryFactory, int $page = 1): Response { + $isMultisiteAccess = $this->getConfigResolver() + ->getParameter('multi_siteaccess_sitemap', 'nova_ezseo') ?? false; + $sitemap = new DOMDocument('1.0', 'UTF-8'); $root = $sitemap->createElement('urlset'); $sitemap->formatOutput = true; $root->setAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9'); $root->setAttribute('xmlns:image', 'http://www.google.com/schemas/sitemap-image/1.1'); - $sitemap->appendChild($root); - $query = $queryFactory(); - $query->limit = static::PACKET_MAX; - $query->offset = static::PACKET_MAX * ($page - 1); - $searchService = $this->getRepository()->getSearchService(); - $results = $searchService->findLocations($query); - $this->fillSitemap($sitemap, $root, $results); + // Create an index for multi site + if ($isMultisiteAccess) { + $this->multisiteAccessPage($sitemap, $root, $queryFactory, $page); + } else { + $sitemap->appendChild($root); + $query = $queryFactory(); + $query->limit = static::PACKET_MAX; + $query->offset = static::PACKET_MAX * ($page - 1); + $searchService = $this->getRepository()->getSearchService(); + $results = $searchService->findLocations($query); + $this->fillSitemap($sitemap, $root, $results); + } $response = new Response($sitemap->saveXML(), 200, ['Content-type' => 'text/xml']); $response->setSharedMaxAge(86400); @@ -113,6 +149,41 @@ public function pageAction(QueryFactory $queryFactory, int $page = 1): Response return $response; } + public function multisiteAccessPage( + DOMDocument $sitemap, + DOMElement $root, + QueryFactory $queryFactory, + int $page = 1 + ): void { + $schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9" . + " http://www.w3.org/1999/xhtml http://www.w3.org/2002/08/xhtml/xhtml1-strict.xsd". + " http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1". + " http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd" . + " http://www.google.com/schemas/sitemap-video/1.1"; + $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $root->setAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml'); + $root->setAttribute('xmlns:video', 'http://www.google.com/schemas/sitemap-video/1.1'); + $root->setAttribute('xsi:schemaLocation', $schemaLocation); + $sitemap->appendChild($root); + //Get The site Access of selected Host and local + $currentSiteAccess = $this->siteMapHelper->getCurrentSiteAccess(); + if ($currentSiteAccess) { + $rootLocationId = $this->siteMapHelper->getCurrentSiteAccessRootLocationId(); + $mainLanguage = $this->siteMapHelper->getCurrentSiteAccessMainLanguage(); + $query = $queryFactory( + $rootLocationId, + [$mainLanguage], + false + ); + $query->limit = static::PACKET_MAX; + $query->offset = static::PACKET_MAX * ($page - 1); + $searchService = $this->getRepository()->getSearchService(); + + $results = $searchService->findLocations($query); + $this->fillMultiLanguagesSitemap($sitemap, $root, $results); + } + } + /** * Fill a sitemap. */ @@ -120,7 +191,6 @@ protected function fillSitemap(DOMDocument $sitemap, DOMElement $root, SearchRes { foreach ($results->searchHits as $searchHit) { /** - * @var SearchHit * @var Location $location */ $location = $searchHit->valueObject; @@ -131,9 +201,7 @@ protected function fillSitemap(DOMDocument $sitemap, DOMElement $root, SearchRes UrlGeneratorInterface::ABSOLUTE_URL ); } catch (\Exception $exception) { - if ($this->has('logger')) { - $this->get('logger')->error('NovaeZSEO: '.$exception->getMessage()); - } + $this->siteMapHelper->logException($exception); continue; } @@ -146,36 +214,8 @@ protected function fillSitemap(DOMDocument $sitemap, DOMElement $root, SearchRes $lastmod = $sitemap->createElement('lastmod', $modified); $urlElt = $sitemap->createElement('url'); - // Inject the image tags if config is enabl - - $displayImage = $this->getConfigResolver()->getParameter('display_images_in_sitemap', 'nova_ezseo'); - if (true === $displayImage) { - $content = $this->getRepository()->getContentService()->loadContentByContentInfo( - $location->contentInfo - ); - foreach ($content->getFields() as $field) { - $fieldTypeIdentifier = $content->getContentType()->getFieldDefinition( - $field->fieldDefIdentifier - )->fieldTypeIdentifier; - - if ('ezimage' !== $fieldTypeIdentifier) { - continue; - } - - if ($this->fieldHelper->isFieldEmpty($content, $field->fieldDefIdentifier)) { - continue; - } - $variation = $this->imageVariationService->getVariation( - $field, - $content->getVersionInfo(), - 'original' - ); - $imageContainer = $sitemap->createElement('image:image'); - $imageLoc = $sitemap->createElement('image:loc', $variation->uri); - $imageContainer->appendChild($imageLoc); - $urlElt->appendChild($imageContainer); - } - } + // Inject the image tags if config is enabled + $this->injectImageTag($location, $sitemap, $urlElt); $urlElt->appendChild($loc); $urlElt->appendChild($lastmod); @@ -199,9 +239,7 @@ protected function fillSitemapIndex(DOMDocument $sitemap, int $numberOfResults, UrlGeneratorInterface::ABSOLUTE_URL ); } catch (\Exception $exception) { - if ($this->has('logger')) { - $this->get('logger')->error('NovaeZSEO: '.$exception->getMessage()); - } + $this->siteMapHelper->logException($exception); continue; } @@ -214,4 +252,157 @@ protected function fillSitemapIndex(DOMDocument $sitemap, int $numberOfResults, $root->appendChild($sitemapElt); } } + protected function fillSitemapMultiSiteIndex( + DOMDocument $sitemap, + DOMElement $root, + QueryFactory $queryFactory + ): void { + $siteMapHelper = $this->siteMapHelper; + $this->repository->sudo(static function (Repository $repository) use ( + $sitemap, + $root, + $queryFactory, + $siteMapHelper + ) { + $siteAccesses = $siteMapHelper->getSiteAccessesLocationIdLanguages(); + foreach ($siteAccesses as $siteAccess => $rootLocationLanguages) { + $rootLocationId = $rootLocationLanguages['rootLocationId']; + $query = $queryFactory( + $rootLocationId, + [$rootLocationLanguages['mainLanguage']], + false + ); + $query->limit = 0; + $numberOfResults = $repository->getSearchService()->findLocations($query)->totalCount; + $numberOfPage = (int) ceil($numberOfResults / static::PACKET_MAX); + for ($sitemapNumber = 1; $sitemapNumber <= $numberOfPage; ++$sitemapNumber) { + $sitemapElt = $sitemap->createElement('sitemap'); + $locUrl = $siteMapHelper->generateRouteUrl( + '_novaseo_sitemap_page', + $siteAccess, + ['page' => $sitemapNumber] + ); + $loc = $sitemap->createElement('loc', $locUrl); + $date = new DateTime(); + $modificationDate = $date->format('c'); + $mod = $sitemap->createElement('lastmod', $modificationDate); + $sitemapElt->appendChild($loc); + $sitemapElt->appendChild($mod); + $root->appendChild($sitemapElt); + } + } + }); + } + + /** + * Fill a sitemap. + */ + protected function fillMultiLanguagesSitemap( + DOMDocument $sitemap, + DOMElement $root, + SearchResult $results + ): void { + $currentSiteAccess = $this->siteMapHelper->getCurrentSiteAccess(); + foreach ($results->searchHits as $searchHit) { + /** + * @var Location $location + */ + $location = $searchHit->valueObject; + + $mainLanguageUrl = $this->siteMapHelper->generateLocationUrl($location->id, $currentSiteAccess); + if (null === $mainLanguageUrl || 0 != strpos($mainLanguageUrl, 'view/content/')) { + continue; + } + + $modified = $location->contentInfo->modificationDate->format('c'); + $loc = $sitemap->createElement('loc', $mainLanguageUrl); + $lastmod = $sitemap->createElement('lastmod', $modified); + $urlElt = $sitemap->createElement('url'); + + $urlElt->appendChild($loc); + // Inject the image tags if config is enabled + $this->injectImageTag($location, $sitemap, $urlElt); + // Inject the alternate lang tags if config is enabled + $this->injectAlternateLangTag($location, $sitemap, $urlElt); + + $urlElt->appendChild($lastmod); + $root->appendChild($urlElt); + } + } + + public function injectAlternateLangTag($location, DOMDocument $sitemap, DOMElement $root): void + { + $isMultiLanguages = $this->getConfigResolver()->getParameter('multi_languages_sitemap', 'nova_ezseo'); + if ($isMultiLanguages) { + try { + $siteAccesses = $this->getConfigResolver()->getParameter('translation_siteaccesses'); + $languagesCodes = []; + $contentInfo = $location->contentInfo; + $contentLanguages = $this->repository->getContentService() + ->loadVersionInfo($contentInfo)->getLanguages(); + foreach ($contentLanguages as $language) { + $languagesCodes[] = $language->languageCode; + } + foreach ($siteAccesses as $siteAccess) { + $siteAccessMainLanguage = $this->siteMapHelper->getSiteAccessMainLanguage($siteAccess); + if (!in_array($this->siteMapHelper->getSiteAccessMainLanguage($siteAccess), $languagesCodes)) { + continue; + } + + $url = $this->siteMapHelper->generateLocationUrl($location->id, $siteAccess); + $hreflang = $this->siteMapHelper->getHrefLang($siteAccessMainLanguage); + if (null === $url || 0 != strpos($url, 'view/content/')) { + continue; + } + + $xhtml = $sitemap->createElement('xhtml:link'); + $xhtml->setAttribute('rel', 'alternate'); + $xhtml->setAttribute('hreflang', $hreflang); + $xhtml->setAttribute('href', $url); + $root->appendChild($xhtml); + } + } catch (Throwable $e) { + $this->siteMapHelper->logException($e); + } + } + } + + public function injectImageTag($location, DOMDocument $sitemap, DOMElement $root): void + { + $displayImage = $this->getConfigResolver()->getParameter('display_images_in_sitemap', 'nova_ezseo'); + + if (true === $displayImage) { + $content = $this->getRepository()->getContentService()->loadContentByContentInfo( + $location->contentInfo + ); + foreach ($content->getFields() as $field) { + $fieldTypeIdentifier = $content->getContentType()->getFieldDefinition( + $field->fieldDefIdentifier + )->fieldTypeIdentifier; + + if ('ezimage' !== $fieldTypeIdentifier && 'ezimageasset' !== $fieldTypeIdentifier) { + continue; + } + + if ($this->fieldHelper->isFieldEmpty($content, $field->fieldDefIdentifier)) { + continue; + } + try { + $variation = $this->imageVariationService->getVariation( + $field, + $content->getVersionInfo(), + 'original' + ); + + $imageContainer = $sitemap->createElement('image:image'); + $imageLoc = $sitemap->createElement('image:loc', $variation->uri); + $imageContainer->appendChild($imageLoc); + $root->appendChild($imageContainer); + } catch (Throwable $exception) { + $this->siteMapHelper->logException($exception); + continue; + } + } + } + } } diff --git a/components/SEOBundle/bundle/Core/Helper/SiteMapHelper.php b/components/SEOBundle/bundle/Core/Helper/SiteMapHelper.php new file mode 100644 index 000000000..0b5e887b8 --- /dev/null +++ b/components/SEOBundle/bundle/Core/Helper/SiteMapHelper.php @@ -0,0 +1,173 @@ +configResolver = $configResolver; + $this->siteAccessService = $siteAccessService; + $this->routeReferenceGenerator = $routeReferenceGenerator; + $this->router = $router; + $this->localeConverter = $localeConverter; + $this->logger = $logger ?? new NullLogger(); + } + + public function generateLocationUrl( + int $locationId, + string $siteAccess = null + ): ?string { + try { + $routeParams['locationId'] = $locationId; + $routeReference = $this->routeReferenceGenerator->generate( + UrlAliasRouter::URL_ALIAS_ROUTE_NAME, + $routeParams + ); + if ($siteAccess) { + $routeReference->set('siteaccess', $siteAccess); + } + $url = $this->router->generate( + UrlAliasRouter::URL_ALIAS_ROUTE_NAME, + $routeReference->getParams(), + UrlGeneratorInterface::ABSOLUTE_URL + ); + } catch (Throwable $exception) { + $this->logger->error('NovaeZSEO: ' . $exception->getMessage()); + $url = null; + } + + return $url; + } + public function generateRouteUrl( + string $routeName, + string $siteAccess = null, + array $parameters = [] + ): ?string { + try { + $url = $this->router->generate( + $routeName, + [...['siteaccess' => $siteAccess], ...$parameters], + UrlGeneratorInterface::ABSOLUTE_URL + ); + } catch (Throwable $exception) { + $this->logger->error('NovaeZSEO: ' . $exception->getMessage()); + $url = null; + } + + return $url; + } + public function getSiteAccessesLocationIdLanguages(): array + { + + static $rootLocationLanguages = null; + + if (is_array($rootLocationLanguages)) { + return $rootLocationLanguages; + } + + $rootLocationLanguages = []; + $siteAccesses = $this->configResolver->getParameter('translation_siteaccesses'); + foreach ($siteAccesses as $siteAccess) { + $rootLocationLanguages[$siteAccess] = [ + 'rootLocationId' => $this->getSiteAccessRootLocationId($siteAccess), + 'mainLanguage' => $this->getSiteAccessMainLanguage($siteAccess), + 'languages' => $this->getSiteAccessLanguages($siteAccess) + ]; + } + + return $rootLocationLanguages; + } + + public function getCurrentSiteAccess(): ?string + { + return $this->siteAccessService->getCurrent()?->name; + } + + public function getCurrentSiteAccessRootLocationId(): ?int + { + return $this->getSiteAccessRootLocationId($this->getCurrentSiteAccess()); + } + public function getCurrentSiteAccessMainLanguage(): ?string + { + return $this->getSiteAccessMainLanguage($this->getCurrentSiteAccess()); + } + + public function getSiteAccessRootLocationId(string $siteAccess): ?int + { + return $this->configResolver->getParameter('content.tree_root.location_id', null, $siteAccess); + } + + public function getSiteAccessLanguages(string $siteAccess): array + { + return (array) $this->configResolver->getParameter('languages', null, $siteAccess); + } + + public function getSiteAccessMainLanguage(string $siteAccess): string + { + $languages = $this->configResolver->getParameter('languages', null, $siteAccess); + return array_shift($languages); + } + + public function getHrefLang(string $languageCode): string + { + return str_replace( + '_', + '-', + ($this->localeConverter->convertToPOSIX($languageCode) ?? '') + ); + } + + public function logException(Exception $exception): void + { + $this->logger?->error('NovaeZSEO: ' . $exception->getMessage()); + } +} diff --git a/components/SEOBundle/bundle/Core/MetaNameSchema.php b/components/SEOBundle/bundle/Core/MetaNameSchema.php index 5d07844f2..43f90bf32 100644 --- a/components/SEOBundle/bundle/Core/MetaNameSchema.php +++ b/components/SEOBundle/bundle/Core/MetaNameSchema.php @@ -74,6 +74,11 @@ class MetaNameSchema extends NameSchemaService */ private $configurationResolver; + /** + * @var FieldTypeRegistry + */ + protected $fieldTypeRegistry; + public function __construct( ContentTypeHandler $contentTypeHandler, FieldTypeRegistry $fieldTypeRegistry, @@ -83,6 +88,7 @@ public function __construct( ConfigResolverInterface $configurationResolver, array $settings = [] ) { + $this->fieldTypeRegistry = $fieldTypeRegistry; $settings['limit'] = $this->fieldContentMaxLength; $handler = new ContentTypeDomainMapper( $contentTypeHandler, diff --git a/components/SEOBundle/bundle/Core/Sitemap/QueryFactory.php b/components/SEOBundle/bundle/Core/Sitemap/QueryFactory.php index b2bc4e338..d0d7405ca 100644 --- a/components/SEOBundle/bundle/Core/Sitemap/QueryFactory.php +++ b/components/SEOBundle/bundle/Core/Sitemap/QueryFactory.php @@ -6,6 +6,7 @@ use Ibexa\Bundle\Core\DependencyInjection\Configuration\ConfigResolver; use Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException; +use Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException; use Ibexa\Contracts\Core\Repository\Repository; use Ibexa\Contracts\Core\Repository\Values\Content\Location; use Ibexa\Contracts\Core\Repository\Values\Content\LocationQuery as Query; @@ -52,8 +53,11 @@ private function getRootLocation(): Location ); } - public function __invoke(): Query - { + public function __invoke( + int $rootLocationId = null, + array $languages = [], + bool $matchAlwaysAvailable = true + ): Query { $query = new Query(); // always here, we want visible Contents @@ -61,7 +65,16 @@ public function __invoke(): Query // do we want to limit per Root Location, but default we don't $limitToRootLocation = $this->configResolver->getParameter('limit_to_rootlocation', 'nova_ezseo'); - if (true === $limitToRootLocation) { + if ((int) $rootLocationId) { + try { + $rootLocation = $this->repository->getLocationService()->loadLocation($rootLocationId); + } catch (NotFoundException|UnauthorizedException $e) { + $rootLocation = null; + } + if ($rootLocation) { + $criterions[] = new Criterion\Subtree($rootLocation->pathString); + } + } elseif (true === $limitToRootLocation) { $criterions[] = new Criterion\Subtree($this->getRootLocation()->pathString); } @@ -88,7 +101,8 @@ public function __invoke(): Query ) ); - $criterions[] = new Criterion\LanguageCode($this->configResolver->getParameter('languages'), true); + $languages = empty($languages) ? $this->configResolver->getParameter('languages') : $languages; + $criterions[] = new Criterion\LanguageCode($languages, $matchAlwaysAvailable); $query->query = new Criterion\LogicalAnd($criterions); $query->sortClauses = [new SortClause\DatePublished(Query::SORT_DESC)]; diff --git a/components/SEOBundle/bundle/DependencyInjection/Configuration.php b/components/SEOBundle/bundle/DependencyInjection/Configuration.php index 6f05b4216..b3594cd35 100644 --- a/components/SEOBundle/bundle/DependencyInjection/Configuration.php +++ b/components/SEOBundle/bundle/DependencyInjection/Configuration.php @@ -28,8 +28,10 @@ public function getConfigTreeBuilder(): TreeBuilder ->scalarNode('google_gatracker')->defaultValue('~')->end() ->scalarNode('google_anonymizeIp')->defaultValue('~')->end() ->scalarNode('bing_verification')->defaultValue('~')->end() - ->booleanNode('limit_to_rootlocation')->defaultValue('~')->end() - ->booleanNode('display_images_in_sitemap')->defaultValue('~')->end() + ->booleanNode('limit_to_rootlocation')->defaultFalse()->end() + ->booleanNode('multi_siteaccess_sitemap')->defaultFalse()->end() + ->booleanNode('multi_languages_sitemap')->defaultFalse()->end() + ->booleanNode('display_images_in_sitemap')->defaultFalse()->end() ->scalarNode('fieldtype_metas_identifier')->defaultValue('metas')->end() ->arrayNode('fieldtype_metas') ->isRequired() diff --git a/components/SEOBundle/bundle/DependencyInjection/NovaeZSEOExtension.php b/components/SEOBundle/bundle/DependencyInjection/NovaeZSEOExtension.php index a91191aa8..ce334a310 100644 --- a/components/SEOBundle/bundle/DependencyInjection/NovaeZSEOExtension.php +++ b/components/SEOBundle/bundle/DependencyInjection/NovaeZSEOExtension.php @@ -38,6 +38,7 @@ public function prepend(ContainerBuilder $container): void 'ez_field_templates.yml' => 'ibexa', 'variations.yml' => 'ibexa', 'admin_ui/ez_field_templates.yml' => 'ibexa', + 'ibexa_locale_conversion.yml' => 'ibexa', ]; foreach ($configs as $fileName => $extensionName) { @@ -68,6 +69,8 @@ public function load(array $configs, ContainerBuilder $container): void $processor->mapSetting('bing_verification', $config); $processor->mapSetting('limit_to_rootlocation', $config); $processor->mapSetting('display_images_in_sitemap', $config); + $processor->mapSetting('multi_siteaccess_sitemap', $config); + $processor->mapSetting('multi_languages_sitemap', $config); $processor->mapSetting('robots', $config); $processor->mapConfigArray('fieldtype_metas', $config, ContextualizerInterface::MERGE_FROM_SECOND_LEVEL); $processor->mapConfigArray('default_metas', $config); diff --git a/components/SEOBundle/bundle/Resources/config/default_settings.yml b/components/SEOBundle/bundle/Resources/config/default_settings.yml index 0e7264427..d9195ed11 100644 --- a/components/SEOBundle/bundle/Resources/config/default_settings.yml +++ b/components/SEOBundle/bundle/Resources/config/default_settings.yml @@ -11,8 +11,10 @@ parameters: nova_ezseo.default.google_anonymizeIp: ~ nova_ezseo.default.bing_verification: ~ nova_ezseo.default.custom_fallback_service: ~ - nova_ezseo.default.limit_to_rootlocation: ~ - nova_ezseo.default.display_images_in_sitemap: ~ + nova_ezseo.default.limit_to_rootlocation: false + nova_ezseo.default.display_images_in_sitemap: false + nova_ezseo.default.multi_siteaccess_sitemap: false + nova_ezseo.default.multi_languages_sitemap: false nova_ezseo.default.sitemap_excludes: locations: [] subtrees: [] diff --git a/components/SEOBundle/bundle/Resources/config/ibexa_locale_conversion.yml b/components/SEOBundle/bundle/Resources/config/ibexa_locale_conversion.yml new file mode 100644 index 000000000..82877472d --- /dev/null +++ b/components/SEOBundle/bundle/Resources/config/ibexa_locale_conversion.yml @@ -0,0 +1,15 @@ +#list from https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes +locale_conversion: + bre-BT: br_BT + rum-RO: ro_RO + mlt-MT: mt_MT + gle-IE: ga_IE + est-EE: et_EE + slv-SI: sl_SI + sla-MK: sl_MK + lav-LV: lv_LV + lit-LT: lt_LT + geo-GE: ka_GE + per-IR: fa_IR + bul-BG: bg_BG + bel-BY: be_BY diff --git a/components/SEOBundle/bundle/Resources/config/services.yml b/components/SEOBundle/bundle/Resources/config/services.yml index f7619425b..a53782e91 100644 --- a/components/SEOBundle/bundle/Resources/config/services.yml +++ b/components/SEOBundle/bundle/Resources/config/services.yml @@ -51,9 +51,9 @@ services: Novactive\Bundle\eZSEOBundle\Core\MetaNameSchema: lazy: true arguments: - $contentTypeHandler: "@Ibexa\\Contracts\\Core\\Persistence\\Content\\Type\\Handler" - $languageHandler: "@Ibexa\\Core\\Persistence\\Cache\\ContentLanguageHandler" - $translationHelper: "@Ibexa\\Core\\Helper\\TranslationHelper" + $contentTypeHandler: '@Ibexa\Contracts\Core\Persistence\Content\Type\Handler' + $languageHandler: '@Ibexa\Core\Persistence\Cache\ContentLanguageHandler' + $translationHelper: '@Ibexa\Core\Helper\TranslationHelper' calls: - [setRichTextConverter, ["@Ibexa\\FieldTypeRichText\\RichText\\Converter\\Html5"]] # Note: injecting lower layer Variation Handler (AliasGenerator) as a workaround for missing Public API objects context @@ -73,6 +73,8 @@ services: $ioService: '@ezseo_importurls.ibexa.core.io_service' $cacheDirectory: '%kernel.cache_dir%' + Novactive\Bundle\eZSEOBundle\Core\Helper\SiteMapHelper: + Novactive\Bundle\eZSEOBundle\Core\SiteAccessAwareEntityManagerFactory: arguments: $repositoryConfigurationProvider: "@Ibexa\\Bundle\\Core\\ApiLoader\\RepositoryConfigurationProvider" diff --git a/components/SEOBundle/documentation/USAGE.md b/components/SEOBundle/documentation/USAGE.md index 06214de91..fa2bcbef3 100644 --- a/components/SEOBundle/documentation/USAGE.md +++ b/components/SEOBundle/documentation/USAGE.md @@ -141,6 +141,48 @@ nova_ezseo: contentTypeIdentifiers: ['footer','something'] ``` + +Set `multi_siteaccess_sitemap` to true to automatically generate the index page sitemap.xml per site access. + +```yml +nova_ezseo: + system: + default: + multi_siteaccess_sitemap: true +``` + +```html + + + https://www.example.com/fr/sitemap-1.xml + 2024-05-31T01:04:52+00:00 + + + https://www.example.com/en/sitemap-1.xml + 2024-05-31T01:04:52+00:00 + + +``` + +Set `multi_languages_sitemap` to inject the multilingual and multinational site annotations tag +Notice: this doesn't work with `multi_siteaccess_sitemap: false`. +```html + + https://www.example.com/pageFr + + + 2024-05-03T08:48:52+00:00 + +``` + +```yml +nova_ezseo: + system: + default: + multi_siteaccess_sitemap: true + multi_languages_sitemap: true +``` + Set "display_images_in_sitemap" to true to inject the image tags. Notice: this doesn't work with `limit_to_rootlocation: true`. diff --git a/composer.json b/composer.json index ee87e8344..abaf71ac5 100644 --- a/composer.json +++ b/composer.json @@ -61,6 +61,7 @@ "knplabs/knp-menu": "^3.1", "behat/behat": "^3.8", "friends-of-behat/mink-extension": "^2.5", - "ext-pdo": "*" + "ext-pdo": "*", + "ext-dom": "*" } }