User tests: Successful: Unsuccessful:
Pull Request resolves #46097.
com_categories had no component-level ACL, unlike every other core component. In practice this meant there was no way to prevent a user from creating brand-new categories "on the fly" while creating content (e.g. from the com_content article form), as long as that user had core.create for the host component (com_content). Restricting article-creation permission had no effect on category creation, and there was no separate toggle to deny it.
The same pattern (canCreateCategory() checking the host component's own core.create instead of com_categories) also existed in com_banners, com_contact, and com_newsfeeds. All three are updated alongside com_content to check com_categories instead.
This PR:
administrator/components/com_categories/access.xml, declaring core.admin, core.create, core.delete, core.edit, core.edit.state, and core.edit.own at the component level.administrator/components/com_categories/config.xml with a permissions fieldset (type="rules"), which is what actually makes a Permissions tab render for the component — access.xml alone only declares the actions, it doesn't expose anywhere in the UI to configure them.administrator/components/com_categories/categories.xml so they're installed/updated correctly.ArticleModel::canCreateCategory() in administrator/components/com_content/src/Model/ArticleModel.php to check core.create against com_categories instead of com_content. Previously this method checked the same permission that already grants article creation, so there was no way to decouple "can create articles" from "can invent new categories."com_categories.ini file to add the COM_CATEGORIES_CONFIGURATIONWith this change, site administrators can go to System → Global Configuration → Categories and explicitly deny "Create" for a given user group, independently of that group's article-creation rights.
Before applying this Pull Request:
com_categories has no ACL to configure.After applying this Pull Request:
ArticleModel::save()'s handling of a failed canCreateCategory() check).Any user with core.create on com_content (or any other component using categories) can create new categories on the fly, with no ACL available anywhere to restrict this independently of their content-creation rights.
com_categories exposes a component-level Permissions screen (System → Global Configuration → Categories), and denying core.create there blocks on-the-fly category creation for that group, without affecting their ability to create content.
Explicit testing instructions for (Banners, Contacts, News Feeds) aren't included above, but testers are welcome to also try creating a category on the fly from their respective fields to confirm the fix holds there too.
Please select:
| Status | New | ⇒ | Pending |
| Category | ⇒ | Administration com_categories com_content |
| Labels |
Added:
PR-6.2-dev
|
||
| Title |
|
||||||
| Category | Administration com_categories com_content | ⇒ | Administration com_categories com_content Language & Strings |
| Labels |
Added:
Language Change
|
||
| Category | Administration com_categories com_content Language & Strings | ⇒ | Administration com_banners com_categories com_contact com_content com_newsfeeds Language & Strings |
Done, thanks @brianteeman!
The main question for now (and difficult to answer) is with permissions for com_categories defined like this, how people want permissions of top level categories of each component inherit from:
Thanks @joomdonation for the review.
Since only core.create is actually read by any code path right now (via canCreateCategory()), I plan to trim access.xml down to just that (plus core.admin, needed for the Permissions tab itself to render) if that's the preferred direction. Happy to push that change once we've settled on scope.
I looked closer into how category permissions actually work yesterday (the #__assets tree, per-category-node inheritance via com_content.category.<id> and the Category Manager's own "New" button already checks that per-node permission independently of this PR). I agree there's a real, separate piece of work here: making com_categories an inherited node in that tree, and refactor it with the per-category-node mechanism that already exists, rather than the flat, standalone core.create check this PR adds. I'd like to take that on properly, after doing more research to confirm it's applicable, if the maintainers are open to the direction.
Separately, I found that the existing per-category ACL might already fix this issue without any code change. Article creation (and the equivalent in Banners, Contacts, Newsfeeds) checks core.create on the specific category node when one is known, falling back to the flat component-level check otherwise. Denying core.create at the component level and granting it back per-category could also block on-the-fly creation as a side effect. I haven't tested this end-to-end and I suspect it could has real downsides. Just sharing it as context.
Open to discussion on which direction makes the most sense here. Happy to adjust this PR further or start a separate issue for the tree-inheritance work, whichever you'd prefer.
I think, as com_categories is a vertical extension, the permission should not be standalone there but always in the context of the component. So I find it more important, that the category permission is in-layered into the component assets.
That means, it must be possible to set the "create category" permission for each component individual. Perhaps we need there new permissions because e.g. core.edit is currently wrongly inherit from com_categories => com_content articles.
I have no clean solution to offer but that are thoughts I have.
As I was the permission who made the original report the specific use case that I wanted to address is that I didnt want some users to be able to dynamically create categories when they were creating content. So maybe there is a different way completely to address that scenario
Yes, ideally, we need to have new permissions for categories, for fields, for workflow in each component which supports these vertical extensions.
The problem is that these permissions will need new names (like category.create, category.edit, category.edit.state....) and with these new names, I guess the permissions inheritance as designed in Joomla won't work properly anymore (Global -> Component ->Parent Asset (like Categories) -> Item (haven't read all related code but I guess for permission inheritance to work, permissions need to use same name across these items). We would also need to update permission of category to use new name, not core.create, core.edit..., maybe data migration as well, which is not easy.
@brianteeman But I think that still does not prevent them to access to Categories management and create categories from there if they have create permission for that component.
I think there's a middle-ground design that addresses both without requiring the full asset-tree restructuring.
The rule
For any "can I create a category" check, frontend or backend:
authorise('core.create', 'com_content.category.15'). This is the existing, already-correct per-category mechanism, unchanged.com_categories (e.g. com_categories.content, com_categories.banners, ...) instead of the current flat, shared com_content/com_banners/etc. component check.This mirrors the pattern ArticleController::allowAdd() already uses for articles (check the category node if known, fall back to the component only if not), just applied to category creation itself, with the fallback target changed from the ambiguous shared component node to a dedicated one.
Frontend (the original bug)
On-the-fly creation is always top-level with no parent chosen, so it always hits case 2. This is exactly canCreateCategory() checking com_categories.content as already proposed.
Backend
CategoryController::allowAdd() currently ignores which parent the user actually selected:
protected function allowAdd($data = [])
{
$user = $this->app->getIdentity();
return $user->authorise('core.create', $this->extension)
|| \count($user->getAuthorisedCategories($this->extension, 'core.create'));
}It would need a small change to start using $data['parent_id'], the same way ArticleController::allowAdd() already uses $data['catid']:
protected function allowAdd($data = [])
{
$user = $this->app->getIdentity();
$parentId = ArrayHelper::getValue($data, 'parent_id', 0, 'int');
if ($parentId) {
return $user->authorise('core.create', $this->extension . '.category.' . $parentId);
}
return $user->authorise('core.create', 'com_categories.content');
}(Same change, mirrored, for Banners/Contacts/Newsfeeds' category controllers.)
With this, picking a real parent still goes through the existing, correct per-node inheritance. Creating a top-level category goes through the same dedicated per-component toggle regardless of whether it happens inline while writing an article or directly in the Category Manager, so the backend bypassing is closed as well.
What this does and does not do
This directly answers the two concerns raised so far: it is scoped per hosting component (@bembelimen), and it closes both the frontend and backend paths (@joomdonation), without reparenting any existing category asset or migrating existing permission data. It is not the same as making com_categories a real ancestor in the tree that existing categories dynamically inherit through. It only introduces a small, fixed set of dedicated leaf nodes to serve as the fallback for the "no parent yet" case specifically. I think that is a reasonable trade-off given the migration risk the fuller approach would carry.
So, what do you think?
(I've been thinking of this for a while and discussed the solution with Claude AI and it likes it and helped me writing this proposal).
access.xml should look like this:
<?xml version="1.0" encoding="UTF-8"?>
<access component="com_categories">
<section name="content">
<action name="core.create" title="JACTION_CREATE" />
</section>
<section name="banners">
<action name="core.create" title="JACTION_CREATE" />
</section>
<section name="contact">
<action name="core.create" title="JACTION_CREATE" />
</section>
<section name="newsfeeds">
<action name="core.create" title="JACTION_CREATE" />
</section>
</access>config.xml should look like this:
<?xml version="1.0" encoding="UTF-8"?>
<config>
<fieldset
name="permissions"
label="JCONFIG_PERMISSIONS_LABEL"
description="JCONFIG_PERMISSIONS_DESC"
>
<field
name="rules_content"
type="rules"
label="JGLOBAL_ARTICLES"
validate="rules"
filter="rules"
component="com_categories"
section="content"
/>
<field
name="rules_banners"
type="rules"
label="JGLOBAL_BANNERS"
validate="rules"
filter="rules"
component="com_categories"
section="banners"
/>
<field
name="rules_contact"
type="rules"
label="JGLOBAL_CONTACTS"
validate="rules"
filter="rules"
component="com_categories"
section="contact"
/>
<field
name="rules_newsfeeds"
type="rules"
label="JGLOBAL_NEWSFEEDS"
validate="rules"
filter="rules"
component="com_categories"
section="newsfeeds"
/>
</fieldset>
</config>On navigating to System → Global Configuration → Categories → Permissions, the admin gets four separate rule grids on that one page, each labeled by hosting component (Articles/Banners/Contacts/News Feeds), each independently settable per user group exactly the same rendering mechanism com_content's own access.xml already uses to show separate grids for its category/article/fieldgroup/workflow sections.
So, is this what you mean?
Need to see it in action to see if that works. Also, assume we come up with a solution, there need to be a way for third party extensions to use that, too (from what I see, you are hard code the support for core components in config.xml)
dont you need to do that for all core components that use com_categories?