Redirect to new slug in Page.update:after?

Question:
How do I redirect to a newly updated page slug after updating the page’s slug?

Situation:
I have a page called Work with subpages called project - each project’s title and url appendix is generated from two fields; Client and project title.

On the project page I can update either client or project title which correctly set the new title and slug but I am unable to redirect to the new page.

Project.yml blueprint:

#
# Blueprint for Projects
#
# page title
title: Project
# options for when creating a new work projects
create:
  title: "{{ page.client }} – {{ page.project }}"
  fields:
    - client
    - project
  slug: "{{ page.client }}-{{ page.project }}"

# options for available status options
status:
  listed: Published
  draft: Draft
  unlisted: Unlisted

#
# Content
#
tabs:
  #
  # TAB 1 - content
  #
  content:
    label: Content
    icon: text
    columns:
      # layout
      media:
        width: 1/2
        fields:
          #
          # Project - media
          #
          mediaGallery:
            type: files
            layout: cards
            size: medium
            label: Project Media
            uploads: false
            help: select media, to upload see storage tab.
            sortable: true
            info: "{{ file.template }}"
      #
      # Project - information
      #
      information:
        width: 1/2
        fields:
          #
          # Project - client
          #
          client:
            label: Project client
            width: 1/2
            type: select
            required: true
            placeholder: select client
            help: Select client for project, or create new under Work > Settings
            options:
              type: query
              query: site.find("work").clients.toStructure
              text: "{{ item.clientname }}"
              value: "{{ item.clientname }}"
          #
          # Project - title
          #
          project:
            label: project title
            width: 1/2
            type: text
            help: Enter the project name
            required: true
          #
          # Project - url
          #
          project url:
            type: headline
            help: "{{ page.url }}"
          # break
          lineA:
            type: line
          #
          # Project - categories
          #
          tags:
            type: tags
            label: Categories
            min: 1
            accept: options
            help: Select one or more categories for the project, or create new under Work > Settings
            options:
              type: query
              query: site.find("work").categories.toStructure
              text: "{{ item.category }}"
              value: "{{ item.category }}"
          # break
          lineB:
            type: line
          #
          # Project - description
          #
          description:
            type: textarea
            size: small
            buttons: false
          toggleDisplayDescription:
            label: Hide description on project page?
            type: toggle
            help: Hide the description on the project page but will keep it for SEO.
            default: "no"
            options:
              - no
              - yes
          # break
          lineC:
            type: line
          #
          # Project - credit list
          #
          credits:
            label: Credit list
            type: structure
            fields:
              name:
                label: name
                type: text
              role:
                label: role
                type: text
              link:
                label: link
                type: url

  #
  # TAB 2 - media storage
  #
  storage:
    label: Media
    icon: import
    fields:
      #
      # Media - Image uploading section
      #
      imageMedia:
        label: Image upload
        help: Upload project images here.
        type: files
        layout: cards
        size: medium
        uploads: image
        info: "{{ file.template }}"
      # break
      lineD:
        type: line
      #
      # Media - Video uploading section
      #
      videoMedia:
        label: Video upload
        help: Upload project videos here.
        type: files
        layout: cards
        size: medium
        uploads: video
        info: "{{ file.template }}"
      # break
      lineF:
        type: line
      #
      # Media - Audio uploading section
      #
      audioMedia:
        label: Audio upload
        help: Upload project audio here.
        type: files
        layout: cards
        size: medium
        uploads: audio
        info: "{{ file.template }}"

“Page.update:after” hook:

"page.update:after" => function (
            Kirby\Cms\Page $newPage,
            Kirby\Cms\Page $oldPage
        ) {
            $client = $newPage->client()->value();
            $project = $newPage->project()->value();

            function convertText($inputText)
            {
                // Replace spaces with dashes
                $textWithDashes = str_replace(" ", "-", $inputText);

                // Convert non-English characters to Latin
                $latinText = iconv("UTF-8", "ASCII//TRANSLIT", $textWithDashes);

                return $latinText;
            }

            $cleanClient = convertText($client);
            $cleanProject = convertText($project);

            $newPage->changeTitle($client . " - " . $project);
            $newPage->changeSlug($client . " - " . $project);
        },

Any suggestions or hints would be greatly appreciated.

Kirby: 4.1.0
PHP: 8.3.3
Browser: Safari 17.3
OS: Mac 14.3

The simple answer is: you can’t.

It’s better to overwrite the relevant methods in a page model, so Kirby can take care of the redirect.

argh right ok. I had a go at creating an update and it seems to work except it does not redirect correctly to the new page, it just shows a panel dialog with the old url and I’m not sure where I’m going wrong.

Do you have a suggestion?

public function update(
        ?array $input = null,
        ?string $languageCode = null,
        bool $validate = false
    ): static {
        parent::update($input, $languageCode, $validate); // Call parent update method

        if (isset($input["client"]) && isset($input["project"])) {
            $client = $input["client"];
            $projectTitle = $input["project"];

            $this->changeTitle($client . " - " . $projectTitle);
            $this->changeSlug($client . "-" . $projectTitle);

            // Get the URL of the updated page with the new slug
            $newUrl = $this->panel()->url("pages/" . $this->id());

            // Redirect to the updated page in the panel with the new slug
            Panel::go($newUrl);

            // Redirect to the updated page in the panel with the new slug
        }
        return $this; // Return an instance of ProjectPage
    }

Don’t redirect from there

Also, don’t call changeTitle/changeSlug from update

would it then be in the page.update:after hook?

I’m not following why that is not okay. Could you link to the docs on what I should use else?
thank you

Oh, sorry, I totally misread (and didn’t read your code properly). This is about the client and project being changed, and you want to change the title and slug accordingly.

exactly yes :slight_smile:

There are two fields on the blueprint ( client and project ) which is the ‘components’ for both the title and slug.

The title and slug format is always: Client name – Project name
i.e. client could be Kirby cms and project name could be theme design, Then the slug would be kirby-cms-theme-design

The client and project is set on the page creation dialogue but I also have it on the page it self and I would like to be able to change either client or project title and then the page title and slug update to match.

Do I need to create separate extensions to changeSlug and changeTitle, and then redirect to the new page through a page.update:after hook?

any help is much appreciated ! thank you

As I said in one of the first post, you cannot redirect to the new pages from that hook. And in particular, you cannot call go() inside the update method, that method needs to return a page object.

However, updating the slug from the model doesn’t seem to be possible, it always results in an error.

So not sure if this is possible at all.

BTW: Unless you are using permalinks, changing the slug without having redirects in place is generally not such a good idea, I think, at least not once your pages are public and indexed by search engines.

Thank you for looking in to it for me!
It’s not a deal breaker as I can just have it update the page title based on my fields and then manually have the slug change based on the title - that works as well.

As I mentioned when I create the page I will select client and project title and the likelihood of me having to change it later is slim, but it was just intended as a neat little helper feature :slight_smile:

I’m familiar with the SEO-issues that can arise from changing the urls if indexed, but a good point :slight_smile: