Language Change PR-6.2-dev Pending

User tests: Successful: Unsuccessful:

avatar CSGoat0
CSGoat0
27 Aug 2026

Pull Request resolves #46097.

  • I read the Generative AI policy and my contribution is either not created with the help of AI or is compatible with the policy and GNU/GPL 2 or later.

Summary of Changes

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:

  • Adds 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.
  • Adds 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.
  • Registers both new files in administrator/components/com_categories/categories.xml so they're installed/updated correctly.
  • Updates 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."
  • Updates the com_categories.ini file to add the COM_CATEGORIES_CONFIGURATION

With 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.


image

Testing Instructions

Before applying this Pull Request:

  1. Create a test user in the Author user group.
  2. Create a frontend menu item for Create Article.
  3. Set the Category parameter to No (so the user can choose/create a category).
  4. Log in as the test user on the frontend.
  5. Go to the Create Article page.
  6. In the Category field, type a new category name (e.g. "On-the-Fly Test Category") and press Enter.
  7. Complete the article and save.
  8. Observe: the new category is created, even though there is no way to have prevented this — com_categories has no ACL to configure.

After applying this Pull Request:

  1. Apply the patch and clear the cache.
  2. Go to System → Global Configuration → Categories.
  3. Under the Permissions tab, set Create to Denied for the Author group. Save.
  4. Repeat steps 4–7 above as the same Author test user.
  5. Observe: the on-the-fly category is not created (article save either blocks the category-creation step or falls back to an existing category, per ArticleModel::save()'s handling of a failed canCreateCategory() check).
  6. As a control, set Create back to Allowed (or Inherited) for Author and repeat — on-the-fly category creation should work again, confirming normal behavior is unaffected when the permission is granted.

Actual result BEFORE applying this Pull Request

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.

Expected result AFTER applying this Pull Request

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.

Additional Notes

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.

Link to documentations

Please select:

  • Documentation link for guide.joomla.org:
  • No documentation changes for guide.joomla.org needed
  • Pull Request link for manual.joomla.org:
  • No documentation changes for manual.joomla.org needed
avatar CSGoat0 CSGoat0 - open - 27 Aug 2026
avatar CSGoat0 CSGoat0 - change - 27 Aug 2026
Status New Pending
avatar joomla-cms-bot joomla-cms-bot - change - 27 Aug 2026
Category Administration com_categories com_content
avatar CSGoat0 CSGoat0 - change - 27 Aug 2026
Labels Added: PR-6.2-dev
avatar CSGoat0 CSGoat0 - change - 27 Aug 2026
Title
Add missing ACL for com_categories
[6.2] Add missing ACL for com_categories
avatar CSGoat0 CSGoat0 - edited - 27 Aug 2026
avatar joomla-cms-bot joomla-cms-bot - change - 27 Aug 2026
Category Administration com_categories com_content Administration com_categories com_content Language & Strings
avatar brianteeman
brianteeman - comment - 28 Aug 2026

Updates ArticleModel::canCreateCategory()

dont you need to do that for all core components that use com_categories?

avatar CSGoat0 CSGoat0 - change - 28 Aug 2026
Labels Added: Language Change
avatar joomla-cms-bot joomla-cms-bot - change - 28 Aug 2026
Category Administration com_categories com_content Language & Strings Administration com_banners com_categories com_contact com_content com_newsfeeds Language & Strings
avatar CSGoat0 CSGoat0 - change - 28 Aug 2026
The description was changed
avatar CSGoat0 CSGoat0 - edited - 28 Aug 2026
avatar CSGoat0
CSGoat0 - comment - 28 Aug 2026

Done, thanks @brianteeman!

avatar joomdonation
joomdonation - comment - 29 Aug 2026

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:

  • If these categories inherit permissions from com_categories (which makes sense if we allow define permissions for com_categories), then the behavior is changed and I'm unsure people will happy with that. Currently, top level categories of each component is inherited from that component permissions. Making this change also requires other code changes, and data migration, too
  • If these categories still inherit permissions from the component it belongs to, then the new permissions defined is useless. The only permission uses right now is create permission to check create categories on fly (but then why it is not used for check create new category permission...)
avatar CSGoat0
CSGoat0 - comment - 30 Aug 2026

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.

avatar bembelimen
bembelimen - comment - 30 Aug 2026

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.

avatar brianteeman
brianteeman - comment - 30 Aug 2026

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

avatar joomdonation
joomdonation - comment - 30 Aug 2026

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.

avatar joomdonation
joomdonation - comment - 30 Aug 2026

@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.

avatar CSGoat0
CSGoat0 - comment - 30 Aug 2026

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:

  1. If a specific parent category is already known (the user picked one), check that parent's own asset node directly, e.g. authorise('core.create', 'com_content.category.15'). This is the existing, already-correct per-category mechanism, unchanged.
  2. If there is no parent yet (a brand new top-level category, nothing to inherit from), fall back to a dedicated per-component node under 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).

avatar joomdonation
joomdonation - comment - 30 Aug 2026

Thanks @CSGoat0. So how the categories node for each extension like com_categories.content, com_categories.banners presented and how admin can change permissions for these nodes?

avatar CSGoat0
CSGoat0 - comment - 30 Aug 2026

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?

avatar joomdonation
joomdonation - comment - 30 Aug 2026

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)

Add a Comment

Login with GitHub to post a comment