Programmable blueprints

Hello,

I looked at the “Programmable blueprints” page:

<?php

use Kirby\Cms\App as Kirby;

Kirby::plugin('cookbook/programmable-blueprints', [
    'blueprints' => [
        'site' => function () {
            if (($user = kirby()->user()) && $user->isAdmin()) {
                return Data::read(__DIR__ . '/blueprints/site.admin.yml');
            } else {
                return Data::read(__DIR__ . '/blueprints/site.editor.yml');
            }
        },
    ]
]);

And in the following example I wanted to adapt the code to have 4 different types of users.

How to adapt the code to more than 2 types of users?

Hello,

You can do $user->name() and $user->username(), see the Docs.

You could also do $user->role() if your users are define by extra roles, or use any of $user methods that fits your case.

With that you can add more if/else clauses to that block, to match each of your users.

Of course your users must exist.

Does this help ?

Hi plagasul,

I tried something like this but I got an error on the login page:

<?php

use Kirby\Cms\App as Kirby;

Kirby::plugin('cookbook/programmable-blueprints', [
    'blueprints' => [
        'site' => function () {
            if (($user = kirby()->user()) && $user->isAdmin()) {
                return Data::read(__DIR__ . '/blueprints/site.admin.yml');
            }
            else if(($user = kirby()->user()->role()) === 'editor'){
                return Data::read(__DIR__ . '/blueprints/site.editor.yml');
            }
            else if(($user = kirby()->user()->role()) === 'editor_stories'){
                return Data::read(__DIR__ . '/blueprints/site.editor_stories.yml');
            }
            else if(($user = kirby()->user()->role()) === 'circuits'){
                return Data::read(__DIR__ . '/blueprints/site.circuits.yml');
            }
        },
    ]
]);

Error: /kirby/config/areas/site.php

<?php
 
use Kirby\Toolkit\I18n;
 
return function ($kirby) {
    return [
        'breadcrumbLabel' => function () use ($kirby) {
            return $kirby->site()->title()->or(I18n::translate('view.site'))->toString();
        },
        'icon'      => 'home',
        'label'     => $kirby->site()->blueprint()->title() ?? I18n::translate('view.site'),
        'menu'      => true,
        'dialogs'   => require __DIR__ . '/site/dialogs.php',
        'dropdowns' => require __DIR__ . '/site/dropdowns.php',
        'searches'  => require __DIR__ . '/site/searches.php',
        'views'     => require __DIR__ . '/site/views.php',
    ];
};

the problematic line is: 'label' => $kirby->site()->blueprint()->title() ?? I18n::translate('view.site'),

If I add an else it works but it always falls into the else.

  1. in your elseifs, you don’t check for an existing user
  2. role returns a role object, not a string, so your comparision will fail
  3. you have to return a default if your ifs fail

I fixed like this and everything works.

Is this a good solution?

<?php

use Kirby\Cms\App as Kirby;

Kirby::plugin('cookbook/programmable-blueprints', [
    'blueprints' => [
        'site' => function () {

            // Si l'utilisateur est deconnecté
            if(!kirby()->user()){
                return Data::read(__DIR__ . '/blueprints/site.circuits.yml');
            };

            // Si l'utilisateur est connecté
            if (($user = kirby()->user()) && $user->isAdmin()) {
                return Data::read(__DIR__ . '/blueprints/site.admin.yml');
            }
            else if(($user = kirby()->user()->role()->name() === 'editor')){
                return Data::read(__DIR__ . '/blueprints/site.editor.yml');
            }
            else if(($user = kirby()->user()->role()->name()) === 'editor_stories'){
                return Data::read(__DIR__ . '/blueprints/site.editor_stories.yml');
            }
            else if(($user = kirby()->user()->role()->name()) === 'circuits'){
                return Data::read(__DIR__ . '/blueprints/site.circuits.yml');
            }
            // else {
            //     return Data::read(__DIR__ . '/blueprints/site.circuits.yml');
            // }
        },
    ]
]);

