 Yeah, good afternoon, everyone, and first of all, I hope you're still up for some technical talk, maybe deeply technical talk after all this day has already been passed. So I'm going to talk to you, speak to you about the capabilities API in WordPress today. And it's going to be, yeah, it's going to be development focused, and I have two main target audiences with this session, which is, first of all, if you are, imagine you think of you are a plugin developer, and you are working on a plugin that you want to maybe, you want to publish it to a wider audience, maybe to WordPress.org, or maybe you want to sell it. That is one, then you're one audience, and then the other part is, if you are a developer working on client projects, we will also get there how you can customize plugins that are developed in a proper way, which I hopefully will get to you know more about. So yeah, as I said before, this talk will be relatively technical. The capabilities API foundation itself is very simple, but to truly leverage its power, we need to know a little bit more about how it works at its roots. So if there's something where it gets like over your head, because I also have to go through it very quickly, please contact me later or have questions afterwards. So first of all, definitions. What are capabilities? Capabilities in WordPress describe tasks that a user may or may not be allowed to perform. So for example, you would ask, can a user edit this post? Is the user allowed to activate a plug-in, everything like that? All these actions, you need to check whether the user actually has permissions. And then we have roles, which are a more like visually represented concept, because they are exposed in the admin interface. We probably all are familiar with the different roles that WordPress provides out of the box, the administrator, editor, author, contributor, subscriber. And all these roles basically are a set of capabilities, and they define, yeah, by giving a user a certain role, you indirectly give that user a set of capabilities, which is determined by this role. So let's look at just some examples of WordPress capabilities. There's a read capability, which is just a very basic capability to, for example, whether a user can read post, which probably everyone can read existing, read public posts. We have edit posts, for example, that would mean, are you allowed to edit posts in WordPress, upload files? Like if you read on, and even with all the other ones that there are, there are tons of them, they are all pretty self-explanatory. However, it's not only WordPress that has capabilities. Any plugin, possibly even themes, but probably plugins for the most part can have its own capabilities. So let's think about an example where we want to make a very simple plugin that introduces a tutorial post type, and we would want to have dedicated capabilities for this plugin. We could, for example, name them as in this example, like let's imagine we call the plugin capability tutorials, so we prefix everything with CT. So capabilities for reading tutorials could be read CT tutorials or editing or managing options of this plugin could be called manage CT options, you could get the gist. So why are capabilities important? There are three very important factors that have to get into account here. First of all, security, you should always check whether the current user should have access to a certain functionality, otherwise, yeah, it's just, it's a huge security, it can be a huge security issue otherwise, and then the other part is usability. Lots of times WordPress by default exposes like tons of things in the admin menu, for example, that your users won't actually ever need. So in this case, it's bad UX. You should only expose to what the user wants to see and wants to use regularly. And customizability is the third part of it. So even if your plugin has a very small scope, and you would think, I don't need any role management, I don't need any capability management, other custom projects might think differently of it, so you should always leverage capabilities to enable other people to customize your plugin to their custom requirements. So as a good example of that would be, for example, WordPress always shows you the area, the appearance menu, it always shows you a way to switch themes, but it also shows you a way to edit widgets, edit menus. But think about a use case where you want the user to not be able to switch themes, but only to edit menus. In that case, you would need to use capabilities to disable the switch themes part, but only show them edit menu part, and you've already made the user experience a little better, and also adjusted the permissions in general, because you don't want the user to submit themes for a reason. Let's look at some of the internals. First of all, roles are internally stored in a database table for options. It's stored as a serialized array, which I will not talk about anymore. So the capabilities that are granted for each role are also part of the array. So it will look like this, something like this, if I went by default, so you would have the administrator and all that role's capabilities, and all the other roles like editor, and here at the bottom you see subscriber contributor. And something very important as you look at these roles is that roles in WordPress make you think that roles are a hierarchical structure. But they're actually not, because when you look at these, when you look at the capabilities granted here, you see that a contributor just simply also has the read capability, which makes us think that the contributor generally has everything that the subscriber has, but that role also has more capabilities. But this is, it makes sense to set it up that way, but I just want you to be aware that you don't need to set it up that way. You could also have certain roles that, to some degree, can do something that another role can do, but also at the same time, not, you don't need to have all the capabilities that the other one has. Then for the user, for the roles that a user actually has, those are stored in a user meta database table. It's also as a serialized array. It's very simple by default. It usually is only one array entry, which simply is a role and a true to granted. But something you might notice now is it is an array. So an array can store multiple values, so we could have multiple roles per user. This is built into WordPress. It's not exposed via UI, because it's only a select with a single choice, but you may be aware that there are plugins to enable that, and it's really simple, because it's already supported by the database schema or by the database setup. You could even theoretically put capabilities into that array directly. That can be, maybe it would be needed in some custom setups, but it's usually a bad idea because you should always tie capabilities to roles, otherwise it will get pretty messy very quickly. So for now, that's all about roles. We want to focus on capabilities, and usually as a plugin developer, you don't need to worry about roles. Always check for capabilities when you want to see if a user has permissions. Don't check the user's role directly. Then the next part is never add new capabilities to the database unless you introduce an entirely new role. Roles are stored in the database like I said before, so if you have a custom role, you need to put it into the database with all the capabilities it has, but don't modify existing core roles, for example, in the database. You can grant additional capabilities to core roles in another way, which we will get to very soon. And then very important, my main takeaway for today should be use custom capabilities wherever possible. Don't just use manage options for your options screen. Rather use something that is custom to your plugin. For example, in our example from before, it could be manage CT options. Even if it doesn't bring you any benefit at this point, it can bring someone else benefits in order, for example, if they want to give a user only access to your plugin screen, but not the WordPress setting screens. So checking capabilities, that is a very simple part of this API. It's the function current user can, and a capability is essentially a string. It's nothing more than that. So you pass a string that could be manage options, it could be read posts or edit posts or whatsoever, and you get a boolean value back telling you the user has access or the user does not have access. There's also a little less used function user can, which is essentially the same, but it does not check it for the current user, but some arbitrary user where you can pass an ID, but otherwise it's the same thing. So capabilities are, as mentioned before, they're either core capabilities or they're custom. And if they're custom, you need to take care of how they should be handled. And that now becomes the tricky part. So there are two types of capabilities. There are primitive capabilities and meta capabilities. Primitive capabilities are the general capabilities which usually consist of verb and plural string, like edit posts, activate plugins, install plugins, whatsoever. They are either granted via a role from the database, so they are part of that role's array in the database, or you can also grant them dynamically via the user has cap filter, which we will also look at in a bit. And if you were a plugin adding capabilities to an existing role, you should always use the user has cap filter. Then we also have meta capabilities, which are capabilities specific to a certain item. And those usually consist of a verb too, but then it's a singular string, a singular noun. For example, before we added posts, this is some meta capability, which is more specific, it's added post singular. And as you can see, you can pass in the post ID into current user can. That is because current user can actually supports any arbitrary arguments, but it's a common practice should be to pass the context here. So, for example, if you want to check if someone can add a post, you should pass the post ID. If you want to check if someone can activate a specific plugin, specify that at this plugin's base name, which is like the filename slug. So, what happens if we have meta capabilities? Those always need to be mapped to a primitive, to one or more primitive capabilities. So, in the most basic example, it could be with the activate plugin, for example. In WordPress Core, this is simply mapped to activate plug-ins. Activate plug-ins then is a primitive capability that is part of the database. And when you check activate plug-in with a certain plug-in, you just get a true result back if the user has activate plug-ins. Seems kind of unnecessary here, but it is not, because by checking this instead of checking activate plug-ins directly, you get the possibility, you give other developers the possibility to tweak it, to really find unit. For example, there may be some rare use case where a user should not be able to activate one specific plug-in, or the other way around, you only want a user to activate one plug-in, or whatever. So, mapping, using meta capabilities is a very powerful tool, and even if your default implementation is to map them in a very simple way, you use meta capabilities in favor of the primitive capabilities, where it makes sense. Edit posts here is a more complicated example, where in core it can map to several different capabilities. For example, in a very simple case, it would map to edit posts, but if the post that we pass in here is, for example, a private post, which is not publicly available, then you also need to have the capability edit private posts, or if it's a published post, you also have the capability to edit a published post, or if it's by another user than you are, you also need to have the capability to edit other people's posts. Then there are last but not least here, we have two very special capabilities. It's not really capability in that case, but these two things allow specific handling, for example, exist is a capability that everybody by definition has. And contrary, do not allow is a capability that nobody has, even not even super admins, which is the higher level of administrators when you use multi-site. That can be useful if, do not allow, can be useful when in a multi-site, for example, you want to take away a capability that a user actually has in the database. Exists, I can't think of a use case where this is currently used, but there may be cases where you just want the capabilities that you give to everyone. But be careful with this, don't put, for example, don't map managed options to exist. That would be a bad idea. As we've seen before, just a quick reminder of the naming conventions. These should always consist of an verb, an action, and a plural string for primitive capabilities, and then the same thing, but with a singular string, a singular noun of the same word, usually for the respective meta capabilities. So now we're going to look at the flow. What happens if you call current user can, how does it check for capabilities? So after we call this function with any parameter, it internally calls mapMetaCAP, which is the function responsible to map, to check if the current check is for a meta capability, and if so, map it to its required primitive capabilities. For that, it runs a filter, mapMetaCAP, that you as a plugin developer can hook into and tweak how your meta capability is mapped, is resolved into primitive capabilities. After that, after you have the array of primitive capabilities required, there's another, the actual capabilities that the user has are fetched from the database, and they are then run through the user has cap filter, so you can alter dynamically which primitive capabilities the user has. And this is the filter that you would use if you want to say, let's say the administrator also should have my edit CT tutorials capability. Don't put it into the database for the administrator role, but use this filter and add this capability to the array. And after that, the user, the user's primitive capability that come out of this apply filters call, they now need to include all capabilities that come out of the apply filters from the mapMetaCAP call. So if you have each of these capabilities, you can proceed. If just one of them is not present, you cannot proceed. So you don't have the required capabilities. So how do we actually use this in plugins? I set up a, yeah, like I said, this example from before I implemented this. It's a very simple plugin that adds a tutorial post type and a settings screen with some options to customize the behavior of the post type. That's not a very user-friendly idea, but I'm a developer, so yeah. So this is, you can customize what the post type works like, but the idea, the purpose of this plugin is to show you an example of how you can use capabilities. So what do we do here? Again, current user can checks for capabilities and a good example here is also the functions for adding a menu page and add submenu page because those require to pass in a capability by default, which here I use manageCTOptions to add it to access the plugins settings screen. You could, again, you could use manage options, but that prevents other users from fine-tuning access to your screen. You would always say, you can always, if you use manage options, which core uses for all its settings screens, you can then only say, if you want someone to have access to that screen, that person will also have access to all this core settings screens, which might not be what you want. So we could become even more granular here. A good example for metacapability checks is settings. The individual settings of a settings page where we use a settings API for could look like this. Let's say, this is for the screen I showed before. It has a setting for the rewrite slug, for what the post type should support, whether the post type has an archive. But by default, that means all these settings are accessible when you just can access the basic screen itself. You could be really granular and add controls, add app capability checks for each of these settings. This is, I admittedly, a very extreme example, but this would allow really fine-tuned control about this screen. By checking each individual setting, you allow other developers to say, let's, for example, I want a user to only access the third setting in this list, or for example. So after we've added all these capability checks, when we want to access this page, we see this. Why do we see this? We use a custom capability. We have not, but we haven't taken care of actually granting it to anybody. So we need to grant the capability. The managed CT options is a primitive capability, so we need to use the user has cap filter in order to grant it. So here, the first array is the capabilities that the user actually has from in the database. Then the second array is the capabilities that are currently checked. Usually, you would only need to use the first parameter because you generally want to grant that primitive capabilities based on the capabilities that the user already has. If that somehow includes expensive logic, though, maybe you should also check if the capabilities even currently needed by detecting whether it's part of the second parameter, the caps area. Because this filter runs on every capability check of every capability, and if you have expensive logic there, yeah, it should not happen then. It should not happen if it's not needed. So how we could do that here is, in our example, let's just say with our very simple implementation, we just grant managed CT options whenever a user has the regular managed options. Again, for us now, it does not help at all. It doesn't help us at all because we could have just used managed options in the first place, but another developer of a custom project could unhook this filter and implement their own handling of managed CT options, and that allows us granular access to the setting screen. Then let's access the screen now. It looks like this. Why? Because we also added individual capability checks for each of the settings, which we haven't taken care of granting. So because it's single settings, and as you can see before, we always passed in the slug of the setting to the check managed CT option, Zingular. We now, those are meta capabilities, and we need to map them to the required primitive capabilities. Using the map meta cap filter, we can do that. And here, we also apply it in a very simple way. If the current capability checked is managed CT options, Zingular, we just say you need to have managed CT options. Again, it's a very simple solution, but a developer of a custom project could tweak that again, either remove this filter entirely or they could add further capabilities that are required to have for this meta capability check. And after we've done this, we've added this code bit, we can access the screen. Yeah, so we wrote a little additional code of what we would currently write. What's the benefit of all that? Like I said, security, usability, and customizability. And now we will have a brief look so at how this could be used to customize access if you are in a custom project that wants to use this plugin. So for example, let's imagine you are writing a custom project using this CT tutorials plugin and you want to add a designated role to manage all the tutorial stuff. In that case, you could introduce a role. You should introduce roles when activating a plugin because they are part of the database and they're written into the database. So that should absolutely not happen on every request or something like that. So we would add our role tutorial manager, grant all our custom capabilities to that role, and after that, we would remove the filter that the plugin added by default to map managed CT options to manage options because we don't want that anymore. From now on, we only want people to have that role to have access. That would be one example to tweak it. I also have an example for adjusting a meta capability. For example, let's say that rewrite slug setting, we only wanted to be editable by a network administrator when there is a multisite setup. In that case, we can add our own meta cap filter and if that capability is checked, if that capability is currently checked, we would want to check if the past argument, which could be CT rewrite slug, if it's CT rewrite slug and we are in a multisite, let's say we should also require managed network options, which is the capability that the network administrator has in order to manage network options. And at this point, the first setting can only be accessed and managed by the network administrator. So the screen would look like this. The first setting is just gone and we have tweaked access to it in a very fine-tuned way. Just a few notes about multisite because those are a few things to have in mind. The network administrator, which I've mentioned before, it appears to be an additional role in WordPress. It's technically only a flag, so it's not, it isn't really a role, it doesn't have any capabilities. It's just a flag that says this user has all capabilities. The only way to get around this is to say do not allow. So that is a very good use case of the do not allow special value. If there's something where you don't even want a network administrator to have access to, which can happen in certain cases, yeah, use do not allow. That being said, even though the network administrator is a flag, and there is a function to check that flag with its super admin, it has been determined that this is a bad practice and we should always check against capabilities, which as the similar as we shouldn't check against user roles in regular roles, we also shouldn't check against this flag. There's still occurrences of its super admin in core, but we are trying to eliminate this and maybe at some point, hopefully at some point, we'll even be able to migrate the network administrator to an actual role-based system for a multi-site network to it so that it's not just a simple flag anymore. Here's just a brief list of the current core capabilities that are used for multi-site. Be aware that none of these capabilities are actually somewhere in the database just because the network administrator is not a role as mentioned before. These capabilities are used in the code and you can use it in your code as well. Again, preferably use your own, but you could use them and those capabilities are the network administrator capabilities by default. We have a few differences when using multi-site versus single-site. Oftentimes it happens that in multi-site, there are capabilities that only a network administrator have, but in a single site, the administrator should have them. So because single-site is the default, these capabilities are always just granted to administrators, so in multi-site, we have to artificially take them away from them. So the way we can do that is either by this code snippet, we could, installing plug-ins is a good example of this because in a multi-site only network administrators can install plugins, but in a regular site, regular site administrators can also install plugins. So we could do this by checking if install plugin is called and if we're in a multi-site and the current user is not a super admin, we use do not allow. Again, this is just an example, you don't need to do this check because it's already in core. But this is, as it says above, not so great. Why? Because we check a super admin and as I just mentioned, we should rather check against capabilities. So a better way to do this exact same thing is to simply say if we're in multi-site, also require managed network plugins. That means because only the user super admin or network administrator has this capability, it essentially will do the same thing, but we check against capabilities solely as we should. And that's it for now. There are a couple of more things that you might want to know about capabilities, especially if something very important is how you use custom capabilities for registering custom post types or custom taxonomies. This was a little beyond the scope of this session, but it's also part of this plugin. So if you're interested in that, you can look at the plugins code base. It's available on GitHub. And that's it for me for now. Before we dive into questions, I just want to open up a short list of other resources that you may be interested in if you're interested in learning more about capabilities in WordPress, but that's it. Fantastic, Felix, thank you. There's a lot to take in there. I'm really pleased, Felix, how you drew attention to thinking about how others might want to use the code that you write. I found it really interesting that you said you couldn't imagine how people might want to do it, but you still do it. Yeah. And I think that's part of the joy of working in WordPress when developers just go that extra stretch to say, I don't know what you might want to do with this, but I'm very open to you doing it, and that's exactly what has made this such a strong project. Do we have any questions? I don't believe we do. No? Yes, we do, we do. Again, it's the light. Sorry. Hello. Hello. I'm Wartosz of Poland. I wanted to ask one question about the things you've been talking about, because a lot of, you've been talking about fine-grained control over what we are doing, and I agree about that, that if we build something custom, like a plugin or something else, we do have fine-grained control. I wanted to ask about your opinion. If you don't you have a feeling that we don't have fine-grained control over the native WordPress elements? That is very good, and fair question. We partly have, but to a big extent, we also don't. It's actually the second point here addresses that. So there are many places in core where core itself doesn't use capabilities as granular as I pointed out we should, and yeah, it's a step getting to that point. Well, my personal worst thing is like you can, there's no capability to edit menus or to edit widgets. You always have to use some other thing, which is a pain, oftentimes. But there are efforts to fix that in all these different areas, and yeah, the second bullet point here also has a link to all these tickets that we've been working on or will be working on in the future. But I agree, this is a problem at this point in some times. We have a question down here in the middle. Look at that, whoa, careful. It wasn't actually a question, I was waving at my colleague, who just entered the room. And I couldn't think of something, but thank you for a great talk. Anyone else? I think we might be done. Are we? I think we're clear. Felix, thank you very much. Felix Hans, ladies and gentlemen. Thank you. And Felix, if anyone does want to ask you any detailed questions you were on for the rest of the event. Oh yeah, absolutely. Approach me today, tomorrow, I'm here. Fantastic, thank you.