From 6dc15b146bb480261a598aeea9d72981556f888f Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Wed, 13 Mar 2024 11:51:08 +0100 Subject: [PATCH] XWIKI-21848: Migrate NotificationFilterPreferenceLivetableResults to a Live Data source * Improve work after Manuel's review * Provide some helpers to factorize code and provide better SoC * Use a dedicated StaticList displayer Co-Authored-By: Manuel Leduc --- ...tNotificationFilterLiveDataEntryStore.java | 149 ++++++ ...cationFilterLiveDataTranslationHelper.java | 23 +- ...mFiltersLiveDataConfigurationProvider.java | 29 +- ...cationCustomFiltersLiveDataEntryStore.java | 450 ++---------------- .../NotificationCustomFiltersQueryHelper.java | 326 +++++++++++++ ...mFiltersLiveDataConfigurationProvider.java | 42 +- ...cationSystemFiltersLiveDataEntryStore.java | 134 ++---- .../main/resources/META-INF/components.txt | 1 + ...ficationsCustomFiltersPreferencesMacro.xml | 1 + ...ficationsSystemFiltersPreferencesMacro.xml | 1 + .../src/components/DisplayerStaticList.vue | 64 +++ .../src/main/vue/src/main.js | 3 +- 12 files changed, 665 insertions(+), 558 deletions(-) create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/AbstractNotificationFilterLiveDataEntryStore.java create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java create mode 100644 xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/components/DisplayerStaticList.vue diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/AbstractNotificationFilterLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/AbstractNotificationFilterLiveDataEntryStore.java new file mode 100644 index 000000000000..499729fec22c --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/AbstractNotificationFilterLiveDataEntryStore.java @@ -0,0 +1,149 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +package org.xwiki.notifications.filters.internal.livedata; + +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import javax.inject.Inject; +import javax.inject.Provider; + +import org.xwiki.livedata.LiveDataEntryStore; +import org.xwiki.livedata.LiveDataException; +import org.xwiki.livedata.LiveDataQuery; +import org.xwiki.model.EntityType; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.model.reference.EntityReference; +import org.xwiki.model.reference.EntityReferenceResolver; +import org.xwiki.model.reference.EntityReferenceSerializer; +import org.xwiki.model.reference.WikiReference; +import org.xwiki.notifications.NotificationFormat; +import org.xwiki.notifications.filters.internal.DefaultNotificationFilterPreference; +import org.xwiki.security.authorization.ContextualAuthorizationManager; +import org.xwiki.security.authorization.Right; + +import com.xpn.xwiki.XWikiContext; + +/** + * Abstract class providing helpers for both + * {@link org.xwiki.notifications.filters.internal.livedata.custom.NotificationCustomFiltersLiveDataEntryStore} and + * {@link org.xwiki.notifications.filters.internal.livedata.system.NotificationSystemFiltersLiveDataEntryStore}. + * + * @version $Id$ + * @since 16.2.0RC1 + */ +public abstract class AbstractNotificationFilterLiveDataEntryStore implements LiveDataEntryStore +{ + private static final String TARGET_SOURCE_PARAMETER = "target"; + private static final String WIKI_SOURCE_PARAMETER = "wiki"; + private static final String UNAUTHORIZED_EXCEPTION_MSG = "You don't have rights to access those information."; + + @Inject + protected NotificationFilterLiveDataTranslationHelper translationHelper; + + @Inject + protected EntityReferenceResolver entityReferenceResolver; + + @Inject + protected EntityReferenceSerializer entityReferenceSerializer; + + @Inject + private Provider contextProvider; + + @Inject + private ContextualAuthorizationManager contextualAuthorizationManager; + + protected static final class TargetInformation + { + public boolean isWikiTarget; + public EntityReference ownerReference; + + } + + protected Map getStaticListInfo(List items) + { + return Map.of( + "extraClass", "list-unstyled", + "items", items + ); + } + + protected Map displayNotificationFormats(Collection notificationFormats) + { + List items = notificationFormats + .stream() + .sorted(Comparator.comparing(NotificationFormat::name)) + .map(notificationFormat -> this.translationHelper.getFormatTranslation(notificationFormat)) + .toList(); + + return getStaticListInfo(items); + } + + protected TargetInformation getTargetInformation(LiveDataQuery query) throws LiveDataException + { + Map sourceParameters = query.getSource().getParameters(); + if (!sourceParameters.containsKey(TARGET_SOURCE_PARAMETER)) { + throw new LiveDataException("The target source parameter is mandatory."); + } + String target = String.valueOf(sourceParameters.get(TARGET_SOURCE_PARAMETER)); + TargetInformation result = new TargetInformation(); + if (WIKI_SOURCE_PARAMETER.equals(target)) { + result.isWikiTarget = true; + result.ownerReference = + this.entityReferenceResolver.resolve(String.valueOf(sourceParameters.get(WIKI_SOURCE_PARAMETER)), + EntityType.WIKI); + } else { + result.isWikiTarget = false; + result.ownerReference = this.entityReferenceResolver.resolve(String.valueOf(sourceParameters.get(target)), + EntityType.DOCUMENT); + } + if (!this.contextualAuthorizationManager.hasAccess(Right.ADMIN) + && !result.ownerReference.equals(getCurrentUserReference())) { + throw new LiveDataException(UNAUTHORIZED_EXCEPTION_MSG); + } + return result; + } + + protected WikiReference getCurrentWikiReference() + { + XWikiContext context = this.contextProvider.get(); + return context.getWikiReference(); + } + + private DocumentReference getCurrentUserReference() + { + XWikiContext context = this.contextProvider.get(); + return context.getUserReference(); + } + + protected void checkAccessFilterPreference(DefaultNotificationFilterPreference filterPreference) + throws LiveDataException + { + if (!this.contextualAuthorizationManager.hasAccess(Right.ADMIN) + && !Objects.equals(filterPreference.getOwner(), + this.entityReferenceSerializer.serialize(getCurrentUserReference()))) { + throw new LiveDataException(UNAUTHORIZED_EXCEPTION_MSG); + } + } +} diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java index b780ee506433..c6f53fa54dd1 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/NotificationFilterLiveDataTranslationHelper.java @@ -26,6 +26,7 @@ import javax.inject.Inject; import javax.inject.Singleton; +import org.slf4j.Logger; import org.xwiki.component.annotation.Component; import org.xwiki.eventstream.EventStreamException; import org.xwiki.eventstream.RecordableEventDescriptor; @@ -35,12 +36,13 @@ import org.xwiki.notifications.NotificationFormat; import org.xwiki.notifications.filters.NotificationFilterType; import org.xwiki.notifications.filters.internal.livedata.custom.NotificationCustomFiltersLiveDataConfigurationProvider; +import org.xwiki.wiki.descriptor.WikiDescriptorManager; /** * Helper for getting various translations for live data custom sources. * * @version $Id$ - * @since 16.2.ORC1 + * @since 16.2.0RC1 */ @Component(roles = NotificationFilterLiveDataTranslationHelper.class) @Singleton @@ -52,6 +54,12 @@ public class NotificationFilterLiveDataTranslationHelper @Inject private RecordableEventDescriptorManager recordableEventDescriptorManager; + @Inject + private WikiDescriptorManager wikiDescriptorManager; + + @Inject + private Logger logger; + private String getTranslationWithFallback(String translationKey) { String translationPlain = this.contextualLocalizationManager.getTranslationPlain(translationKey); @@ -104,15 +112,16 @@ public String getAllEventTypesTranslation() * @return the plain text event type description translation * @throws LiveDataException if the event type descriptor cannot be found */ - public String getEventTypeTranslation(String eventType) throws LiveDataException + public String getEventTypeTranslation(String eventType) { try { RecordableEventDescriptor descriptor = this.recordableEventDescriptorManager.getDescriptorForEventType(eventType, true); return getTranslationWithFallback(descriptor.getDescription()); } catch (EventStreamException e) { - throw new LiveDataException( - String.format("Error while getting description for event type [%s]", eventType), e); + this.logger.error("Error while getting description for event type [{}] falling back on event name", + eventType, e); + return eventType; } } @@ -127,16 +136,16 @@ public String getFormatTranslation(NotificationFormat format) /** * Get event type information from all descriptor to populate a select. - * @param allFarm {@code true} if the event type should be retrieved for the whole farm. * @return a list of maps containing two information: {@code value} holding the event type, and {@code label} * holding a translation of the description * @throws LiveDataException in case of problem to load the descriptors */ - public List> getAllEventTypesOptions(boolean allFarm) throws LiveDataException + public List> getAllEventTypesOptions() throws LiveDataException { try { + boolean isMainWiki = this.wikiDescriptorManager.isMainWiki(this.wikiDescriptorManager.getCurrentWikiId()); List recordableEventDescriptors = - this.recordableEventDescriptorManager.getRecordableEventDescriptors(allFarm); + this.recordableEventDescriptorManager.getRecordableEventDescriptors(isMainWiki); return recordableEventDescriptors.stream().map(descriptor -> Map.of( "value", descriptor.getEventType(), "label", getTranslationWithFallback(descriptor.getDescription()) diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java index 55248bd079a1..232c630f7dfe 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataConfigurationProvider.java @@ -22,7 +22,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import java.util.stream.Stream; import javax.inject.Inject; @@ -69,8 +68,11 @@ public class NotificationCustomFiltersLiveDataConfigurationProvider implements P static final String DOC_HAS_DELETE_FIELD = "doc_hasdelete"; private static final String TRANSLATION_PREFIX = "notifications.settings.filters.preferences.custom.table."; private static final String DELETE = "delete"; + // FIXME: We should define those constants in LiveData module private static final String STRING_TYPE = "String"; private static final String HTML_DISPLAYER = "html"; + private static final String BOOLEAN = "boolean"; + private static final String STATIC_LIST_DISPLAYER = "staticList"; private static final String VALUE_KEY = "value"; private static final String LABEL_KEY = "label"; @@ -148,7 +150,8 @@ public LiveDataConfiguration get() deleteAction.setId(DELETE); deleteAction.setAllowProperty(DOC_HAS_DELETE_FIELD); try { - deleteAction.setIcon(this.iconManager.getMetaData(DELETE)); + // FIXME: we should map delete action icon to the cross.. + deleteAction.setIcon(this.iconManager.getMetaData("cross")); } catch (IconException e) { this.logger.error("Error while getting icon for the remove action", e); } @@ -175,6 +178,7 @@ private LiveDataPropertyDescriptor getDisplayDescriptor() descriptor.setId(DISPLAY_FIELD); descriptor.setType(STRING_TYPE); descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER)); + // This is not visible because we don't want to keep that property and it's aiming at being removed. descriptor.setVisible(false); descriptor.setEditable(false); descriptor.setSortable(false); @@ -187,9 +191,9 @@ private LiveDataPropertyDescriptor.FilterDescriptor createFilterList(List Map.of( VALUE_KEY, item.name(), LABEL_KEY, this.translationHelper.getScopeTranslation(item) - )).collect(Collectors.toList()))); + )).toList())); descriptor.setVisible(true); descriptor.setEditable(false); descriptor.setSortable(true); @@ -240,7 +244,7 @@ private LiveDataPropertyDescriptor getFilterTypeDescriptor() .map(item -> Map.of( VALUE_KEY, item.name(), LABEL_KEY, this.translationHelper.getFilterTypeTranslation(item) - )).collect(Collectors.toList()))); + )).toList())); descriptor.setVisible(true); descriptor.setEditable(false); descriptor.setSortable(true); @@ -255,12 +259,12 @@ private LiveDataPropertyDescriptor getNotificationFormatsDescriptor() descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + NOTIFICATION_FORMATS_FIELD)); descriptor.setId(NOTIFICATION_FORMATS_FIELD); descriptor.setType(STRING_TYPE); - descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER)); descriptor.setFilter(createFilterList(Stream.of(NotificationFormat.values()).map(item -> Map.of( VALUE_KEY, item.name(), LABEL_KEY, this.translationHelper.getFormatTranslation(item) - )).collect(Collectors.toList()))); + )).toList())); + descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(STATIC_LIST_DISPLAYER)); descriptor.setVisible(true); descriptor.setEditable(false); descriptor.setSortable(true); @@ -275,14 +279,13 @@ private LiveDataPropertyDescriptor getEventTypesDescriptor() descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + EVENT_TYPES_FIELD)); descriptor.setId(EVENT_TYPES_FIELD); descriptor.setType(STRING_TYPE); - descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER)); + descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(STATIC_LIST_DISPLAYER)); List> options = new ArrayList<>(); options.add(Map.of( VALUE_KEY, ALL_EVENTS_OPTION_VALUE, LABEL_KEY, this.translationHelper.getAllEventTypesTranslation())); try { - // FIXME: all farm shouldn't always be true - options.addAll(this.translationHelper.getAllEventTypesOptions(true)); + options.addAll(this.translationHelper.getAllEventTypesOptions()); } catch (LiveDataException e) { this.logger.error("Cannot provide event filter options", e); } @@ -300,10 +303,8 @@ private LiveDataPropertyDescriptor getIsEnabledDescriptor() LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + IS_ENABLED_FIELD)); descriptor.setId(IS_ENABLED_FIELD); - descriptor.setType("Boolean"); - LiveDataPropertyDescriptor.FilterDescriptor filterBoolean = - new LiveDataPropertyDescriptor.FilterDescriptor("boolean"); - descriptor.setFilter(filterBoolean); + descriptor.setType(BOOLEAN); + descriptor.setFilter(new LiveDataPropertyDescriptor.FilterDescriptor(BOOLEAN)); descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("toggle")); descriptor.setVisible(true); descriptor.setEditable(false); diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java index 6c79d37f3fa8..9031d7bd8fad 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersLiveDataEntryStore.java @@ -19,18 +19,14 @@ */ package org.xwiki.notifications.filters.internal.livedata.custom; -import java.util.ArrayList; import java.util.Comparator; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import javax.inject.Inject; import javax.inject.Named; -import javax.inject.Provider; import javax.inject.Singleton; import javax.script.ScriptContext; @@ -43,33 +39,22 @@ import org.xwiki.livedata.LiveDataQuery; import org.xwiki.model.EntityType; import org.xwiki.model.reference.EntityReference; -import org.xwiki.model.reference.EntityReferenceResolver; -import org.xwiki.model.reference.EntityReferenceSerializer; import org.xwiki.model.reference.WikiReference; import org.xwiki.notifications.NotificationException; -import org.xwiki.notifications.NotificationFormat; import org.xwiki.notifications.filters.NotificationFilter; import org.xwiki.notifications.filters.NotificationFilterManager; import org.xwiki.notifications.filters.NotificationFilterPreference; -import org.xwiki.notifications.filters.NotificationFilterType; import org.xwiki.notifications.filters.internal.DefaultNotificationFilterPreference; import org.xwiki.notifications.filters.internal.NotificationFilterPreferenceStore; -import org.xwiki.notifications.filters.internal.livedata.NotificationFilterLiveDataTranslationHelper; -import org.xwiki.query.Query; +import org.xwiki.notifications.filters.internal.livedata.AbstractNotificationFilterLiveDataEntryStore; import org.xwiki.query.QueryException; -import org.xwiki.query.QueryManager; -import org.xwiki.query.internal.DefaultQueryParameter; import org.xwiki.rendering.block.Block; import org.xwiki.rendering.renderer.BlockRenderer; import org.xwiki.rendering.renderer.printer.DefaultWikiPrinter; import org.xwiki.rendering.renderer.printer.WikiPrinter; import org.xwiki.script.ScriptContextManager; -import org.xwiki.security.authorization.ContextualAuthorizationManager; -import org.xwiki.security.authorization.Right; import org.xwiki.template.TemplateManager; -import com.xpn.xwiki.XWikiContext; - /** * Dedicated {@link LiveDataEntryStore} for the {@link NotificationCustomFiltersLiveDataSource}. * This component is in charge of performing the actual HQL queries to display the live data. @@ -79,33 +64,17 @@ @Component @Singleton @Named(NotificationCustomFiltersLiveDataSource.NAME) -public class NotificationCustomFiltersLiveDataEntryStore implements LiveDataEntryStore +public class NotificationCustomFiltersLiveDataEntryStore extends AbstractNotificationFilterLiveDataEntryStore { private static final String WIKI = "wiki"; - private static final String STARTS_WITH_OPERATOR = "startsWith"; - private static final String CONTAINS_OPERATOR = "contains"; - private static final String EQUALS_OPERATOR = "equals"; private static final String LOCATION_TEMPLATE = "notification/filters/livedatalocation.vm"; - private static final String TARGET_SOURCE_PARAMETER = "target"; - private static final String WIKI_SOURCE_PARAMETER = WIKI; - private static final String UNAUTHORIZED_EXCEPTION_MSG = "You don't have rights to access those information."; - private static final String AND = " and "; @Inject private NotificationFilterPreferenceStore notificationFilterPreferenceStore; - @Inject - private Provider contextProvider; - @Inject private NotificationFilterManager notificationFilterManager; - @Inject - private EntityReferenceResolver entityReferenceResolver; - - @Inject - private EntityReferenceSerializer entityReferenceSerializer; - @Inject @Named("context") private ComponentManager componentManager; @@ -114,12 +83,6 @@ public class NotificationCustomFiltersLiveDataEntryStore implements LiveDataEntr @Named("html/5.0") private BlockRenderer blockRenderer; - @Inject - private QueryManager queryManager; - - @Inject - private NotificationFilterLiveDataTranslationHelper translationHelper; - @Inject private TemplateManager templateManager; @@ -127,28 +90,21 @@ public class NotificationCustomFiltersLiveDataEntryStore implements LiveDataEntr private ScriptContextManager scriptContextManager; @Inject - private ContextualAuthorizationManager contextualAuthorizationManager; + private NotificationCustomFiltersQueryHelper queryHelper; @Override public Optional> get(Object entryId) throws LiveDataException { Optional> result = Optional.empty(); - XWikiContext context = contextProvider.get(); - WikiReference wikiReference = context.getWikiReference(); Optional filterPreferenceOpt; try { - filterPreferenceOpt = notificationFilterPreferenceStore - .getFilterPreference(String.valueOf(entryId), wikiReference); + filterPreferenceOpt = this.notificationFilterPreferenceStore + .getFilterPreference(String.valueOf(entryId), getCurrentWikiReference()); if (filterPreferenceOpt.isPresent()) { DefaultNotificationFilterPreference filterPreference = (DefaultNotificationFilterPreference) filterPreferenceOpt.get(); - if (this.contextualAuthorizationManager.hasAccess(Right.ADMIN) - || filterPreference.getOwner().equals( - this.entityReferenceSerializer.serialize(context.getUserReference()))) { - result = Optional.of(getPreferenceInformation(filterPreference)); - } else { - throw new LiveDataException(UNAUTHORIZED_EXCEPTION_MSG); - } + checkAccessFilterPreference(filterPreference); + result = Optional.of(getPreferenceInformation(filterPreference)); } } catch (NotificationException e) { throw new LiveDataException( @@ -170,7 +126,7 @@ private Map getPreferenceInformation(NotificationFilterPreferenc result.put(NotificationCustomFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD, this.displayEventTypes(filterPreference)); result.put(NotificationCustomFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD, - displayNotificationFormats(filterPreference)); + displayNotificationFormats(filterPreference.getNotificationFormats())); result.put(NotificationCustomFiltersLiveDataConfigurationProvider.SCOPE_FIELD, getScopeInfo(scope)); result.put(NotificationCustomFiltersLiveDataConfigurationProvider.LOCATION_FIELD, this.displayLocation(filterPreference, scope)); @@ -178,9 +134,8 @@ private Map getPreferenceInformation(NotificationFilterPreferenc this.renderDisplay(filterPreference)); result.put(NotificationCustomFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD, this.translationHelper.getFilterTypeTranslation(filterPreference.getFilterType())); - result.put(NotificationCustomFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD, - displayIsEnabled(filterPreference)); result.put("isEnabled_checked", filterPreference.isEnabled()); + result.put("isEnabled_disabled", filterPreference.getId().startsWith("watchlist_")); result.put("isEnabled_data", Map.of( "preferenceId", filterPreference.getId() )); @@ -189,107 +144,46 @@ private Map getPreferenceInformation(NotificationFilterPreferenc return result; } - private String displayIsEnabled(NotificationFilterPreference filterPreference) - { - String checked = (filterPreference.isEnabled()) ? "checked=\"checked\"" : ""; - String disabled = (filterPreference.getId().startsWith("watchlist_")) ? "disabled=\"disabled\"" : ""; - String html = ""; - return String.format(html, filterPreference.getId(), checked, disabled); - } - private String displayLocation(NotificationFilterPreference filterPreference, NotificationCustomFiltersLiveDataConfigurationProvider.Scope scope) { - EntityReference location; - switch (scope) { - case USER: - location = this.entityReferenceResolver.resolve(filterPreference.getUser(), EntityType.DOCUMENT); - break; - - case WIKI: - location = this.entityReferenceResolver.resolve(filterPreference.getWiki(), EntityType.WIKI); - break; - - case SPACE: - location = this.entityReferenceResolver.resolve(filterPreference.getPage(), EntityType.SPACE); - break; - - default: - case PAGE: - location = this.entityReferenceResolver.resolve(filterPreference.getPageOnly(), EntityType.DOCUMENT); - break; - } - // FIXME: Do we need a new execution context? + EntityReference location = switch (scope) { + case USER -> this.entityReferenceResolver.resolve(filterPreference.getUser(), EntityType.DOCUMENT); + case WIKI -> this.entityReferenceResolver.resolve(filterPreference.getWiki(), EntityType.WIKI); + case SPACE -> this.entityReferenceResolver.resolve(filterPreference.getPage(), EntityType.SPACE); + case PAGE -> + this.entityReferenceResolver.resolve(filterPreference.getPageOnly(), EntityType.DOCUMENT); + }; + // TODO: Create an improvment ticket for having a displayer ScriptContext currentScriptContext = this.scriptContextManager.getCurrentScriptContext(); currentScriptContext.setAttribute("location", location, ScriptContext.ENGINE_SCOPE); return this.templateManager.renderNoException(LOCATION_TEMPLATE); } - private String getUnstyledList(List items) + private Map displayEventTypes(NotificationFilterPreference filterPreference) { - StringBuilder result = new StringBuilder("
    "); - for (String item : items) { - result.append("
  • "); - result.append(item); - result.append("
  • "); - } - result.append("