That is a bit repetitive, and can be shortened to:

Kirby::plugin('cookbook/programmable-blueprints', [
    'blueprints' => [
        'site' => function () {
            $user = kirby()->user();
            // Si l'utilisateur est deconnecté
            if(! $user || ! in_array($user->role()->name(), ['admin', 'editor', 'editor_stories'])) {
                return Data::read(__DIR__ . '/blueprints/site.circuits.yml');
            }

            // Si l'utilisateur est connecté
            return Data::read(__DIR__ . '/blueprints/site.' . $user->role()->name() . '.yml');
       
 
        },
    ]
]);

(since writing without editor, I might have missed some parenthesis…)

1 Like

Thanks, is perfect!

@texnixe dear sonja, could it be that this does not work for user blueprints? i have a project where it works perfectly for pages, for this new project i needed it for users and the panel crashes somehow. can you please confirm this?

@texnixe sonja, if you have time to look into my question above I would really appreciate it. right now i am just hiding fields with css in the panel :see_no_evil:

Could you please provide your code for testing (either here or via a download link), then I will look into it tonight when back home.

sure. i have two cases. i want to show some fields of the editormaterial user depending on who is logged in, an admin, or everyone else. by doing this, only the admin can set the field “canaccess” (i use it for the bouncer plugin):

editormaterial.admin.yml

title: Material Editor
description: Material Editors can edit downloads and ressources

permissions:
  access:
    system: false
    users: false
    languages: false
    trevor: false
    retour: false
  users: false
  user:
    delete: false
    changeRole: false
    changeEmail: false
    changePassword: false

fields:
  canaccess:
    label: 'The user will only be able to access:'
    type: pages
    multiple: true
    options: query
    query: site.pages

editormaterial.user.yml

title: Material Editor
description: Material Editors can edit downloads and ressources

permissions:
  access:
    system: false
    users: false
    languages: false
    trevor: false
    retour: false
  users: false
  user:
    delete: false
    changeRole: false
    changeEmail: false
    changePassword: false

then, in my plugin for the blueprints i do (just to be sure i wrapped in an extra if the $user check):

Kirby::plugin('squareclouds/smartblueprints', [
    'blueprints' => [
        'users/editormaterial' => function () {
            $user = kirby()->user();

            if ($user) {
                if ($user->isAdmin()) {
                    return Data::read(kirby()->root() . '/site/blueprints/users/editormaterial.admin.yml');
                } else {
                    return Data::read(kirby()->root() . '/site/blueprints/users/editormaterial.user.yml');
                }
            }
        },
    ]
]);

but then the system just gives me

Fatal error : Allowed memory size of 134217728 bytes exhausted (tried to allocate 262144 bytes) in /Users/santiagoduque/Documents/WEBPROJECTS/mamp/pb-website/www/kirby/src/Cms/App.php on line 1313

Fatal error : Allowed memory size of 134217728 bytes exhausted (tried to allocate 262144 bytes) in Unknown on line 0

Hm, somehow this setup is weird. An admin user with the role admin would need a blueprint called admin. You cannot have a user role editormaterial who is an admin user at the same time. Guess you need to rethink how you name your roles.

@texnixe the role admin exists of course. i think yes, it is a misunderstanding due to maybe an unlucky naming. i named it “editormaterial.admin” because that is the blueprint an admin would see while checking the user page of an editormaterial. i hope that clarifies that. the only difference in the blueprints as you can see, is that one has a field that should be visible when an admin visits the user page, and when the editormaterial him/herself checks his/her page the field should not be visible.

so the use case is: an admin has some extra settings when he manages that type of user.

right now i am just hidding the field via CSS, which might not be the safest thing to do.

div[data-role="editormaterial"] .k-field-name-canaccess{
    display: none;
}

