Removing the parent page slug from urls and sitemap.xml (in multilanguage sites)

Hi.

I would like to modify the URL in my current project so that the parent page slug is omitted for certain pages. The project is also multilingual (DE, EN) and the URLs are partial multilevel.

I have used the following instructions and modified them accordingly:

and

Now I would like to remove the parent slugs from the XML sitemap as well.

I have already coded the following, but I am not sure if there is a better or more performant way to do this.

<?= '<?xml version="1.0" encoding="utf-8"?>'; ?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <?php foreach ($languages as $l): ?>
    <?php foreach ($pages as $p): 
    if (in_array($p->uri(), $ignorePages) || in_array($p->intendedTemplate(), $ignoreTemplates)) continue;
      $removePath = ['/kollektionen/', '/collections/'];
      $url = str_replace($removePaths, '/', $p->url($l->code()));
    ?>
    <url>
        <loc><?= html($url) ?></loc>
        <lastmod><?= $p->modified('c', 'date') ?></lastmod>
        <priority><?= ($p->isHomePage()) ? 1 : number_format(0.5 / $p->depth(), 1) ?></priority>
    </url>
    <?php endforeach ?>
    <?php endforeach ?>
</urlset>

Without modification, the URLs may look like this:

http://localhost/myproject/kollektionen/italienische-kollektion/serie-1
http://localhost/myproject/en/collections/german-collection/serie-1

with modification it should look like this

http://localhost/myproject/deutsche-kollektion/serie-1
http://localhost/myproject/en/german-collection/serie-1

What is the most elegant way to do this? Is there a best practice for this?

Regarding the removal of parent slugs in pages with multiple levels in combination with routing I may have questions in consequence later :slight_smile:

Thanks for your help and best regards,
Robert

The most elegant way to do this would be to define a custom page method or to overwrite the url() method in a page model.

Hi Texnixe,

thank you for the quick reply.
I have now solved it using Page Model.

use Kirby\Cms\Page;

class CollectionLightPage extends Page {
   public function url($options = null): string {
      $langCode = kirby()->language()->code();
      $removeFromPath = ['kollektionen','collections'];
      $_removeFromPath  = A::map($removeFromPath, function ($item) {
        return sprintf('/%s/', $item);
      });
      return str_replace($_removeFromPath, '/', parent::url($langCode));
   }
}

To remove really only the desired part of the path, and no other strings in the URL, I wrapped everything in “/” with the A::map() method before replacing, but I’m still not sure if this is the most elegant way to do it…

I am not familiar with custom page methods…

I’m also still struggling a bit with the appropriate routing …

Best regards,
Robert

Hi,
now I noticed that in the sitemap the urls where I overwrote the url() method (with the page model) don’t have any language-code. For all other pages it works fine. Here is the code for the sitemap.

....
<url>
   <loc>http://localhost/myproject/italienische-kollektion</loc> 
   <!--should be <loc>http://localhost/myproject/en/italienische-kollektion</loc>-->
   <lastmod>2022-08-30T19:29:33+00:00</lastmod> 
   <priority>0.3</priority>
</url>
<url>
   <loc>http://localhost/myproject/italienische-kollektion/serie-1</loc>
   <!--should be <loc>http://localhost/myproject/en/italienische-kollektion/serie-1</loc>-->
   <lastmod>2022-09-02T10:56:15+00:00</lastmod>
   <priority>0.2</priority>
</url>
...
...
<url>
  <loc>http://localhost/myproject/en/about</loc>
  <!-- that's ok-->
  <lastmod>2022-08-30T19:41:33+00:00</lastmod>
  <priority>0.5</priority>
</url>
....

I have also adapted the php code of the sitemap:

<?= '<?xml version="1.0" encoding="utf-8"?>'; ?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <?php foreach ($languages as $l): ?>
    <?php foreach ($pages as $p): 
      if (in_array($p->uri(), $ignorePages) || in_array($p->intendedTemplate(), $ignoreTemplates)) continue;
    ?>
    <url>
        <loc><?= html($p->url($l->code())) ?></loc>
        <lastmod><?= $p->modified('c', 'date') ?></lastmod>
        <priority><?= ($p->isHomePage()) ? 1 : number_format(0.5 / $p->depth(), 1) ?></priority>
    </url>
    <?php endforeach ?>
    <?php endforeach ?>
</urlset>

What am I doing wrong?

I’ve been trying for almost the whole weekend now, but I can’t get it to work.

