Updating structure field inside a hook

Hi there

I’m trying to update a structure field after updating the site. It’s used for multilingual tags inside each project. Since I need slugs to get the filtering done, I planned to generate slugs inside a structure field with type: hidden and use it as a value inside my project-blueprint. The Site throws an error, that the form couldn’t be saved. Does anybody has hints on how to solve that?

Here is my code:

'hooks' => [
  'site.update:after' => function ($newSite,$oldSite) {
    $tags = $newSite->tags()->yaml();
    foreach($tags as $tag) {
      $tag["slugde"] = Str::slug($tag["de"]);
      $tag["slugen"] = Str::slug($tag["en"]);
    }
    $newSite->update([
      'tags' => Data::encode($tags, "yaml")
    ], 'de');
  },
]

When and where exactly is the error thrown? When you try to save the page and before the hook is called? Does the hook do anything despite the error?

Could you provide the blueprint or at least the relevant parts for testing?

hey @texnixe

thanks for looking into it! The error shows up when I throw an error after iterating through the structure field with

throw new Exception(Data::encode($tags, "yaml"));

I think it shows up before the hook tries to update the page, because afterwards everything reloads on my local environment.

My site-blueprint looks like this:

title: 
  en: Site
  de: Startseite

tabs:
  dashboard:
    # ...
  settings:
    label: 
      en: Language settings
      de: Sprach-Einstellungen
    columns:
      - width: 2/4
        sections:
          projectsTags:
            type: fields
            fields:
              tags:
                label: Tags
                type: structure
                translate: false
                fields:
                  de:
                    label:
                      en: German
                      de: Deutsch
                    type: text
                    width: 1/2
                    required: true
                  slugde:
                    type: text
                    width: 1/2
                    type: hidden
                  en:
                    label: 
                      en: English
                      de: Englisch
                    type: text
                    width: 1/2
                    required: true
                  slugen:
                    type: text
                    width: 1/2
                    type: hidden
    # ...

German is your default language, isn’t it?

The problem with your code is that you want to modify $tag in your loop, so you have to change it like this:

    'hooks' => [
      'site.update:after' => function ($newSite,$oldSite) {
        $tags = $newSite->tags()->yaml();
        foreach($tags as &$tag) { // note the `&` in front of `$tag`
          $tag["slugde"] = Str::slug($tag["de"]);
          $tag["slugen"] = Str::slug($tag["en"]);
        }
        $newSite->update([
          'tags' => Data::encode($tags, "yaml")
        ], 'de');
      },
    ],

Apart from that, everything should work. Why are you throwing the exception?

What’s the purpose of creating these slugs?

Amazing, thanks for your support!

Yes, that’s why I update just the German language. The structure-field is set to translate: false, so that there is only an option to create the tags in the default language. The idea is a kind of taxnomy area, where you see the translations next to each other.

Just to understand what’s going on. Is there a better method?

The possibility to add tags inside the sites taxonomy field. Later on, the user can choose the tags with following setup inside my project-blueprint:


  currentLang:
    type: hidden
  tags:
    type: hidden
  tagsde:
    label: Tags
    type: multiselect
    accept: options
    options: query
    query:
      fetch: site.tags.toStructure
      text: "{{ structureItem.de }}"
      value: "{{ structureItem.slugde }}"
    when: 
      currentLang: de
  tagsen:
    label: Tags
    type: multiselect
    accept: options
    options: query
    query:
      fetch: site.tags.toStructure
      text: "{{ structureItem.en }}"
      value: "{{ structureItem.slugen }}"
    when: 
      currentLang: en

And following hooks inside my config:

  return [
    'hooks' => [
      'site.update:after' => function ($newSite,$oldSite) {
        $tags = $newSite->tags()->yaml();
        foreach($tags as &$tag) {
          $tag["slugde"] = Str::slug($tag["de"]);
          $tag["slugen"] = Str::slug($tag["en"]);
        }
        $newSite->update([
          'tags' => Data::encode($tags, "yaml")
        ], 'de');
      },
      'page.update:after' => function ($newPage,$oldPage) {
        if ($oldPage->intendedTemplate()->name() === 'project') {
          foreach (kirby()->languages() as $language) {
            $code = $language->code();
            $newPage->update([
              'currentLang' => $code,
              'tagsen' => $code === 'en' ? $newPage->tagsen() : $newPage->content('en')->tagsen(),
              'tagsde' => $code === 'de' ? $newPage->tagsde() : $newPage->content('de')->tagsde(),
              'tags' => $code === 'en' ? $newPage->tagsen() : $newPage->tagsde(),
            ], $code);
          }
        }
      },
      'page.create:after' => function ($page) {
        if ($page->intendedTemplate()->name() === 'project') {
          foreach (kirby()->languages() as $language) {
            $page->update([
                'currentLang' => $code
            ], $code);
          }
        }
      }
    ]
  ];

Is it overblown? And one question to understand it better: What is the exact meaning of the ampersand, something php specific? (I’m more a Designer than a coder, still getting into php and kirby step by step)

It means that you are passing the variable by reference, allows you to modify your variable: https://www.php.net/manual/en/language.references.pass.php