Ok, sorry, guess I was too tired. But yes, calling $kirby->user() in this context seems to run into some loop and therefore does not work.

Edit:

I played around with this a bit and it looks as if you could work around this issue by defining an option in your plugin, e.g. userRole, and set this to null by default. Then within the ready config option:

   'ready' => function() {
	    return [
		    'squareclouds.smartblueprints.userRole' => kirby()->user()?->isAdmin() ? 'admin' : 'editor'
	    ];
    },

In your plugin:

<?php
Kirby::plugin('squareclouds/smartblueprints', [
	'options' => [
		'userRole' => null,
	],
	'blueprints' => [
		'users/editormaterial' => function($kirby) {
			return Data::read(__DIR__ . '/editormaterial.' . (option('squareclouds.smartblueprints.userRole') ?? 'editor') . '.yml');
		}
	]
]);

Note that the blueprint itself needs the name property, with the role name in lowercase.

Tested with 4.2.0

oh thank you! i would have never thought of something like that. i am getting an error though. a yaml is getting read but the Role class expects a name somehow, which is not present.

Bildschirmfoto 2024-05-11 um 18.25.36

if i kirbylog the $props param i get this

[2024-05-11 16:31:44] INFO {
    "title": "Material Editor",
    "description": "Material Editors can edit downloads and ressources",
    "permissions": {
        "access": {
            "system": false,
            "users": false,
            "languages": false,
            "trevor": false,
            "retour": false
        },
        "users": false,
        "user": {
            "delete": false,
            "changeRole": false,
            "changeEmail": false,
            "changePassword": false
        }
    }
}

and technically i am logging in with the admin account, so the other yaml should be loading, the one with the pages field. this one has only permissions

i am also kirbylogging the user data in the config, but the role() is not returning anything somehow. i can log name, email, etc, but not role()

'ready' => function() {
        kirbylog(kirby()->user()->role()); #this is not returning anything, and hence the wrong yaml is being loaded
        return [          
          'squareclouds.smartblueprints.userRole' => kirby()->user()?->isAdmin() ? 'admin' : 'editor'
        ];
      },

i am also using kirby 4.2 and php 8.2 :confused: besides kirbylog i also removed all other plugins. i will now test with the starterkit

hmm no somehow in the starterkit it is the same. the Role class expects a name in the $props

btw you are calling

kirby()->user()?->isAdmin()

with a ?. is that correct? i have tried both though.

also, will i be able to create users even if we manage to make it work? i am getting skeptical if this is even a viable approach. otherwise, what would you suggest for my use case? so the admin has to have some extra user-settings/user-field in the panel when managing the other users.

---------------BIG EDIT---------------

@texnixe dear sonja, i have solved it with the whenQuery plugin.

i changed my field to this

fields:
  canaccess:
    label: 'The user will only be able to access:'
    type: pages
    multiple: true
    options: query
    query: site.pages
    whenQuery: _account = false

from it’s documentation:

The accessible User properties are:

  • _id: the id of the user
  • _role: the role name of the user (e.g. “Admin” with a capital “A”) (somehow checking if Admin did not work)
  • _avatar: the url of the user’s avatar
  • _account: boolean indicating if this is the current user’s account ← so i used this

since the users can only see their own profile, and all admins should be able to see the field, i only had to hide the field in case the user is checking his/her own profile.

technically a lot less complicated, but if i have more user roles that should also not be able to see the field, it could get tricky since _role did not work as i mentioned above (will now create an issue in the plugin repo)

in case you give the blueprints a try and you know why it is not working on my side, please let me know just as a learning exercise

That’s what I mentioned above, your blueprints have a title but no name

title: Material Editor
name: editormaterial

#...

oh sorry, did not see that part! do the blueprints usually need the name property? i have never set it in any project

Not the ones in the site/blueprints folder, it seems, but the ones registered in a plugin. BTW. you register the blueprints from the site folder, which won’t work either. The blueprints need to be in the plugin folder-