Adding a translatable visibility toggle to all pages

Hello!

What I am trying to achieve is to have some kind of a visibility toggle on all pages without having to add it manually to every blueprint. What I need is to be able to explicitly mark a page as visible or hide independently for each translation of a page without having to only rely on a “does this page have a translation” filter. This is because a client might first translate a page to all languages and then later decide that they want to hide it in a specific language, and there doesn’t seem to be any obvious way to delete a translation of a page through the panel, nor is it super clear in the panel if a page has a translation or not. So is there some nice way to do this or to achieve the same effect some other way?

An alternative that could work for this is if a page’s status could be set as translatable, which would allow a single translation to be marked as unlisted. But I’m guessing that isn’t possible currently.

Thanks for the help!

The easiest would indeed be to add a field to each blueprint.

There is no such thing as custom page statuses, and status refers to the page as a whole, not a translation.

Just an idea to explore: A custon site.page Panel area with such a field

Or maybe a custom dialog for the site area

Thank you Sonja for the reply!

I spent some time trying to extend the site.page view, mainly trying to figure out how to add a button to the k-header inside the k-page-view component. The only way I could see doing it would have been to duplicate the code of k-page-view and make my own modified version which contained the additional components in the header. But I felt it would be too risky because future versions of Kirby might make changes the the k-page-view component, so I decided against it. It would be great if there was some way to inject additional components into the page without having to duplicate all the code.

I ended up instead just going the blueprint route, by adding a “visible” boolean field to relevant blueprints and then adding the following route hook to return the error page if a page (or any of its parents) was set to not be visible:

'route:after' => function (Kirby\Http\Route $route, string $path, string $method, mixed $result, bool $final) {
    
    // Return 404 if page has or one of its parents has a "visible" field which is set to false.
    if ($path != "error" && $result != null && 
        (is_subclass_of($result, 'Kirby\Cms\Page') || get_class($result) === 'Kirby\Cms\Page')) {
        if (isVisible($result)) {
            return site()->errorPage();
        }
    }
    return $result;
}
function isVisible($page) {
    if ($page->visible()->exists() && $page->visible()->bool() === false) {
        return site()->errorPage();
    }
    else if ($page->parent()) {
        return isVisible($page->parent());
    }
}

So I got it working, but just wanted to say thanks and to share the solution I ended up with :slight_smile:

EDIT:
After implementing this, I ran into a weird issue where changing the status of a page to listed resulted in the panel redirecting to the JSON representation of the status dialog. After a good while of trial and error I figured out that the problem was that the isVisible method was (being inside config.php) in the global namespace which presumably resulted in it overriding some other method of the same name. Not sure exactly what method it was overriding/interfering with, but after moving the isVisible method into its own file and setting a proper namespace for it, the weird bug was gone. So just a heads up for anyone looking at this post in the future.

One more edit (can’t seem to edit twice?).
Noticed that I had a bit of brain fart with the isVisible implementation (tho it worked),
the proper implementation is this:

function isVisible($page) {
    if ($page->visible()->exists() && $page->visible()->bool() === false) {
        return false;
    }
    else if ($page->parent()) {
        return isVisible($page->parent());
    }
    else {
        return true;
    }
}

And the route:after hook should then check it using

...
if (!isVisible($result)) {
...