Skip to content

Commit

Permalink
generate sitemap.xml per siteAccess && inject the multilingual and mu…
Browse files Browse the repository at this point in the history
…ltinational site annotations tag.
  • Loading branch information
mohamed-larbi-jebari committed May 31, 2024
1 parent 17637d6 commit bb3f0d4
Show file tree
Hide file tree
Showing 11 changed files with 528 additions and 77 deletions.
321 changes: 256 additions & 65 deletions components/SEOBundle/bundle/Controller/SitemapController.php

Large diffs are not rendered by default.

173 changes: 173 additions & 0 deletions components/SEOBundle/bundle/Core/Helper/SiteMapHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<?php

declare(strict_types=1);

namespace Novactive\Bundle\eZSEOBundle\Core\Helper;

use Exception;
use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface;
use Ibexa\Core\MVC\Symfony\Locale\LocaleConverter;
use Ibexa\Core\MVC\Symfony\Routing\Generator\RouteReferenceGenerator;
use Ibexa\Core\MVC\Symfony\Routing\UrlAliasRouter;
use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface;
use Ibexa\Migration\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
use Throwable;

class SiteMapHelper
{
use LoggerAwareTrait;

/**
* @var ConfigResolverInterface
*/
private $configResolver;

/**
* @var RouteReferenceGenerator
*/
private $routeReferenceGenerator;

/**
* @var RouterInterface
*/
private $router;

/**
* @var LocaleConverter
*/
private $localeConverter;

/**
* @var SiteAccessServiceInterface
*/
private $siteAccessService;

public function __construct(
ConfigResolverInterface $configResolver,
SiteAccessServiceInterface $siteAccessService,
RouteReferenceGenerator $routeReferenceGenerator,
RouterInterface $router,
LocaleConverter $localeConverter,
?LoggerInterface $logger = null
) {
$this->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());
}
}
6 changes: 6 additions & 0 deletions components/SEOBundle/bundle/Core/MetaNameSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ class MetaNameSchema extends NameSchemaService
*/
private $configurationResolver;

/**
* @var FieldTypeRegistry
*/
protected $fieldTypeRegistry;

public function __construct(
ContentTypeHandler $contentTypeHandler,
FieldTypeRegistry $fieldTypeRegistry,
Expand All @@ -83,6 +88,7 @@ public function __construct(
ConfigResolverInterface $configurationResolver,
array $settings = []
) {
$this->fieldTypeRegistry = $fieldTypeRegistry;
$settings['limit'] = $this->fieldContentMaxLength;
$handler = new ContentTypeDomainMapper(
$contentTypeHandler,
Expand Down
22 changes: 18 additions & 4 deletions components/SEOBundle/bundle/Core/Sitemap/QueryFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -52,16 +53,28 @@ 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
$criterions = [new Criterion\Visibility(Criterion\Visibility::VISIBLE)];

// 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);
}

Expand All @@ -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)];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: []
Expand Down
Original file line number Diff line number Diff line change
@@ -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
8 changes: 5 additions & 3 deletions components/SEOBundle/bundle/Resources/config/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down
42 changes: 42 additions & 0 deletions components/SEOBundle/documentation/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://www.example.com/fr/sitemap-1.xml</loc>
<lastmod>2024-05-31T01:04:52+00:00</lastmod>
</sitemap>
<sitemap>
<loc>https://www.example.com/en/sitemap-1.xml</loc>
<lastmod>2024-05-31T01:04:52+00:00</lastmod>
</sitemap>
</sitemapindex>
```

Set `multi_languages_sitemap` to inject the multilingual and multinational site annotations tag
Notice: this doesn't work with `multi_siteaccess_sitemap: false`.
```html
<url xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<loc>https://www.example.com/pageFr</loc>
<xhtml:link xmlns:xhtml="http://www.w3.org/1999/xhtml" rel="alternate" hreflang="en-GB" href="https://www.example.com/en/pageEn" />
<xhtml:link xmlns:xhtml="http://www.w3.org/1999/xhtml" rel="alternate" hreflang="fr-FR" href="https://www.example.com/pageFr" />
<lastmod>2024-05-03T08:48:52+00:00</lastmod>
</url>
```

```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`.

Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "*"
}
}

0 comments on commit bb3f0d4

Please sign in to comment.