diff --git a/CHANGELOG_de-DE.md b/CHANGELOG_de-DE.md index 363c5bfb..39f49c3c 100644 --- a/CHANGELOG_de-DE.md +++ b/CHANGELOG_de-DE.md @@ -1,3 +1,8 @@ +# Unveröffentlicht +- Konfigurierbares Blog-URL-Präfix hinzugefügt, um Blog-Detail-URLs anzupassen (z. B. von "blog/..." zu "beauty-blog/...") +- SEO-URLs werden automatisch neu generiert, wenn das Präfix geändert wird +- Standard-Präfix bleibt "blog" für Abwärtskompatibilität + # 5.0.2 - Kompatibilität mit Shopware 6.7.5.0 diff --git a/CHANGELOG_en-GB.md b/CHANGELOG_en-GB.md index ed70fba5..c89b2ba2 100644 --- a/CHANGELOG_en-GB.md +++ b/CHANGELOG_en-GB.md @@ -1,3 +1,8 @@ +# Unreleased +- Added configurable blog URL prefix to allow customization of blog detail URLs (e.g., from "blog/..." to "beauty-blog/...") +- SEO URLs are automatically regenerated when the prefix is changed +- Default prefix remains "blog" for backward compatibility + # 5.0.2 - Compatibility with Shopware 6.7.5.0 diff --git a/README.md b/README.md index 30ac5545..8733e3db 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,25 @@ where you can also select from all available variables. ![](https://res.cloudinary.com/dtgdh7noz/image/upload/v1602580850/Bildschirmfoto_2020-10-13_um_12.20.25_xxnrro.png) *SEO URL template* +#### Custom Blog URL Prefix +You can now customize the URL prefix for your blog detail pages. Instead of the default "blog" prefix, +you can set any custom prefix (e.g., "beauty-blog", "news", "articles") in the plugin configuration: + +1. Navigate to **Settings → System → OpenBlogware** +2. Find the **"Blog URL prefix"** field in the "SEO Meta Configuration" section +3. Enter your desired prefix (e.g., "beauty-blog") +4. Save the configuration + +When you change the prefix, all existing blog SEO URLs are automatically regenerated. This is particularly +useful for: +- Maintaining existing URL structure when migrating from other platforms +- SEO optimization with brand-specific URLs +- Multi-language sites with different prefixes + +**Example:** +- Default: `blog/category-name/my-post` +- Custom: `beauty-blog/category-name/my-post` + ## RSS Feed For access **RSS Feed** url you can use this path `/blog/rss` Example(`http://Your-domain/blog/rss`) diff --git a/src/Content/Blog/BlogSeoUrlRoute.php b/src/Content/Blog/BlogSeoUrlRoute.php index 064445dd..0b07640d 100644 --- a/src/Content/Blog/BlogSeoUrlRoute.php +++ b/src/Content/Blog/BlogSeoUrlRoute.php @@ -9,26 +9,42 @@ use Shopware\Core\Framework\DataAbstractionLayer\Entity; use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; use Shopware\Core\System\SalesChannel\SalesChannelEntity; +use Shopware\Core\System\SystemConfig\SystemConfigService; class BlogSeoUrlRoute implements SeoUrlRouteInterface { public const ROUTE_NAME = 'werkl.frontend.blog.detail'; public const DEFAULT_TEMPLATE = 'blog/{{ entry.blogCategories.first.translated.name|lower }}/{{ entry.translated.slug|lower }}'; - public function __construct(private readonly BlogEntryDefinition $blogEntryDefinition) - { + public function __construct( + private readonly BlogEntryDefinition $blogEntryDefinition, + private readonly SystemConfigService $systemConfigService + ) { } public function getConfig(): SeoUrlRouteConfig { + $template = $this->getTemplate(); + return new SeoUrlRouteConfig( $this->blogEntryDefinition, self::ROUTE_NAME, - self::DEFAULT_TEMPLATE, + $template, true ); } + private function getTemplate(): string + { + $prefix = $this->systemConfigService->getString('WerklOpenBlogware.config.blogUrlPrefix'); + if ($prefix === null || $prefix === '') { + $prefix = 'blog'; + } + + // Use the configured prefix instead of hardcoded "blog" + return $prefix . '/{{ entry.blogCategories.first.translated.name|lower }}/{{ entry.translated.slug|lower }}'; + } + public function prepareCriteria(Criteria $criteria, SalesChannelEntity $salesChannel): void { $criteria->addAssociations([ diff --git a/src/Resources/config/config.xml b/src/Resources/config/config.xml index b74e2a28..50d2f10d 100644 --- a/src/Resources/config/config.xml +++ b/src/Resources/config/config.xml @@ -16,6 +16,15 @@ SEO Meta Configuration SEO Metakonfiguration + + blogUrlPrefix + + + blog + The prefix used in blog detail URLs. Change this to customize your blog URLs (e.g., from "blog/my-post" to "beauty-blog/my-post"). After changing this value, the SEO URLs will be regenerated automatically. + Das Präfix für Blog-Detail-URLs. Ändern Sie dies, um Ihre Blog-URLs anzupassen (z. B. von "blog/mein-beitrag" zu "beauty-blog/mein-beitrag"). Nach der Änderung werden die SEO-URLs automatisch neu generiert. + + maximumMetaTitleCharacter diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index 67c27133..be7a1585 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -127,6 +127,7 @@ + diff --git a/src/Subscriber/BlogCacheInvalidSubscriber.php b/src/Subscriber/BlogCacheInvalidSubscriber.php index 710aef9c..c138b7b3 100644 --- a/src/Subscriber/BlogCacheInvalidSubscriber.php +++ b/src/Subscriber/BlogCacheInvalidSubscriber.php @@ -66,6 +66,9 @@ public static function getSubscribedEvents(): array SeoEvents::SEO_URL_TEMPLATE_WRITTEN_EVENT => [ ['updateSeoUrlForAllArticles', 10], ], + 'system_config.written' => [ + ['onSystemConfigChanged', 10], + ], ]; } @@ -147,6 +150,36 @@ public function updateSeoUrlForAllArticles(EntityWrittenEvent $event): void $this->seoUrlUpdater->update(BlogSeoUrlRoute::ROUTE_NAME, $ids); } + /** + * When the blog URL prefix configuration changes, regenerate all blog SEO URLs + */ + public function onSystemConfigChanged(EntityWrittenEvent $event): void + { + $context = $event->getContext(); + + // Check if any of the written config keys is the blogUrlPrefix + $payloads = $event->getPayloads(); + $needsUpdate = false; + + foreach ($payloads as $payload) { + if (isset($payload['configurationKey']) && $payload['configurationKey'] === 'WerklOpenBlogware.config.blogUrlPrefix') { + $needsUpdate = true; + break; + } + } + + if (!$needsUpdate) { + return; + } + + /** @var list $ids */ + $ids = $this->blogRepository->searchIds(new Criteria(), $context)->getIds(); + + $this->seoUrlUpdater->update(BlogSeoUrlRoute::ROUTE_NAME, $ids); + $this->invalidateCache($ids); + $this->invalidateCacheCategory($context); + } + /** * Invalidate blog category cache */ diff --git a/tests/PHPUnit/Content/Blog/BlogSeoUrlRouteTest.php b/tests/PHPUnit/Content/Blog/BlogSeoUrlRouteTest.php new file mode 100644 index 00000000..c5074caa --- /dev/null +++ b/tests/PHPUnit/Content/Blog/BlogSeoUrlRouteTest.php @@ -0,0 +1,128 @@ +createMock(SystemConfigService::class); + $systemConfigService->method('getString') + ->with('WerklOpenBlogware.config.blogUrlPrefix') + ->willReturn(''); + + $blogEntryDefinition = $this->createMock(BlogEntryDefinition::class); + $route = new BlogSeoUrlRoute($blogEntryDefinition, $systemConfigService); + + $config = $route->getConfig(); + $template = $config->getTemplate(); + + static::assertStringStartsWith('blog/', $template); + static::assertStringContainsString('{{ entry.blogCategories.first.translated.name|lower }}', $template); + static::assertStringContainsString('{{ entry.translated.slug|lower }}', $template); + } + + /** + * Test that custom prefix is used when configured + */ + public function testGetConfigWithCustomPrefix(): void + { + $systemConfigService = $this->createMock(SystemConfigService::class); + $systemConfigService->method('getString') + ->with('WerklOpenBlogware.config.blogUrlPrefix') + ->willReturn('beauty-blog'); + + $blogEntryDefinition = $this->createMock(BlogEntryDefinition::class); + $route = new BlogSeoUrlRoute($blogEntryDefinition, $systemConfigService); + + $config = $route->getConfig(); + $template = $config->getTemplate(); + + static::assertStringStartsWith('beauty-blog/', $template); + static::assertStringContainsString('{{ entry.blogCategories.first.translated.name|lower }}', $template); + static::assertStringContainsString('{{ entry.translated.slug|lower }}', $template); + } + + /** + * Test that "news" prefix works correctly + */ + public function testGetConfigWithNewsPrefix(): void + { + $systemConfigService = $this->createMock(SystemConfigService::class); + $systemConfigService->method('getString') + ->with('WerklOpenBlogware.config.blogUrlPrefix') + ->willReturn('news'); + + $blogEntryDefinition = $this->createMock(BlogEntryDefinition::class); + $route = new BlogSeoUrlRoute($blogEntryDefinition, $systemConfigService); + + $config = $route->getConfig(); + $template = $config->getTemplate(); + + static::assertStringStartsWith('news/', $template); + } + + /** + * Test that "articles" prefix works correctly + */ + public function testGetConfigWithArticlesPrefix(): void + { + $systemConfigService = $this->createMock(SystemConfigService::class); + $systemConfigService->method('getString') + ->with('WerklOpenBlogware.config.blogUrlPrefix') + ->willReturn('articles'); + + $blogEntryDefinition = $this->createMock(BlogEntryDefinition::class); + $route = new BlogSeoUrlRoute($blogEntryDefinition, $systemConfigService); + + $config = $route->getConfig(); + $template = $config->getTemplate(); + + static::assertStringStartsWith('articles/', $template); + } + + /** + * Test that "my-custom-blog" prefix with hyphens works correctly + */ + public function testGetConfigWithHyphenatedPrefix(): void + { + $systemConfigService = $this->createMock(SystemConfigService::class); + $systemConfigService->method('getString') + ->with('WerklOpenBlogware.config.blogUrlPrefix') + ->willReturn('my-custom-blog'); + + $blogEntryDefinition = $this->createMock(BlogEntryDefinition::class); + $route = new BlogSeoUrlRoute($blogEntryDefinition, $systemConfigService); + + $config = $route->getConfig(); + $template = $config->getTemplate(); + + static::assertStringStartsWith('my-custom-blog/', $template); + } + + /** + * Test that route name remains constant + */ + public function testRouteNameIsConstant(): void + { + $systemConfigService = $this->createMock(SystemConfigService::class); + $systemConfigService->method('getString')->willReturn('test-prefix'); + + $blogEntryDefinition = $this->createMock(BlogEntryDefinition::class); + $route = new BlogSeoUrlRoute($blogEntryDefinition, $systemConfigService); + + $config = $route->getConfig(); + + static::assertSame(BlogSeoUrlRoute::ROUTE_NAME, $config->getRouteName()); + static::assertSame('werkl.frontend.blog.detail', $config->getRouteName()); + } +}