"); - return result.toString(); - } - - private String displayEventTypes(NotificationFilterPreference filterPreference) throws LiveDataException - { - String result; - List eventTypes = new ArrayList<>(filterPreference.getEventTypes()); - // Ensure to always have same order - eventTypes.sort(Comparator.naturalOrder()); - if (eventTypes.isEmpty()) { - result = getUnstyledList(List.of(this.translationHelper.getAllEventTypesTranslation())); + List items; + if (filterPreference.getEventTypes().isEmpty()) { + items = List.of(this.translationHelper.getAllEventTypesTranslation()); } else { - List items = new ArrayList<>(); - for (String eventType : eventTypes) { - items.add(this.translationHelper.getEventTypeTranslation(eventType)); - } - result = getUnstyledList(items); + items = filterPreference.getEventTypes() + .stream() + .sorted(Comparator.naturalOrder()) + .map(eventType -> this.translationHelper.getEventTypeTranslation(eventType)) + .toList(); } - return result; - } - private String displayNotificationFormats(NotificationFilterPreference filterPreference) - { - List items = new ArrayList<>(); - List notificationFormats = new ArrayList<>(filterPreference.getNotificationFormats()); - // Ensure to always have same order - notificationFormats.sort(Comparator.comparing(NotificationFormat::name)); - for (NotificationFormat notificationFormat : notificationFormats) { - items.add(this.translationHelper.getFormatTranslation(notificationFormat)); - } - - return getUnstyledList(items); + return getStaticListInfo(items); } private Map getScopeInfo(NotificationCustomFiltersLiveDataConfigurationProvider.Scope scope) { - String icon; - switch (scope) { - case USER: - icon = "user"; - break; - - case WIKI: - icon = WIKI; - break; - - case SPACE: - icon = "chart-organisation"; - break; - - default: - case PAGE: - icon = "page"; - break; - } + String icon = switch (scope) { + case USER -> "user"; + case WIKI -> WIKI; + case SPACE -> "chart-organisation"; + case PAGE -> "page"; + }; return Map.of("icon", icon, "name", this.translationHelper.getScopeTranslation(scope)); } @@ -330,285 +224,25 @@ private NotificationCustomFiltersLiveDataConfigurationProvider.Scope getScope(No } } - private static final class FiltersHQLQuery - { - private String whereClause; - private final Map bindings = new LinkedHashMap<>(); - } - - private Optional handleFilter(List queryFilters) - { - FiltersHQLQuery result = new FiltersHQLQuery(); - List queryWhereClauses = new ArrayList<>(); - for (LiveDataQuery.Filter queryFilter : queryFilters) { - switch (queryFilter.getProperty()) { - case NotificationCustomFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD -> - this.handleIsEnabledFilter(queryFilter, queryWhereClauses); - - case NotificationCustomFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD -> - this.handleNotificationFormatsFilter(queryFilter, queryWhereClauses); - - case NotificationCustomFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> - this.handleScopeFilter(queryFilter, queryWhereClauses); - - case NotificationCustomFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD -> - this.handleFilterTypeFilter(queryFilter, queryWhereClauses, result); - - case NotificationCustomFiltersLiveDataConfigurationProvider.LOCATION_FIELD -> - this.handleLocationFilter(queryFilter, queryWhereClauses, result); - - case NotificationCustomFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD -> - this.handleEventTypeFilter(queryFilter, queryWhereClauses, result); - - default -> { - } - } - } - if (!queryWhereClauses.isEmpty()) { - StringBuilder stringBuilder = new StringBuilder(AND); - Iterator iterator = queryWhereClauses.iterator(); - while (iterator.hasNext()) { - stringBuilder.append(iterator.next()); - if (iterator.hasNext()) { - stringBuilder.append(AND); - } - } - result.whereClause = stringBuilder.toString(); - return Optional.of(result); - } else { - return Optional.empty(); - } - } - - private void handleFilterTypeFilter(LiveDataQuery.Filter queryFilter, List queryWhereClauses, - FiltersHQLQuery result) - { - // We authorize only a single constraint here - LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); - if (EQUALS_OPERATOR.equals(constraint.getOperator())) { - queryWhereClauses.add("nfp.filterType = :filterType"); - result.bindings.put("filterType", - NotificationFilterType.valueOf(String.valueOf(constraint.getValue()))); - } - } - - private void handleScopeFilter(LiveDataQuery.Filter queryFilter, List queryWhereClauses) - { - // We authorize only a single constraint here - LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); - if (EQUALS_OPERATOR.equals(constraint.getOperator()) - && !StringUtils.isBlank(String.valueOf(constraint.getValue()))) { - NotificationCustomFiltersLiveDataConfigurationProvider.Scope scope = - NotificationCustomFiltersLiveDataConfigurationProvider.Scope.valueOf( - String.valueOf(constraint.getValue())); - queryWhereClauses.add(String.format("length(nfp.%s) > 0 ", scope.getFieldName())); - } - } - - private void handleNotificationFormatsFilter(LiveDataQuery.Filter queryFilter, List queryWhereClauses) - { - // We authorize only a single constraint here - LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); - String constraintValue = String.valueOf(constraint.getValue()); - if (EQUALS_OPERATOR.equals(constraint.getOperator()) - && !StringUtils.isEmpty(constraintValue)) { - if (NotificationFormat.ALERT.name().equals(constraintValue)) { - queryWhereClauses.add("nfp.alertEnabled = 1"); - } - if (NotificationFormat.EMAIL.name().equals(constraintValue)) { - queryWhereClauses.add("nfp.emailEnabled = 1"); - } - } - } - - private void handleIsEnabledFilter(LiveDataQuery.Filter queryFilter, List queryWhereClauses) - { - // We only check first constraint: if there's more they are either redundant or contradictory. - LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); - if (Boolean.parseBoolean(String.valueOf(constraint.getValue()))) { - queryWhereClauses.add("nfp.enabled = 1"); - } else { - queryWhereClauses.add("nfp.enabled = 0"); - } - } - - private void handleEventTypeFilter(LiveDataQuery.Filter queryFilter, List queryWhereClauses, - FiltersHQLQuery result) - { - // We authorize only a single constraint here - // FIXME: Actually maybe we should allow specifying multiple equals constraints? - LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); - if (EQUALS_OPERATOR.equals(constraint.getOperator())) { - if (NotificationCustomFiltersLiveDataConfigurationProvider.ALL_EVENTS_OPTION_VALUE.equals( - constraint.getValue())) - { - queryWhereClauses.add("length(nfp.allEventTypes) = 0 "); - } else { - queryWhereClauses.add("nfp.allEventTypes like :eventTypes"); - DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); - queryParameter.anyChars().literal(String.valueOf(constraint.getValue())).anyChars(); - result.bindings.put("eventTypes", queryParameter); - } - } - } - - private void handleLocationFilter(LiveDataQuery.Filter locationFilter, List queryWhereClauses, - FiltersHQLQuery result) - { - List clauses = new ArrayList<>(); - int clauseCounter = 0; - String clauseValue = "(nfp.pageOnly like :constraint_%1$s or nfp.page like :constraint_%1$s or " - + "nfp.wiki like :constraint_%1$s or nfp.user like :constraint_%1$s)"; - String constraintName = "constraint_%s"; - for (LiveDataQuery.Constraint constraint : locationFilter.getConstraints()) { - if (EQUALS_OPERATOR.equals(constraint.getOperator())) { - clauses.add(String.format("(nfp.pageOnly = :constraint_%1$s or nfp.page = :constraint_%1$s or " - + "nfp.wiki = :constraint_%1$s or nfp.user = :constraint_%1$s)", clauseCounter)); - DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); - queryParameter.literal(String.valueOf(constraint.getValue())); - result.bindings.put(String.format(constraintName, clauseCounter), queryParameter); - } else if (STARTS_WITH_OPERATOR.equals(constraint.getOperator())) { - clauses.add(String.format(clauseValue, clauseCounter)); - DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); - queryParameter.literal(String.valueOf(constraint.getValue())).anyChars(); - result.bindings.put(String.format(constraintName, clauseCounter), queryParameter); - } else if (CONTAINS_OPERATOR.equals(constraint.getOperator())) { - clauses.add(String.format(clauseValue, clauseCounter)); - DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); - queryParameter.anyChars().literal(String.valueOf(constraint.getValue())).anyChars(); - result.bindings.put(String.format(constraintName, clauseCounter), queryParameter); - } - clauseCounter++; - } - if (!clauses.isEmpty()) { - queryWhereClauses.add(buildQueryClause(locationFilter, clauses)); - } - } - - private String buildQueryClause(LiveDataQuery.Filter filter, List clauses) - { - StringBuilder result = new StringBuilder("("); - String operatorAppender; - if (filter.isMatchAll()) { - operatorAppender = AND; - } else { - operatorAppender = " or "; - } - Iterator iterator = clauses.iterator(); - while (iterator.hasNext()) { - result.append(iterator.next()); - if (iterator.hasNext()) { - result.append(operatorAppender); - } - } - result.append(")"); - return result.toString(); - } - - private String handleSortEntries(List sortEntries) - { - List clauses = new ArrayList<>(); - for (LiveDataQuery.SortEntry sortEntry : sortEntries) { - String sortOperator = "asc"; - if (sortEntry.isDescending()) { - sortOperator = "desc"; - } - switch (sortEntry.getProperty()) { - case NotificationCustomFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD -> - clauses.add(String.format("nfp.enabled %s", sortOperator)); - - case NotificationCustomFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD -> { - if (sortEntry.isDescending()) { - clauses.add("nfp.alertEnabled desc, nfp.emailEnabled asc"); - } else { - clauses.add("nfp.alertEnabled asc, nfp.emailEnabled desc"); - } - } - - case NotificationCustomFiltersLiveDataConfigurationProvider.LOCATION_FIELD, - NotificationCustomFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> { - if (sortEntry.isDescending()) { - clauses.add("nfp.user desc, nfp.wiki desc, nfp.page desc, nfp.pageOnly desc"); - } else { - clauses.add("nfp.pageOnly asc, nfp.page asc, nfp.wiki asc, nfp.user asc"); - } - } - - case NotificationCustomFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD -> - clauses.add(String.format("nfp.filterType %s", sortOperator)); - - case NotificationCustomFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD -> - clauses.add(String.format("nfp.allEventTypes %s", sortOperator)); - - default -> { - } - } - } - if (!clauses.isEmpty()) { - StringBuilder result = new StringBuilder(" order by "); - Iterator iterator = clauses.iterator(); - while (iterator.hasNext()) { - result.append(iterator.next()); - if (iterator.hasNext()) { - result.append(", "); - } - } - return result.toString(); - } else { - return ""; - } - } - @Override public LiveData get(LiveDataQuery query) throws LiveDataException { if (query.getOffset() > Integer.MAX_VALUE) { throw new LiveDataException("Currently only integer offsets are supported."); } - Map sourceParameters = query.getSource().getParameters(); - if (!sourceParameters.containsKey(TARGET_SOURCE_PARAMETER)) { - throw new LiveDataException("The target source parameter is mandatory."); - } - String target = String.valueOf(sourceParameters.get(TARGET_SOURCE_PARAMETER)); - EntityReference ownerReference; - if (WIKI_SOURCE_PARAMETER.equals(target)) { - ownerReference = - this.entityReferenceResolver.resolve(String.valueOf(sourceParameters.get(WIKI_SOURCE_PARAMETER)), - EntityType.WIKI); - } else { - ownerReference = this.entityReferenceResolver.resolve(String.valueOf(sourceParameters.get(target)), - EntityType.DOCUMENT); - } - XWikiContext context = this.contextProvider.get(); - if (!this.contextualAuthorizationManager.hasAccess(Right.ADMIN) - && !ownerReference.equals(context.getUserReference())) { - throw new LiveDataException(UNAUTHORIZED_EXCEPTION_MSG); - } - String serializedOwner = this.entityReferenceSerializer.serialize(ownerReference); + TargetInformation targetInformation = getTargetInformation(query); + String serializedOwner = this.entityReferenceSerializer.serialize(targetInformation.ownerReference); + WikiReference wikiReference = + (WikiReference) targetInformation.ownerReference.extractReference(EntityType.WIKI); LiveData liveData = new LiveData(); - String baseQuery = "select nfp from DefaultNotificationFilterPreference nfp where owner = :owner"; - Optional optionalFiltersHQLQuery = handleFilter(query.getFilters()); - if (optionalFiltersHQLQuery.isPresent()) { - baseQuery += optionalFiltersHQLQuery.get().whereClause; - } - baseQuery += handleSortEntries(query.getSort()); try { - Query hqlQuery = this.queryManager.createQuery(baseQuery, Query.HQL) - .bindValue("owner", serializedOwner) - .setWiki(ownerReference.extractReference(EntityType.WIKI).getName()); - if (optionalFiltersHQLQuery.isPresent()) { - for (Map.Entry binding : optionalFiltersHQLQuery.get().bindings.entrySet()) { - hqlQuery = hqlQuery.bindValue(binding.getKey(), binding.getValue()); - } - } - List notificationFilterPreferences = hqlQuery - .setLimit(query.getLimit()) - .setOffset(query.getOffset().intValue()) - .execute(); - liveData.setCount(notificationFilterPreferences.size()); + long filterCount = this.queryHelper.countTotalFilters(serializedOwner, wikiReference); + List filterPreferences = + this.queryHelper.getFilterPreferences(query, serializedOwner, wikiReference); + liveData.setCount(filterCount); List> entries = liveData.getEntries(); - for (NotificationFilterPreference notificationFilterPreference : notificationFilterPreferences) { + for (NotificationFilterPreference notificationFilterPreference : filterPreferences) { entries.add(getPreferenceInformation(notificationFilterPreference)); } } catch (QueryException | NotificationException e) { diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java new file mode 100644 index 000000000000..c053758deaec --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/custom/NotificationCustomFiltersQueryHelper.java @@ -0,0 +1,326 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +package org.xwiki.notifications.filters.internal.livedata.custom; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.apache.commons.lang3.StringUtils; +import org.xwiki.component.annotation.Component; +import org.xwiki.livedata.LiveDataQuery; +import org.xwiki.model.reference.WikiReference; +import org.xwiki.notifications.NotificationFormat; +import org.xwiki.notifications.filters.NotificationFilterPreference; +import org.xwiki.notifications.filters.NotificationFilterType; +import org.xwiki.query.Query; +import org.xwiki.query.QueryException; +import org.xwiki.query.QueryFilter; +import org.xwiki.query.QueryManager; +import org.xwiki.query.internal.DefaultQueryParameter; + +/** + * Helper to perform DB queries for retrieving notification custom filters with live data filters and sort. + * TODO: maybe this could be improved with some APIs to put in NotificationFilterPreferenceStore. + * + * @version $Id$ + * @since 16.2.0RC1 + */ +@Component(roles = NotificationCustomFiltersQueryHelper.class) +@Singleton +public class NotificationCustomFiltersQueryHelper +{ + private static final String BASE_QUERY = "select nfp from DefaultNotificationFilterPreference nfp " + + "where owner = :owner"; + private static final String OWNER_BINDING = "owner"; + private static final String STARTS_WITH_OPERATOR = "startsWith"; + private static final String CONTAINS_OPERATOR = "contains"; + private static final String EQUALS_OPERATOR = "equals"; + private static final String AND = " and "; + + @Inject + private QueryManager queryManager; + + @Inject + @Named("count") + private QueryFilter countQueryFilter; + + private static final class FiltersHQLQuery + { + private String whereClause; + private final Map bindings = new LinkedHashMap<>(); + } + + private Optional handleFilter(List queryFilters) + { + FiltersHQLQuery result = new FiltersHQLQuery(); + List queryWhereClauses = new ArrayList<>(); + for (LiveDataQuery.Filter queryFilter : queryFilters) { + switch (queryFilter.getProperty()) { + case NotificationCustomFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD -> + this.handleIsEnabledFilter(queryFilter, queryWhereClauses); + + case NotificationCustomFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD -> + this.handleNotificationFormatsFilter(queryFilter, queryWhereClauses); + + case NotificationCustomFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> + this.handleScopeFilter(queryFilter, queryWhereClauses); + + case NotificationCustomFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD -> + this.handleFilterTypeFilter(queryFilter, queryWhereClauses, result); + + case NotificationCustomFiltersLiveDataConfigurationProvider.LOCATION_FIELD -> + this.handleLocationFilter(queryFilter, queryWhereClauses, result); + + case NotificationCustomFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD -> + this.handleEventTypeFilter(queryFilter, queryWhereClauses, result); + + default -> { + } + } + } + if (!queryWhereClauses.isEmpty()) { + result.whereClause = queryWhereClauses.stream().collect(Collectors.joining(AND, AND, "")); + return Optional.of(result); + } else { + return Optional.empty(); + } + } + + private void handleFilterTypeFilter(LiveDataQuery.Filter queryFilter, List queryWhereClauses, + FiltersHQLQuery result) + { + // We authorize only a single constraint here + LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); + if (EQUALS_OPERATOR.equals(constraint.getOperator())) { + queryWhereClauses.add("nfp.filterType = :filterType"); + result.bindings.put("filterType", + NotificationFilterType.valueOf(String.valueOf(constraint.getValue()))); + } + } + + private void handleScopeFilter(LiveDataQuery.Filter queryFilter, List queryWhereClauses) + { + // We authorize only a single constraint here + LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); + if (EQUALS_OPERATOR.equals(constraint.getOperator()) + && !StringUtils.isBlank(String.valueOf(constraint.getValue()))) { + NotificationCustomFiltersLiveDataConfigurationProvider.Scope scope = + NotificationCustomFiltersLiveDataConfigurationProvider.Scope.valueOf( + String.valueOf(constraint.getValue())); + queryWhereClauses.add(String.format("length(nfp.%s) > 0 ", scope.getFieldName())); + } + } + + private void handleNotificationFormatsFilter(LiveDataQuery.Filter queryFilter, List queryWhereClauses) + { + // We authorize only a single constraint here + LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); + String constraintValue = String.valueOf(constraint.getValue()); + if (EQUALS_OPERATOR.equals(constraint.getOperator()) + && !StringUtils.isEmpty(constraintValue)) { + if (NotificationFormat.ALERT.name().equals(constraintValue)) { + queryWhereClauses.add("nfp.alertEnabled = 1"); + } + if (NotificationFormat.EMAIL.name().equals(constraintValue)) { + queryWhereClauses.add("nfp.emailEnabled = 1"); + } + } + } + + private void handleIsEnabledFilter(LiveDataQuery.Filter queryFilter, List queryWhereClauses) + { + // We only check first constraint: if there's more they are either redundant or contradictory. + LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); + if (Boolean.parseBoolean(String.valueOf(constraint.getValue()))) { + queryWhereClauses.add("nfp.enabled = 1"); + } else { + queryWhereClauses.add("nfp.enabled = 0"); + } + } + + private void handleEventTypeFilter(LiveDataQuery.Filter queryFilter, List queryWhereClauses, + FiltersHQLQuery result) + { + // We authorize only a single constraint here + LiveDataQuery.Constraint constraint = queryFilter.getConstraints().get(0); + if (EQUALS_OPERATOR.equals(constraint.getOperator())) { + if (NotificationCustomFiltersLiveDataConfigurationProvider.ALL_EVENTS_OPTION_VALUE.equals( + constraint.getValue())) + { + queryWhereClauses.add("length(nfp.allEventTypes) = 0 "); + } else { + queryWhereClauses.add("nfp.allEventTypes like :eventTypes"); + DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); + queryParameter.anyChars().literal(String.valueOf(constraint.getValue())).anyChars(); + result.bindings.put("eventTypes", queryParameter); + } + } + } + + private void handleLocationFilter(LiveDataQuery.Filter locationFilter, List queryWhereClauses, + FiltersHQLQuery result) + { + List clauses = new ArrayList<>(); + int clauseCounter = 0; + + // for searching in location we actually need to look in 4 columns in DB, so we reuse same constraint binding + // in those 4 columns, and since we might have multiple constraints we use a counter to name those bindings + String clauseValue = "(nfp.pageOnly like :constraint_%1$s or nfp.page like :constraint_%1$s or " + + "nfp.wiki like :constraint_%1$s or nfp.user like :constraint_%1$s)"; + String constraintName = "constraint_%s"; + + for (LiveDataQuery.Constraint constraint : locationFilter.getConstraints()) { + if (EQUALS_OPERATOR.equals(constraint.getOperator())) { + clauses.add(String.format("(nfp.pageOnly = :constraint_%1$s or nfp.page = :constraint_%1$s or " + + "nfp.wiki = :constraint_%1$s or nfp.user = :constraint_%1$s)", clauseCounter)); + DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); + queryParameter.literal(String.valueOf(constraint.getValue())); + result.bindings.put(String.format(constraintName, clauseCounter), queryParameter); + } else if (STARTS_WITH_OPERATOR.equals(constraint.getOperator())) { + clauses.add(String.format(clauseValue, clauseCounter)); + DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); + queryParameter.literal(String.valueOf(constraint.getValue())).anyChars(); + result.bindings.put(String.format(constraintName, clauseCounter), queryParameter); + } else if (CONTAINS_OPERATOR.equals(constraint.getOperator())) { + clauses.add(String.format(clauseValue, clauseCounter)); + DefaultQueryParameter queryParameter = new DefaultQueryParameter(null); + queryParameter.anyChars().literal(String.valueOf(constraint.getValue())).anyChars(); + result.bindings.put(String.format(constraintName, clauseCounter), queryParameter); + } + clauseCounter++; + } + if (!clauses.isEmpty()) { + queryWhereClauses.add(buildQueryClause(locationFilter, clauses)); + } + } + + private String buildQueryClause(LiveDataQuery.Filter filter, List clauses) + { + String operatorAppender; + if (filter.isMatchAll()) { + operatorAppender = AND; + } else { + operatorAppender = " or "; + } + return clauses.stream().collect(Collectors.joining(operatorAppender, "(", ")")); + } + + /** + * Count the total number of filter preferences for given owner on given wiki. + * @param owner the owner for which to count all filter preferences + * @param wikiReference the wiki where to count all filter preferences + * @return the total number of filter preferences + * @throws QueryException in case of problem to perform the query + */ + public long countTotalFilters(String owner, WikiReference wikiReference) throws QueryException + { + return this.queryManager.createQuery(BASE_QUERY, Query.HQL) + .bindValue(OWNER_BINDING, owner) + .setWiki(wikiReference.getName()) + .addFilter(this.countQueryFilter) + .execute() + .get(0); + } + + private String handleSortEntries(List sortEntries) + { + List clauses = new ArrayList<>(); + for (LiveDataQuery.SortEntry sortEntry : sortEntries) { + String sortOperator = "asc"; + if (sortEntry.isDescending()) { + sortOperator = "desc"; + } + switch (sortEntry.getProperty()) { + case NotificationCustomFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD -> + clauses.add(String.format("nfp.enabled %s", sortOperator)); + + case NotificationCustomFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD -> { + // We use on purpose asc/desc in combination to ensure having both formats in the middle of the data + // since it's the most common: by doing that we'll have [only alerts, both formats, only emails] + if (sortEntry.isDescending()) { + clauses.add("nfp.emailEnabled asc, nfp.alertEnabled desc"); + } else { + clauses.add("nfp.alertEnabled asc, nfp.emailEnabled desc"); + } + } + + case NotificationCustomFiltersLiveDataConfigurationProvider.LOCATION_FIELD, + NotificationCustomFiltersLiveDataConfigurationProvider.SCOPE_FIELD -> + clauses.add(String.format("nfp.user %1$s, nfp.wiki %1$s, nfp.page %1$s, nfp.pageOnly %1$s", + sortOperator)); + + case NotificationCustomFiltersLiveDataConfigurationProvider.FILTER_TYPE_FIELD -> + clauses.add(String.format("nfp.filterType %s", sortOperator)); + + case NotificationCustomFiltersLiveDataConfigurationProvider.EVENT_TYPES_FIELD -> + clauses.add(String.format("nfp.allEventTypes %s", sortOperator)); + + default -> { + } + } + } + if (!clauses.isEmpty()) { + return clauses.stream().collect(Collectors.joining(", ", " order by ", "")); + } else { + return ""; + } + } + + /** + * Retrieve all filter preferences matching the live data query for the given owner on the given wiki. + * + * @param query the livedata query containing filters and sort criteria + * @param owner the owner of the filter preferences to retrieve + * @param wikiReference the wiki where to retrieve the filter preferences + * @return the list of filter preferences + * @throws QueryException in case of problem to perform the query + */ + public List getFilterPreferences(LiveDataQuery query, String owner, + WikiReference wikiReference) throws QueryException + { + String baseQuery = BASE_QUERY; + Optional optionalFiltersHQLQuery = handleFilter(query.getFilters()); + if (optionalFiltersHQLQuery.isPresent()) { + baseQuery += optionalFiltersHQLQuery.get().whereClause; + } + baseQuery += handleSortEntries(query.getSort()); + Query hqlQuery = this.queryManager.createQuery(baseQuery, Query.HQL) + .bindValue(OWNER_BINDING, owner) + .setWiki(wikiReference.getName()); + if (optionalFiltersHQLQuery.isPresent()) { + for (Map.Entry binding : optionalFiltersHQLQuery.get().bindings.entrySet()) { + hqlQuery = hqlQuery.bindValue(binding.getKey(), binding.getValue()); + } + } + return hqlQuery + .setLimit(query.getLimit()) + .setOffset(query.getOffset().intValue()) + .execute(); + } +} diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java index d70de002156d..f7ebdf1982ef 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataConfigurationProvider.java @@ -50,7 +50,6 @@ public class NotificationSystemFiltersLiveDataConfigurationProvider implements P static final String IS_ENABLED_FIELD = "isEnabled"; private static final String TRANSLATION_PREFIX = "notifications.settings.filters.preferences.system.table."; private static final String STRING_TYPE = "String"; - private static final String HTML_DISPLAYER = "html"; @Inject private ContextualLocalizationManager l10n; @@ -80,18 +79,21 @@ public LiveDataConfiguration get() return input; } - private LiveDataPropertyDescriptor getNameDescriptor() + private void setDescriptorValues(LiveDataPropertyDescriptor descriptor) { - LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); - descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + NAME_FIELD)); - descriptor.setId(NAME_FIELD); - descriptor.setType(STRING_TYPE); - descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER)); descriptor.setVisible(true); descriptor.setEditable(false); descriptor.setSortable(false); descriptor.setFilterable(false); + } + private LiveDataPropertyDescriptor getNameDescriptor() + { + LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); + descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + NAME_FIELD)); + descriptor.setId(NAME_FIELD); + descriptor.setType(STRING_TYPE); + this.setDescriptorValues(descriptor); return descriptor; } @@ -101,12 +103,7 @@ private LiveDataPropertyDescriptor getDescriptionDescriptor() descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + DESCRIPTION_FIELD)); descriptor.setId(DESCRIPTION_FIELD); descriptor.setType(STRING_TYPE); - descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER)); - descriptor.setVisible(true); - descriptor.setEditable(false); - descriptor.setSortable(false); - descriptor.setFilterable(false); - + this.setDescriptorValues(descriptor); return descriptor; } @@ -116,12 +113,8 @@ private LiveDataPropertyDescriptor getNotificationFormatsDescriptor() descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + NOTIFICATION_FORMATS_FIELD)); descriptor.setId(NOTIFICATION_FORMATS_FIELD); descriptor.setType(STRING_TYPE); - descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor(HTML_DISPLAYER)); - descriptor.setVisible(true); - descriptor.setEditable(false); - descriptor.setSortable(false); - descriptor.setFilterable(false); - + descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("staticList")); + this.setDescriptorValues(descriptor); return descriptor; } @@ -130,16 +123,9 @@ private LiveDataPropertyDescriptor getIsEnabledDescriptor() LiveDataPropertyDescriptor descriptor = new LiveDataPropertyDescriptor(); descriptor.setName(this.l10n.getTranslationPlain(TRANSLATION_PREFIX + IS_ENABLED_FIELD)); descriptor.setId(IS_ENABLED_FIELD); - descriptor.setType("Boolean"); - LiveDataPropertyDescriptor.FilterDescriptor filterBoolean = - new LiveDataPropertyDescriptor.FilterDescriptor("boolean"); - descriptor.setFilter(filterBoolean); + descriptor.setType("boolean"); descriptor.setDisplayer(new LiveDataPropertyDescriptor.DisplayerDescriptor("toggle")); - descriptor.setVisible(true); - descriptor.setEditable(false); - descriptor.setSortable(false); - descriptor.setFilterable(false); - + this.setDescriptorValues(descriptor); return descriptor; } } diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java index d5de17701e19..b7c16bcb7cc8 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/java/org/xwiki/notifications/filters/internal/livedata/system/NotificationSystemFiltersLiveDataEntryStore.java @@ -23,11 +23,9 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Named; -import javax.inject.Provider; import javax.inject.Singleton; import org.xwiki.component.annotation.Component; @@ -36,21 +34,15 @@ import org.xwiki.livedata.LiveDataException; import org.xwiki.livedata.LiveDataQuery; import org.xwiki.model.reference.DocumentReference; -import org.xwiki.model.reference.DocumentReferenceResolver; import org.xwiki.model.reference.LocalDocumentReference; import org.xwiki.model.reference.WikiReference; import org.xwiki.notifications.NotificationException; -import org.xwiki.notifications.NotificationFormat; import org.xwiki.notifications.filters.NotificationFilter; import org.xwiki.notifications.filters.NotificationFilterManager; import org.xwiki.notifications.filters.internal.FilterPreferencesModelBridge; import org.xwiki.notifications.filters.internal.ToggleableNotificationFilter; import org.xwiki.notifications.filters.internal.ToggleableNotificationFilterActivation; -import org.xwiki.notifications.filters.internal.livedata.NotificationFilterLiveDataTranslationHelper; -import org.xwiki.security.authorization.ContextualAuthorizationManager; -import org.xwiki.security.authorization.Right; - -import com.xpn.xwiki.XWikiContext; +import org.xwiki.notifications.filters.internal.livedata.AbstractNotificationFilterLiveDataEntryStore; /** * Dedicated {@link LiveDataEntryStore} for the {@link NotificationSystemFiltersLiveDataSource}. @@ -61,32 +53,17 @@ @Component @Singleton @Named(NotificationSystemFiltersLiveDataSource.NAME) -public class NotificationSystemFiltersLiveDataEntryStore implements LiveDataEntryStore +public class NotificationSystemFiltersLiveDataEntryStore extends AbstractNotificationFilterLiveDataEntryStore { - private static final String TARGET_SOURCE_PARAMETER = "target"; - private static final String WIKI_SOURCE_PARAMETER = "wiki"; - private static final String UNAUTHORIZED_EXCEPTION_MSG = "You don't have rights to access those information."; private static final LocalDocumentReference NOTIFICATION_ADMINISTRATION_REF = new LocalDocumentReference(List.of("XWiki", "Notifications", "Code"), "NotificationAdministration"); - @Inject - private Provider contextProvider; - @Inject private NotificationFilterManager notificationFilterManager; @Inject private FilterPreferencesModelBridge filterPreferencesModelBridge; - @Inject - private DocumentReferenceResolver documentReferenceResolver; - - @Inject - private NotificationFilterLiveDataTranslationHelper translationHelper; - - @Inject - private ContextualAuthorizationManager contextualAuthorizationManager; - @Override public Optional> get(Object entryId) throws LiveDataException { @@ -105,13 +82,12 @@ private Map getPreferencesInformation(ToggleableNotificationFilt this.translationHelper.getTranslationWithPrefix("notifications.filters.description.", notificationFilter.getName()), NotificationSystemFiltersLiveDataConfigurationProvider.NOTIFICATION_FORMATS_FIELD, - this.displayNotificationFormats(notificationFilter), - NotificationSystemFiltersLiveDataConfigurationProvider.IS_ENABLED_FIELD, - this.displayIsEnabled(notificationFilter, filterActivation), + displayNotificationFormats(notificationFilter.getFormats()), + // We don't need to return isEnabled, but only the metadata defined below. "isEnabled_data", - this.displayIsEnabledData(notificationFilter, filterActivation), + displayIsEnabledData(notificationFilter, filterActivation), "isEnabled_checked", - this.isEnabled(notificationFilter, filterActivation) + isEnabled(notificationFilter, filterActivation) ); } @@ -133,98 +109,56 @@ private Map displayIsEnabledData(NotificationFilter notification private boolean isEnabled(ToggleableNotificationFilter notificationFilter, ToggleableNotificationFilterActivation filterActivation) { - return (filterActivation != null && filterActivation.isEnabled()) || - (filterActivation == null && notificationFilter.isEnabledByDefault()); - } - - private String displayIsEnabled(ToggleableNotificationFilter notificationFilter, - ToggleableNotificationFilterActivation filterActivation) - { - String checked = (this.isEnabled(notificationFilter, filterActivation)) ? "checked=\"checked\"" : ""; - String html = ""; - return String.format(html, notificationFilter.getName(), getObjectNumber(filterActivation), checked); - } - - private String displayNotificationFormats(ToggleableNotificationFilter filter) - { - StringBuilder result = new StringBuilder("
    "); - - for (NotificationFormat notificationFormat : filter.getFormats()) { - result.append("
  • "); - result.append(this.translationHelper.getFormatTranslation(notificationFormat)); - result.append("
  • "); - } - result.append("
"); - - return result.toString(); + return (filterActivation != null && filterActivation.isEnabled()) + || (filterActivation == null && notificationFilter.isEnabledByDefault()); } @Override public LiveData get(LiveDataQuery query) throws LiveDataException { - if (query.getOffset() > Integer.MAX_VALUE) { - throw new LiveDataException("Currently only integer offsets are supported."); - } - Map sourceParameters = query.getSource().getParameters(); - if (!sourceParameters.containsKey(TARGET_SOURCE_PARAMETER)) { - throw new LiveDataException("The target source parameter is mandatory."); - } - String target = String.valueOf(sourceParameters.get(TARGET_SOURCE_PARAMETER)); - List filters = null; + TargetInformation targetInformation = getTargetInformation(query); Map filtersActivations = null; - boolean isAuthorized = false; + List allFilters = null; try { - if (WIKI_SOURCE_PARAMETER.equals(target)) { - WikiReference wikiReference = - new WikiReference(String.valueOf(sourceParameters.get(WIKI_SOURCE_PARAMETER))); - if (this.contextualAuthorizationManager.hasAccess(Right.ADMIN, wikiReference)) { - isAuthorized = true; - filters = this.notificationFilterManager.getAllFilters(wikiReference) + if (targetInformation.isWikiTarget) { + WikiReference wikiReference = (WikiReference) targetInformation.ownerReference; + allFilters = + this.notificationFilterManager.getAllFilters(wikiReference) .stream() .filter(filter -> filter instanceof ToggleableNotificationFilter) .map(item -> (ToggleableNotificationFilter) item) - .collect(Collectors.toList()); - filtersActivations = - this.filterPreferencesModelBridge.getToggleableFilterActivations( - new DocumentReference(NOTIFICATION_ADMINISTRATION_REF, wikiReference)); - } + .toList(); + filtersActivations = + this.filterPreferencesModelBridge.getToggleableFilterActivations( + new DocumentReference(NOTIFICATION_ADMINISTRATION_REF, wikiReference)); } else { - XWikiContext context = this.contextProvider.get(); - String targetValue = String.valueOf(sourceParameters.get(target)); - DocumentReference userDoc = - this.documentReferenceResolver.resolve(targetValue); - if (this.contextualAuthorizationManager.hasAccess(Right.ADMIN) - || context.getUserReference().equals(userDoc)) { - isAuthorized = true; - filters = this.notificationFilterManager.getAllFilters(userDoc, false) + DocumentReference userDoc = (DocumentReference) targetInformation.ownerReference; + allFilters = this.notificationFilterManager.getAllFilters(userDoc, false) .stream() .filter(filter -> filter instanceof ToggleableNotificationFilter) .map(item -> (ToggleableNotificationFilter) item) - .collect(Collectors.toList()); - filtersActivations = this.filterPreferencesModelBridge.getToggleableFilterActivations(userDoc); - } + .toList(); + filtersActivations = this.filterPreferencesModelBridge.getToggleableFilterActivations(userDoc); } } catch (NotificationException e) { throw new LiveDataException("Error when getting list of filters", e); } - if (!isAuthorized) { - throw new LiveDataException(UNAUTHORIZED_EXCEPTION_MSG); - } - filters.sort(Comparator.comparing(NotificationFilter::getName)); + int totalFilters = allFilters.size(); + List filters = allFilters + .stream() + .sorted(Comparator.comparing(NotificationFilter::getName)) + .skip(query.getOffset()) + .limit(query.getLimit()) + .toList(); LiveData liveData = new LiveData(); - int offset = query.getOffset().intValue(); - if (offset < filters.size()) { - List> entries = liveData.getEntries(); - for (int i = offset; i < Math.min(filters.size(), offset + query.getLimit()); i++) { - ToggleableNotificationFilter notificationFilter = filters.get(i); - entries.add(getPreferencesInformation(notificationFilter, - filtersActivations.get(notificationFilter.getName()))); - } - liveData.setCount(entries.size()); + List> entries = liveData.getEntries(); + for (ToggleableNotificationFilter notificationFilter : filters) { + entries.add(getPreferencesInformation(notificationFilter, + filtersActivations.get(notificationFilter.getName()))); } + liveData.setCount(totalFilters); return liveData; } } diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/resources/META-INF/components.txt b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/resources/META-INF/components.txt index d236c7ff52db..0f2cb49877b7 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/resources/META-INF/components.txt +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-filters/xwiki-platform-notifications-filters-default/src/main/resources/META-INF/components.txt @@ -4,6 +4,7 @@ org.xwiki.notifications.filters.internal.livedata.custom.NotificationCustomFilte org.xwiki.notifications.filters.internal.livedata.custom.NotificationCustomFiltersLiveDataEntryStore org.xwiki.notifications.filters.internal.livedata.custom.NotificationCustomFiltersLiveDataPropertyDescriptorStore org.xwiki.notifications.filters.internal.livedata.custom.NotificationCustomFiltersLiveDataSource +org.xwiki.notifications.filters.internal.livedata.custom.NotificationCustomFiltersQueryHelper org.xwiki.notifications.filters.internal.livedata.system.NotificationSystemFiltersLiveDataConfigurationProvider org.xwiki.notifications.filters.internal.livedata.system.NotificationSystemFiltersLiveDataConfigurationResolver org.xwiki.notifications.filters.internal.livedata.system.NotificationSystemFiltersLiveDataEntryStore diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml index a2af30cbd19a..4bad4fe6febf 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsCustomFiltersPreferencesMacro.xml @@ -470,6 +470,7 @@ require(['jquery', 'NewCustomNotificationFilterModal', 'xwiki-bootstrap-switch', ], function ($, xm, Vue, notifications, l10n) { Vue.component('DisplayerToggle', notifications.DisplayerToggle); Vue.component('DisplayerScope', notifications.DisplayerScope); + Vue.component('DisplayerStaticList', notifications.DisplayerStaticList); // Globals var serviceReference = XWiki.Model.resolve('XWiki.Notifications.Code.NotificationPreferenceService', diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml index e2fd864295c5..098d83e97a9e 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-ui/src/main/resources/XWiki/Notifications/Code/NotificationsSystemFiltersPreferencesMacro.xml @@ -136,6 +136,7 @@ require(['jquery', 'xwiki-l10n!notifications-system-filters-preferences-macro', 'vue', 'xwiki-platform-notifications-webjar'], function ($, l10n, Vue, notifications) { Vue.component('DisplayerToggle', notifications.DisplayerToggle) + Vue.component('DisplayerStaticList', notifications.DisplayerStaticList); function save(checked, filterName, objectNumber, docURL) { return new Promise((resolve) => { diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/components/DisplayerStaticList.vue b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/components/DisplayerStaticList.vue new file mode 100644 index 000000000000..3d31adb65306 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/components/DisplayerStaticList.vue @@ -0,0 +1,64 @@ + + + + + + \ No newline at end of file diff --git a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/main.js b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/main.js index edc65b39e77a..b6b669eb2aa8 100644 --- a/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/main.js +++ b/xwiki-platform-core/xwiki-platform-notifications/xwiki-platform-notifications-webjar/src/main/vue/src/main.js @@ -19,6 +19,7 @@ */ import DisplayerToggle from "./components/DisplayerToggle.vue"; import DisplayerScope from "./components/DisplayerScope.vue"; +import DisplayerStaticList from "./components/DisplayerStaticList.vue"; // Export the elements that are expected to be imported from other modules. -export {DisplayerToggle,DisplayerScope} \ No newline at end of file +export {DisplayerToggle,DisplayerScope,DisplayerStaticList} \ No newline at end of file