From 280946ad30c4e0ad95203d1c496dc25b04802203 Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Wed, 9 Apr 2025 12:50:21 +0300 Subject: [PATCH 1/6] Add search vocabs --- eea/volto/policy/overrides.zcml | 16 ++++++------- eea/volto/policy/vocabularies/principals.py | 25 ++++++++++++++++----- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/eea/volto/policy/overrides.zcml b/eea/volto/policy/overrides.zcml index da7e5889..bd623f80 100644 --- a/eea/volto/policy/overrides.zcml +++ b/eea/volto/policy/overrides.zcml @@ -3,18 +3,18 @@ xmlns:zcml="http://namespaces.zope.org/zcml" xmlns:browser="http://namespaces.zope.org/browser"> - + - - - + + + + /> diff --git a/eea/volto/policy/vocabularies/principals.py b/eea/volto/policy/vocabularies/principals.py index 8d2fe1b3..b5a93b64 100644 --- a/eea/volto/policy/vocabularies/principals.py +++ b/eea/volto/policy/vocabularies/principals.py @@ -1,23 +1,26 @@ -""" Vocabulary for users. -""" +"""Vocabulary for users.""" + from plone import api from plone.app.vocabularies.principals import UsersFactory as BaseUsersFactory from plone.app.vocabularies.principals import PrincipalsVocabulary from zope.schema.vocabulary import SimpleTerm from zope.component.hooks import getSite from Products.CMFCore.utils import getToolByName +from zope.globalrequest import getRequest class UsersFactory(BaseUsersFactory): - """ Factory creating a UsersVocabulary - """ + """Factory creating a UsersVocabulary""" + + _needs_search = False + @property def items(self): """Return a list of users""" if not self.should_search(query=""): return acl_users = getToolByName(getSite(), "acl_users") - userids = set(u.get('id') for u in acl_users.searchUsers()) + userids = set(u.get("id") for u in acl_users.searchUsers()) for userid in userids: user = api.user.get(userid) if not user: @@ -27,7 +30,19 @@ def items(self): continue yield SimpleTerm(userid, userid, fullname) + def should_search(self, query): + if self._needs_search: + return True + + return super().should_search(query) + def __call__(self, *args, **kwargs): + request = getRequest() + tokens = request.form.get("tokens", None) + if tokens: + self._needs_search = True + return super().__call__(*args, **kwargs) + vocabulary = PrincipalsVocabulary(list(self.items)) vocabulary.principal_source = self.source return vocabulary From 8d684fd855df6bec7650be0cb4d14952ba04d570 Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Wed, 9 Apr 2025 13:49:53 +0300 Subject: [PATCH 2/6] Bring in override to allow fullname of users in token lookup --- eea/volto/policy/vocabularies/principals.py | 95 ++++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/eea/volto/policy/vocabularies/principals.py b/eea/volto/policy/vocabularies/principals.py index b5a93b64..0827dd13 100644 --- a/eea/volto/policy/vocabularies/principals.py +++ b/eea/volto/policy/vocabularies/principals.py @@ -2,12 +2,56 @@ from plone import api from plone.app.vocabularies.principals import UsersFactory as BaseUsersFactory -from plone.app.vocabularies.principals import PrincipalsVocabulary +from plone.app.vocabularies.principals import ( + PrincipalsVocabulary, + SOURCES, + _get_acl_users, + # merge_principal_infos, + token_from_principal_info, +) from zope.schema.vocabulary import SimpleTerm from zope.component.hooks import getSite from Products.CMFCore.utils import getToolByName from zope.globalrequest import getRequest +_USER_SEARCH_UID = { + "search": "searchUsers", + # Hint: The fullname search is provided i.e. in IUserEnumeration of + # the property plugin in PlonePAS. + "searchattr": "uid", + "searchargs": {"sort_by": "fullname"}, + "many": "plone.many_users", +} + +SOURCES["user"]["searches"] += [_USER_SEARCH_UID] +SOURCES["principal"]["searches"] += [_USER_SEARCH_UID] + + +def merge_principal_infos(infos, acl_users, prefix=False): + info = infos[0] + if len(infos) > 1: + principal_types = { + info["principal_type"] for info in infos if info["principal_type"] + } + if len(principal_types) > 1: + # Principals with the same ID but different types. Should not + # happen. + raise ValueError("Principal ID not unique: {}".format(info["id"])) + if not info["title"]: + for candidate in infos: + if candidate["title"]: + info["title"] = candidate["title"] + break + + if ( + info.get("pluginid", "") == "pasldap" + and info.get("principal_type", "") == "user" + and info.get("title", None) == info.get("id", "") + ): + user = acl_users.getUserById(info["id"]) + info["title"] = user.getProperty("fullname") + return info + class UsersFactory(BaseUsersFactory): """Factory creating a UsersVocabulary""" @@ -41,8 +85,55 @@ def __call__(self, *args, **kwargs): tokens = request.form.get("tokens", None) if tokens: self._needs_search = True - return super().__call__(*args, **kwargs) + return self.original_tweaked__call__(*args, query=tokens) vocabulary = PrincipalsVocabulary(list(self.items)) vocabulary.principal_source = self.source return vocabulary + + def original_tweaked__call__(self, context, query=""): + if not self.should_search(query): + vocabulary = PrincipalsVocabulary([]) + vocabulary.principal_source = self.source + return vocabulary + + acl_users = _get_acl_users() + cfg = SOURCES[self.source] + + def term_triples(): + """Generator for term triples (value, token, name)""" + for search_cfg in cfg["searches"]: + search = getattr(acl_users, search_cfg["search"]) + searchargs = search_cfg["searchargs"].copy() + searchargs[search_cfg["searchattr"]] = query + infotree = {} + for info in search(**searchargs): + infotree.setdefault(info["id"], {}).setdefault( + info["principal_type"], [] + ).append(info) + + for principal_id, types_infos in infotree.items(): + if len(types_infos) > 1 and not cfg["prefix"]: + raise ValueError( + f"Principal ID not unique: {principal_id}") + for principal_type, principal_infos in types_infos.items(): + if principal_type == "user": + pass + value = principal_id + info = merge_principal_infos( + principal_infos, acl_users) + if cfg["prefix"]: + value = "{}:{}".format( + info["principal_type"], value) + token = token_from_principal_info( + info, prefix=cfg["prefix"]) + yield (value, token, info["title"]) + + vocabulary = PrincipalsVocabulary( + [ + SimpleTerm(*term_triple) + for term_triple in filter(self.use_principal_triple, term_triples()) + ] + ) + vocabulary.principal_source = self.source + return vocabulary From 1bff4caad0adf5e34c575a5ccc05677a755fabb1 Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Mon, 14 Apr 2025 15:30:12 +0300 Subject: [PATCH 3/6] Use eea volto policy layer for breadcrumbs adapter --- eea/volto/policy/restapi/services/breadcrumbs/get.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eea/volto/policy/restapi/services/breadcrumbs/get.py b/eea/volto/policy/restapi/services/breadcrumbs/get.py index 268e6941..1ddfe7ac 100644 --- a/eea/volto/policy/restapi/services/breadcrumbs/get.py +++ b/eea/volto/policy/restapi/services/breadcrumbs/get.py @@ -6,10 +6,11 @@ from zope.interface import Interface from plone.restapi.interfaces import IExpandableElement, IPloneRestapiLayer from plone.restapi.services.breadcrumbs.get import Breadcrumbs +from eea.volto.policy.interfaces import IEeaVoltoPolicyLayer @implementer(IExpandableElement) -@adapter(Interface, IPloneRestapiLayer) +@adapter(Interface, IEeaVoltoPolicyLayer) class EEABreadcrumbs(Breadcrumbs): """EEA Breadcrumbs""" @@ -40,6 +41,5 @@ def __call__(self, expand=False): items.append(item) result["breadcrumbs"]["items"] = items - result["breadcrumbs"]["root"] = portal_state.navigation_root()\ - .absolute_url() + result["breadcrumbs"]["root"] = portal_state.navigation_root().absolute_url() return result From 44d70356c29484656ecbaa93ff0b1e374efdb697 Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Tue, 27 May 2025 10:32:28 +0300 Subject: [PATCH 4/6] Make sure terms are unique so that we don't crash --- eea/volto/policy/vocabularies/principals.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/eea/volto/policy/vocabularies/principals.py b/eea/volto/policy/vocabularies/principals.py index 0827dd13..b2c53271 100644 --- a/eea/volto/policy/vocabularies/principals.py +++ b/eea/volto/policy/vocabularies/principals.py @@ -53,6 +53,18 @@ def merge_principal_infos(infos, acl_users, prefix=False): return info +def unique_terms(objects): + unique_objects = [] + seen_values = set() + + for obj in objects: + if obj.value not in seen_values: + unique_objects.append(obj) + seen_values.add(obj.value) + + return unique_objects + + class UsersFactory(BaseUsersFactory): """Factory creating a UsersVocabulary""" @@ -129,11 +141,12 @@ def term_triples(): info, prefix=cfg["prefix"]) yield (value, token, info["title"]) - vocabulary = PrincipalsVocabulary( + terms = unique_terms( [ SimpleTerm(*term_triple) for term_triple in filter(self.use_principal_triple, term_triples()) ] ) + vocabulary = PrincipalsVocabulary(terms) vocabulary.principal_source = self.source return vocabulary From 7c360f49cb7c5dcdb659b566afb04d9c8f94b22a Mon Sep 17 00:00:00 2001 From: eea-jenkins Date: Wed, 22 Apr 2026 10:29:25 +0200 Subject: [PATCH 5/6] style: Automated code fix --- eea/volto/policy/vocabularies/principals.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/eea/volto/policy/vocabularies/principals.py b/eea/volto/policy/vocabularies/principals.py index 492d4262..f8304ec1 100644 --- a/eea/volto/policy/vocabularies/principals.py +++ b/eea/volto/policy/vocabularies/principals.py @@ -144,19 +144,15 @@ def term_triples(): for principal_id, types_infos in infotree.items(): if len(types_infos) > 1 and not cfg["prefix"]: - raise ValueError( - f"Principal ID not unique: {principal_id}") + raise ValueError(f"Principal ID not unique: {principal_id}") for principal_type, principal_infos in types_infos.items(): if principal_type == "user": pass value = principal_id - info = merge_principal_infos( - principal_infos, acl_users) + info = merge_principal_infos(principal_infos, acl_users) if cfg["prefix"]: - value = "{}:{}".format( - info["principal_type"], value) - token = token_from_principal_info( - info, prefix=cfg["prefix"]) + value = "{}:{}".format(info["principal_type"], value) + token = token_from_principal_info(info, prefix=cfg["prefix"]) yield (value, token, info["title"]) terms = unique_terms( From 9f58439eef8a0a613e6a444c91902207b529419d Mon Sep 17 00:00:00 2001 From: eea-jenkins Date: Wed, 22 Apr 2026 10:30:39 +0200 Subject: [PATCH 6/6] lint: Automated code fix --- eea/volto/policy/restapi/services/breadcrumbs/get.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eea/volto/policy/restapi/services/breadcrumbs/get.py b/eea/volto/policy/restapi/services/breadcrumbs/get.py index 7fb4f4ed..0faea1e0 100644 --- a/eea/volto/policy/restapi/services/breadcrumbs/get.py +++ b/eea/volto/policy/restapi/services/breadcrumbs/get.py @@ -4,7 +4,7 @@ from zope.component import adapter from zope.interface import implementer from zope.interface import Interface -from plone.restapi.interfaces import IExpandableElement, IPloneRestapiLayer +from plone.restapi.interfaces import IExpandableElement from plone.restapi.services.breadcrumbs.get import Breadcrumbs from eea.volto.policy.interfaces import IEeaVoltoPolicyLayer