How to change page content in `page.update.before` hook?

I’m developing a plugin that adds IDs to structure field entries. However, I have trouble saving the modified data. Here’s my plugin hook:

'hooks' => [
  'page.update:before' => function ($page, $values, $strings) {
    foreach ($values as $key => $field) {
      if (is_array($field)) {
        fillIds($field);

        $page->update([
          $key => yaml::encode($field)
        ]);
      }
    }

    $values['test'] = 'foo1'; // does nothing
    $strings['test'] = 'foo2'; // does nothing

    return [
      'test' => 'foo3' // does nothing
    ];
  }
]

As you can see, I try 4 ways of updating the page. If I call $page->update(), it does nothing, probably because Kirby detects it would cause and endless loop due to the hook? If I change the $values and $strings array, nothing happens (I tried specifying them as references in the arguments but I get errors). Finally, if I return an array, still nothing happens.

How am I supposed to update the page? I could use page.update.after but I think it would be less efficient. Also, I don’t get the $values arguments which is not so convenient.

@texnixe has built a similar plugin for Kirby 2 and it uses the $page->update() method. That does work in page.update.after, but not in page.update.before where I need it.

How do I update the page content in page.update.before?

Before hooks do not let you modify the incoming data. They give you the option to do something before the values are actually saved to disk. Like intervening the save action by throw new Error('Oops') or perhaps clear a cache. In your case you would probably need to use the page.update:after hook and update any structure item that does not have an id yet in there.

2 Likes

You can do it. Edit the content and fire
$page->update($your_edited_content)

In my experience, you even can’t fall into a endless loop with that…

You can do what? Not sure what you are trying to add here?

Nevermind. I’ve tried in page.update.after. So as @hdodov mentioned, it works there…

Hi @texnixe , I have a similar case and I think it is valid in general.

I want to modify field values before they get saved. My application case is that I analyze the contents of layouts and see if a certain block was used that needs a certain css and then store with the page that this page should always include a specific css when rendering (but only when this block type is used). So the field I modify on the page is called ‘usedblocktypes’

I do not want to “save the page twice” with an page.update:after hook. That would be ugly, as it would cost me CPU. Why do two commits() if one is enough?

I am used from other systems (SQL Servers, Dynamics CRM, …) to have hooks that can run before saving a database entry that can manipulate the in-memory entry. I expect that this “just works”, so I wondered how it works with Kirby.

I debugged deeply into the page.update:before hook and found:

```
‘hooks’ => [
‘page.update:before’ => function (Kirby\Cms\Page $page, array $values, array $strings) {
// your code goes here
}
]
```

  • all input variables are passed by value, not by reference. So manipulating $strings or $values won’t change the values going into storage
  • the hook can actually return a maniplated $page entity, this will be parsed. I found no obvious idea how to pass the new input into this.
  • the ModelCommit→call(array $arguments, Closure $callback) is responsible to call the hook, but I found no way how I can pass modified $arguments. I didn’t try manipulating $hook though, maybe that is the answer

… so I found no way to do this using the page.update:before hook.

The stacktrace during debugging showed me that I am within Kirby\Cms\Page→public function update( ?array $input = null, ?string $languageCode = null, bool $validate = false ): static

So I override that update() function on a model in site/models/default.php

```

public function update( ?array $input = null, ?string $languageCode = null, bool $validate = false ): static {
    if ($input) {
       $this->hookPageUpdateBefore($input);
    }
    return parent::update($input, $languageCode, $validate);
}

public function hookPageUpdateBefore(array &$input) {
  $input['usedblocktypes'] = 'whatever';
}

```

What is the “correct Kirby way” ?

The hook doesn’t really make sense here, but overriding the update method does.