Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG_de-DE.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG_en-GB.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down
22 changes: 19 additions & 3 deletions src/Content/Blog/BlogSeoUrlRoute.php
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand Down
9 changes: 9 additions & 0 deletions src/Resources/config/config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@
<title>SEO Meta Configuration</title>
<title lang="de-DE">SEO Metakonfiguration</title>

<input-field type="text">
<name>blogUrlPrefix</name>
<label>Blog URL prefix (e.g., "blog", "news", "beauty-blog")</label>
<label lang="de-DE">Blog URL Präfix (z. B. "blog", "news", "beauty-blog")</label>
<defaultValue>blog</defaultValue>
<helpText>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.</helpText>
<helpText lang="de-DE">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.</helpText>
</input-field>

<input-field type="int">
<name>maximumMetaTitleCharacter</name>
<label>Maximum meta title characters</label>
Expand Down
1 change: 1 addition & 0 deletions src/Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@

<service id="Werkl\OpenBlogware\Content\Blog\BlogSeoUrlRoute">
<argument type="service" id="Werkl\OpenBlogware\Content\Blog\BlogEntryDefinition"/>
<argument type="service" id="Shopware\Core\System\SystemConfig\SystemConfigService"/>

<tag name="shopware.seo_url.route"/>
</service>
Expand Down
33 changes: 33 additions & 0 deletions src/Subscriber/BlogCacheInvalidSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ public static function getSubscribedEvents(): array
SeoEvents::SEO_URL_TEMPLATE_WRITTEN_EVENT => [
['updateSeoUrlForAllArticles', 10],
],
'system_config.written' => [
['onSystemConfigChanged', 10],
],
];
}

Expand Down Expand Up @@ -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<string> $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
*/
Expand Down
128 changes: 128 additions & 0 deletions tests/PHPUnit/Content/Blog/BlogSeoUrlRouteTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php
declare(strict_types=1);

namespace OpenBlogware\Tests\Content\Blog;

use PHPUnit\Framework\TestCase;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Werkl\OpenBlogware\Content\Blog\BlogEntryDefinition;
use Werkl\OpenBlogware\Content\Blog\BlogSeoUrlRoute;

class BlogSeoUrlRouteTest extends TestCase
{
/**
* Test that the default template uses "blog" prefix when no config is set
*/
public function testGetConfigWithDefaultPrefix(): void
{
$systemConfigService = $this->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());
}
}