It seems that on pages which I customized with page model (see post above) the language-code is not set.

Also on pages where I have overridden the url method using page model the language code is apparently ignored. Here is an excerpt from my langunage navigation:

<?php foreach($kirby->languages() as $language): 
    $langCode = $language->code();
    $isAktLang = $aktLanguage == $language;

    if($G_VARS['aktTemplate'] == 'collection-dark' || $G_VARS['aktTemplate'] == 'collection-light') {
       $href = $site->url($langCode) . '/' . $page->slug();
    } else {
       $href = $page->url($langCode);
    }//endif
?>

If someone could help me a little bit here, I would be very happy!

Best regards,
Robert

Here you pass the language code.

But in your method, you complete ignore a language code parameter and try to get the the language via kirby()->language()->code. This cannot work, because in the sitemap where you loop through many pages, this will return nothing useful. So when the option is passed, you have to use it in your method body.

You should also set $this->url when returning, see the original method.

Hi texnixe,
you have put me on the right track.

Apparently I don’t have to override the url method here, but the urlForLanguage method.

For the sake of completeness I left the “multilanguage if query” in the url method … as a fallback in case the page is only monolingual at some point.

Here is the code how I solved it for me now … maybe this will help someone else …

use Kirby\Cms\Page;

class LinePage extends Page {

   public function url($options = null): string {

      if ($this->kirby()->multilang() === true) {
			if (is_string($options) === true) {
				return $this->urlForLanguage($options);
			} else {
				return $this->urlForLanguage(null, $options);
			}
		}

      $parent = $this->parent();
      return $this->url = $this->kirby()->url('base') . '/' . $parent->uid() . '/' . $this->uid();
   }

   public function urlForLanguage($language = null, array $options = null): string {
      $parent = $this->parent();
      return $this->url = $this->site()->urlForLanguage($language) . '/' . $parent->slug($language) . '/' . $this->slug($language);
	}
}

I’m trying to achieve the exact same thing as stated in the title.
My blog is in 2 languages and urls look like this for now:

http://example.com/actualites/mon-blog-post
http://example.com/en/news/my-translated-blog-post

(I removed the /fr/ for the default french language)
And I would like to remove the parent url:

http://example.com/mon-blog-post
http://example.com/en/my-translated-blog-post

I’ve followed the instructions in the docs to remove the default parent url /actualites/
But when I tried to add another config to remove /news/ it does not work.

Here is the code of my routes

 'routes' => [
        [
            'pattern' => '(:any)',
            'action'  => function($uid) {
                $page = page($uid);
                if(!$page) $page = page('actualites/' . $uid);
                if(!$page) $page = site()->errorPage();
                return site()->visit($page);
            }
        ],

        [
          // redirection des urls avec /actualites/{permalink-article} vers /{permalink-article}
            'pattern' => 'actualites/(:any)',
            'action'  => function($uid) {
                go($uid);
            }
        ],
        // Route for English pages without /news/
        [
            'pattern' => 'en/(:any)',
            'action'  => function($uid) {
                $page = page('en/' . $uid);
                if(!$page) $page = page('en/news/' . $uid);
                if(!$page) $page = site()->errorPage();
                return site()->visit($page);
            }
        ],

        // Redirect English URLs with /news/ to the version without /news/
        [
            'pattern' => 'en/news/(:any)',
            'action'  => function($uid) {
                go('en/' . $uid);
            }
        ]

    ]

But it returns this error when I visit a blog post page

TypeError thrown with message "Kirby\Cms\Site::visit(): Argument #1 ($page) must be of type Kirby\Cms\Page|string, null given, called in /home/local/site/config/config.php on line 44"

Stacktrace:
#7 TypeError in /home/local/kirby/src/Cms/Site.php:476
#6 Kirby\Cms\Site:visit in /home/local/site/config/config.php:44
#5 Kirby\Http\Route:{closure} in [internal]:0
#4 Closure:call in /home/local/kirby/src/Http/Router.php:120
#3 Kirby\Http\Router:call in /home/local/kirby/src/Cms/App.php:338
#2 Kirby\Cms\App:call in /home/local/kirby/src/Cms/App.php:1191
#1 Kirby\Cms\App:render in /home/local/index.php:5
#0 require in /home/local/kirby/router.php:15

Should I do it the way @RoBoBo did it? But i’m on Kirby v4 now, what’s the best recommended way to manage this, any suggestion @texnixe?

Thanks for your help