Repository: primefaces/primefaces (java)

Issue:
Animation: Respect CSS `prefers-reduced-motion`
Using animations on the web in the most accessible way requires a little extra consideration:

In general, it's best to use animations on the web in a complementary way rather than the only way to note a state change in UI. Remember to update the text of actions and change elements' other visual aspects too!

And when it comes to an animation's timing and duration, avoid changing the state of something at a rate of three times per second or greater - which can trigger photosensitive epilepsy.

All included animations now support and leverage the prefers-reduced-motion [CSS media feature (opens new window)](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion)to detect if a user has requested that the system minimize the amount of non-essential motion it uses.


When a `prefers-reduced-motion` is set to `reduce`, it indicates that the user prefers less motion, which may trigger discomfort for those with vestibular motion disorders on the page.

![image](https://github.com/primefaces/primefaces/assets/4399574/06a2b931-2c25-4429-87bd-3688e65ad66e)



Pull request:
Fix #11130: Respect OS prefers-reduced-motion setting
Fix #11130: Respect OS prefers-reduced-motion setting

Patch:
diff --git a/docs/14_0_0/gettingstarted/whatsnew.md b/docs/14_0_0/gettingstarted/whatsnew.md
index bbb38cc8e4..fe55c16024 100644
--- a/docs/14_0_0/gettingstarted/whatsnew.md
+++ b/docs/14_0_0/gettingstarted/whatsnew.md
@@ -10,6 +10,7 @@ Look into [migration guide](https://primefaces.github.io/primefaces/14_0_0/#/../
 
 * Core
     * `MOVE_SCRIPTS_TO_BOTTOM` adds new option `defer` to defer loading scripts
+    * OS settings for `prefers-reduced-motion: reduce` is now respected and PF disables all animations
 
 * Accordion
     * Added `toggleSpeed` for toggle speed animation duration.
diff --git a/docs/migrationguide/14_0_0.md b/docs/migrationguide/14_0_0.md
index 4b517c26c6..b3347bb191 100644
--- a/docs/migrationguide/14_0_0.md
+++ b/docs/migrationguide/14_0_0.md
@@ -4,6 +4,8 @@
 
 > :warning: **JSF 2.0, 2.1 and 2.2 support is has been removed!**
 
+> :warning: OS settings for `prefers-reduced-motion: reduce` is now respected and PF disables all animations
+
 ## Deleted
   * JSF 2.0, 2.1 and 2.2 support has been removed
   * `Watermark` in favor passthrough attribute `pt:placeholder` or vanilla JS
diff --git a/pom.xml b/pom.xml
index ef62a19369..fdd72a876a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -59,6 +59,10 @@
             <id>christophs78</id>
             <name>Christoph Straßer</name>
         </developer>
+        <developer>
+            <id>jepsar</id>
+            <name>Jasper de Vries</name>
+        </developer>
     </developers>
 
     <properties>
diff --git a/primefaces/src/main/java/org/primefaces/component/api/UIColumn.java b/primefaces/src/main/java/org/primefaces/component/api/UIColumn.java
index 35256ded6f..3019d8f582 100644
--- a/primefaces/src/main/java/org/primefaces/component/api/UIColumn.java
+++ b/primefaces/src/main/java/org/primefaces/component/api/UIColumn.java
@@ -23,10 +23,11 @@
  */
 package org.primefaces.component.api;
 
-import java.io.IOException;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import org.primefaces.component.celleditor.CellEditor;
+import org.primefaces.model.MatchMode;
+import org.primefaces.util.FacetUtils;
+import org.primefaces.util.LangUtils;
+
 import javax.el.ELContext;
 import javax.el.MethodExpression;
 import javax.el.ValueExpression;
@@ -35,11 +36,11 @@ import javax.faces.component.EditableValueHolder;
 import javax.faces.component.UIComponent;
 import javax.faces.component.UIData;
 import javax.faces.context.FacesContext;
-
-import org.primefaces.component.celleditor.CellEditor;
-import org.primefaces.model.MatchMode;
-import org.primefaces.util.ComponentTraversalUtils;
-import org.primefaces.util.LangUtils;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 public interface UIColumn {
 
@@ -246,9 +247,26 @@ public interface UIColumn {
 
     Object getConverter();
 
-    default EditableValueHolder getFilterValueHolder() {
+    default Object getFilterValueFromValueHolder() {
         UIComponent filterFacet = getFacet("filter");
-        return ComponentTraversalUtils.firstChildRenderedOrSelf(EditableValueHolder.class, filterFacet);
+        if (filterFacet == null) {
+            return null;
+        }
+        AtomicReference<Object> filterValue = new AtomicReference<>(null);
+
+        FacetUtils.invokeOnEditableValueHolder(FacesContext.getCurrentInstance(), filterFacet, (ctx, component) -> {
+            filterValue.set(((EditableValueHolder) component).getValue());
+        });
+
+        return filterValue.get();
+    }
+
+    default void setFilterValueToValueHolder(Object value) {
+        UIComponent filterFacet = getFacet("filter");
+
+        FacetUtils.invokeOnEditableValueHolder(FacesContext.getCurrentInstance(), filterFacet, (ctx, component) -> {
+            ((EditableValueHolder) component).setValue(value);
+        });
     }
 
     default UIComponent asUIComponent() {
diff --git a/primefaces/src/main/java/org/primefaces/component/api/UITable.java b/primefaces/src/main/java/org/primefaces/component/api/UITable.java
index 7365ca8b10..4b0e7bc8cd 100644
--- a/primefaces/src/main/java/org/primefaces/component/api/UITable.java
+++ b/primefaces/src/main/java/org/primefaces/component/api/UITable.java
@@ -23,20 +23,6 @@
  */
 package org.primefaces.component.api;
 
-import java.util.*;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import javax.el.MethodExpression;
-import javax.el.ValueExpression;
-import javax.faces.FacesException;
-import javax.faces.component.UIComponent;
-import javax.faces.component.UINamingContainer;
-import javax.faces.component.ValueHolder;
-import javax.faces.component.search.SearchExpressionHint;
-import javax.faces.context.FacesContext;
-import javax.faces.convert.ConverterException;
-
 import org.primefaces.component.column.ColumnBase;
 import org.primefaces.component.headerrow.HeaderRow;
 import org.primefaces.expression.SearchExpressionUtils;
@@ -48,6 +34,20 @@ import org.primefaces.util.FacetUtils;
 import org.primefaces.util.LangUtils;
 import org.primefaces.util.LocaleUtils;
 
+import javax.el.MethodExpression;
+import javax.el.ValueExpression;
+import javax.faces.FacesException;
+import javax.faces.component.UIComponent;
+import javax.faces.component.UINamingContainer;
+import javax.faces.component.ValueHolder;
+import javax.faces.component.search.SearchExpressionHint;
+import javax.faces.context.FacesContext;
+import javax.faces.convert.ConverterException;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
 public interface UITable<T extends UITableState> extends ColumnAware, MultiViewStateAware<T> {
 
     /**
@@ -177,8 +177,12 @@ public interface UITable<T extends UITableState> extends ColumnAware, MultiViewS
 
         FilterMeta globalFilter = filterBy.get(FilterMeta.GLOBAL_FILTER_KEY);
         if (globalFilter != null) {
-            globalFilter.setFilterValue(
-                    params.get(((UIComponent) this).getClientId(context) + separator + FilterMeta.GLOBAL_FILTER_KEY));
+            String componentIdPrefix = ((UIComponent) this).getClientId(context) + separator + FilterMeta.GLOBAL_FILTER_KEY;
+            String filterValue = params.entrySet().stream()
+                    .filter(e -> e.getKey() != null && e.getKey().startsWith(componentIdPrefix))
+                    .map(e -> e.getValue())
+                    .findFirst().orElse(null);
+            globalFilter.setFilterValue(filterValue);
         }
 
         forEachColumn(column -> {
@@ -191,12 +195,11 @@ public interface UITable<T extends UITableState> extends ColumnAware, MultiViewS
                 ((DynamicColumn) column).applyModel();
             }
 
-            ValueHolder filterValueHolder = column.getFilterValueHolder();
-            boolean hasCustomFilter = filterValueHolder != null;
+            boolean hasCustomFilter = column.getFacet("filter") != null;
 
             Object filterValue;
             if (hasCustomFilter) {
-                filterValue = filterValueHolder.getLocalValue();
+                filterValue = column.getFilterValueFromValueHolder();
             }
             else {
                 String valueHolderClientId = column instanceof DynamicColumn
diff --git a/primefaces/src/main/java/org/primefaces/component/datatable/DataTableRenderer.java b/primefaces/src/main/java/org/primefaces/component/datatable/DataTableRenderer.java
index 84b79d5a4e..74bd44e276 100644
--- a/primefaces/src/main/java/org/primefaces/component/datatable/DataTableRenderer.java
+++ b/primefaces/src/main/java/org/primefaces/component/datatable/DataTableRenderer.java
@@ -23,24 +23,6 @@
  */
 package org.primefaces.component.datatable;
 
-import java.io.IOException;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import javax.el.ELContext;
-import javax.el.MethodExpression;
-import javax.el.ValueExpression;
-import javax.faces.FacesException;
-import javax.faces.component.UIComponent;
-import javax.faces.component.UINamingContainer;
-import javax.faces.component.ValueHolder;
-import javax.faces.context.FacesContext;
-import javax.faces.context.ResponseWriter;
-
 import org.primefaces.component.api.DynamicColumn;
 import org.primefaces.component.api.UIColumn;
 import org.primefaces.component.celleditor.CellEditor;
@@ -58,7 +40,30 @@ import org.primefaces.model.ColumnMeta;
 import org.primefaces.model.SortMeta;
 import org.primefaces.model.SortOrder;
 import org.primefaces.renderkit.DataRenderer;
-import org.primefaces.util.*;
+import org.primefaces.util.ComponentUtils;
+import org.primefaces.util.Constants;
+import org.primefaces.util.FacetUtils;
+import org.primefaces.util.HTML;
+import org.primefaces.util.LangUtils;
+import org.primefaces.util.MessageFactory;
+import org.primefaces.util.WidgetBuilder;
+
+import javax.el.ELContext;
+import javax.el.MethodExpression;
+import javax.el.ValueExpression;
+import javax.faces.FacesException;
+import javax.faces.component.UIComponent;
+import javax.faces.component.UINamingContainer;
+import javax.faces.context.FacesContext;
+import javax.faces.context.ResponseWriter;
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 public class DataTableRenderer extends DataRenderer {
 
@@ -752,8 +757,7 @@ public class DataTableRenderer extends DataRenderer {
         }
         else {
             Object filterValue = table.getFilterValue(column);
-            ValueHolder filterValueHolder = column.getFilterValueHolder();
-            filterValueHolder.setValue(filterValue);
+            column.setFilterValueToValueHolder(filterValue);
 
             writer.startElement("div", null);
             writer.writeAttribute("class", DataTable.COLUMN_CUSTOM_FILTER_CLASS, null);
diff --git a/primefaces/src/main/java/org/primefaces/event/CellEditEvent.java b/primefaces/src/main/java/org/primefaces/event/CellEditEvent.java
index c925ac97fc..f52f0513ca 100644
--- a/primefaces/src/main/java/org/primefaces/event/CellEditEvent.java
+++ b/primefaces/src/main/java/org/primefaces/event/CellEditEvent.java
@@ -33,8 +33,8 @@ import org.primefaces.component.treetable.TreeTable;
 import org.primefaces.util.FacetUtils;
 
 import javax.faces.FacesException;
+import javax.faces.component.EditableValueHolder;
 import javax.faces.component.UIComponent;
-import javax.faces.component.ValueHolder;
 import javax.faces.component.behavior.Behavior;
 import javax.faces.context.FacesContext;
 import java.util.ArrayList;
@@ -121,8 +121,8 @@ public class CellEditEvent<T> extends AbstractAjaxBehaviorEvent {
                 AtomicBoolean invoked = new AtomicBoolean(false);
                 List<Object> values = new ArrayList<>(1);
 
-                FacetUtils.invokeOnValueHolder(FacesContext.getCurrentInstance(), inputFacet, (ctx, component) -> {
-                    values.add(((ValueHolder) component).getValue());
+                FacetUtils.invokeOnEditableValueHolder(FacesContext.getCurrentInstance(), inputFacet, (ctx, component) -> {
+                    values.add(((EditableValueHolder) component).getValue());
                     invoked.set(true);
                 });
 
diff --git a/primefaces/src/main/java/org/primefaces/model/FilterMeta.java b/primefaces/src/main/java/org/primefaces/model/FilterMeta.java
index 9e735f3b05..7226d7c156 100644
--- a/primefaces/src/main/java/org/primefaces/model/FilterMeta.java
+++ b/primefaces/src/main/java/org/primefaces/model/FilterMeta.java
@@ -23,16 +23,6 @@
  */
 package org.primefaces.model;
 
-import java.io.Serializable;
-import java.lang.reflect.Array;
-import java.util.Collection;
-import java.util.Objects;
-import javax.el.ELContext;
-import javax.el.MethodExpression;
-import javax.el.ValueExpression;
-import javax.faces.component.ValueHolder;
-import javax.faces.context.FacesContext;
-
 import org.primefaces.component.api.DynamicColumn;
 import org.primefaces.component.api.UIColumn;
 import org.primefaces.component.column.ColumnBase;
@@ -42,6 +32,15 @@ import org.primefaces.model.filter.FunctionFilterConstraint;
 import org.primefaces.model.filter.GlobalFilterConstraint;
 import org.primefaces.util.LangUtils;
 
+import javax.el.ELContext;
+import javax.el.MethodExpression;
+import javax.el.ValueExpression;
+import javax.faces.context.FacesContext;
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Objects;
+
 public class FilterMeta implements Serializable {
 
     public static final String GLOBAL_FILTER_KEY = "globalFilter";
@@ -100,10 +99,7 @@ public class FilterMeta implements Serializable {
 
         Object filterValue = column.getFilterValue();
         if (filterValue == null) {
-            ValueHolder valueHolder = column.getFilterValueHolder();
-            if (valueHolder != null) {
-                filterValue = valueHolder.getValue();
-            }
+            filterValue = column.getFilterValueFromValueHolder();
         }
 
         return new FilterMeta(column.getColumnKey(),
diff --git a/primefaces/src/main/java/org/primefaces/util/ComponentTraversalUtils.java b/primefaces/src/main/java/org/primefaces/util/ComponentTraversalUtils.java
index 03b5fb2c76..36f48a102a 100644
--- a/primefaces/src/main/java/org/primefaces/util/ComponentTraversalUtils.java
+++ b/primefaces/src/main/java/org/primefaces/util/ComponentTraversalUtils.java
@@ -23,11 +23,15 @@
  */
 package org.primefaces.util;
 
+import javax.faces.component.ContextCallback;
+import javax.faces.component.NamingContainer;
+import javax.faces.component.UIComponent;
+import javax.faces.component.UIForm;
+import javax.faces.component.UniqueIdVendor;
+import javax.faces.context.FacesContext;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
-import javax.faces.component.*;
-import javax.faces.context.FacesContext;
 
 public class ComponentTraversalUtils {
 
@@ -190,17 +194,6 @@ public class ComponentTraversalUtils {
         return (UIComponent) closest(NamingContainer.class, component);
     }
 
-    public static <T> T firstChildRenderedOrSelf(Class<T> childType, UIComponent base) {
-        if (base == null || !base.isRendered()) {
-            return null;
-        }
-        else if (childType.isInstance(base)) {
-            return (T) base;
-        }
-
-        return firstChildRendered(childType, base);
-    }
-
     public static <T> T firstChildRendered(Class<T> childType, UIComponent base) {
         if (base == null || !base.isRendered()) {
             return null;
diff --git a/primefaces/src/main/java/org/primefaces/util/FacetUtils.java b/primefaces/src/main/java/org/primefaces/util/FacetUtils.java
index c8a612232a..08e134b30e 100644
--- a/primefaces/src/main/java/org/primefaces/util/FacetUtils.java
+++ b/primefaces/src/main/java/org/primefaces/util/FacetUtils.java
@@ -24,9 +24,11 @@
 package org.primefaces.util;
 
 import javax.faces.component.ContextCallback;
+import javax.faces.component.EditableValueHolder;
 import javax.faces.component.UIComponent;
 import javax.faces.component.UIPanel;
-import javax.faces.component.ValueHolder;
+import javax.faces.component.visit.VisitContext;
+import javax.faces.component.visit.VisitResult;
 import javax.faces.context.FacesContext;
 
 public class FacetUtils {
@@ -78,27 +80,25 @@ public class FacetUtils {
         return shouldRenderFacet(facet, false);
     }
 
-    public static void invokeOnValueHolder(FacesContext context, UIComponent facet, ContextCallback callback) {
-        // the facet contains multiple childs, so its wrapped inside a UIPanel
-        if (facet instanceof UIPanel) {
-            for (int i = 0; i < facet.getChildCount(); i++) {
-                UIComponent child = facet.getChildren().get(i);
-                if (child instanceof ValueHolder) {
-                    callback.invokeContextCallback(context, child);
-                }
-            }
-        }
-        // the facet contains only one child now, which means that the facet is the child component
-        else {
-            // the child is a ValueHolder, independent if it's a composite or not
-            if (facet instanceof ValueHolder) {
-                callback.invokeContextCallback(context, facet);
+    public static void invokeOnEditableValueHolder(FacesContext context, UIComponent facet, ContextCallback callback) {
+
+        VisitContext visitContext = VisitContext.createVisitContext(context, null,
+                ComponentUtils.VISIT_HINTS_SKIP_UNRENDERED);
+
+        // loop through all facet components
+        // sometimes its not enough to check the first component as it may be wrapped in some layout-components
+        facet.visitTree(visitContext, (ctx, component) -> {
+            // the child is a EditableValueHolder, independent if it's a composite or not
+            if (component instanceof EditableValueHolder) {
+                callback.invokeContextCallback(context, component);
             }
             // try to check for cc:editableValueHolder
-            else if (CompositeUtils.isComposite(facet)) {
-                CompositeUtils.invokeOnDeepestEditableValueHolder(context, facet, callback);
+            else if (CompositeUtils.isComposite(component)) {
+                CompositeUtils.invokeOnDeepestEditableValueHolder(context, component, callback);
             }
-        }
+
+            return VisitResult.ACCEPT;
+        });
     }
 
 }
diff --git a/primefaces/src/main/java/org/primefaces/util/FileUploadUtils.java b/primefaces/src/main/java/org/primefaces/util/FileUploadUtils.java
index a827902416..f06c5591e6 100644
--- a/primefaces/src/main/java/org/primefaces/util/FileUploadUtils.java
+++ b/primefaces/src/main/java/org/primefaces/util/FileUploadUtils.java
@@ -24,6 +24,8 @@
 package org.primefaces.util;
 
 import java.io.*;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -102,6 +104,7 @@ public class FileUploadUtils {
             throw validationError("Invalid path: (" + filePath + ") contains unprintable character: " + ch);
         }
 
+        filePath = URLDecoder.decode(filePath, StandardCharsets.UTF_8);
         if (PERCENTS_PAT.matcher(filePath).find()) {
             throw validationError("Invalid path: " + filePath);
         }
diff --git a/primefaces/src/main/resources/META-INF/resources/primefaces/core/core.env.js b/primefaces/src/main/resources/META-INF/resources/primefaces/core/core.env.js
index a2905cb598..8980d288f1 100644
--- a/primefaces/src/main/resources/META-INF/resources/primefaces/core/core.env.js
+++ b/primefaces/src/main/resources/META-INF/resources/primefaces/core/core.env.js
@@ -36,6 +36,12 @@ if (!PrimeFaces.env) {
          * @type {boolean}
          */
         preferredColorSchemeLight : false,
+        
+        /**
+         * `true` if the user's current OS setting prefers reduced motion or animations, `false` otherwise.
+         * @type {boolean}
+         */
+        prefersReducedMotion : false,
 
         /**
          * Initializes the environment by reading the browser environment.
@@ -47,6 +53,7 @@ if (!PrimeFaces.env) {
             this.ios = /iPhone|iPad|iPod/i.test(window.navigator.userAgent) || (/mac/i.test(window.navigator.userAgent) && PrimeFaces.env.touch);
             this.preferredColorSchemeDark = PrimeFaces.env.evaluateMediaQuery('(prefers-color-scheme: dark)');
             this.preferredColorSchemeLight = !this.preferredColorSchemeDark;
+            this.prefersReducedMotion =  PrimeFaces.env.evaluateMediaQuery('(prefers-reduced-motion: reduce)');
         },
 
        /**
diff --git a/primefaces/src/main/resources/META-INF/resources/primefaces/core/core.utils.js b/primefaces/src/main/resources/META-INF/resources/primefaces/core/core.utils.js
index 2a59f1830c..634ad27b1b 100644
--- a/primefaces/src/main/resources/META-INF/resources/primefaces/core/core.utils.js
+++ b/primefaces/src/main/resources/META-INF/resources/primefaces/core/core.utils.js
@@ -1059,4 +1059,9 @@ if (!PrimeFaces.utils) {
         },
     };
 
+    // set animation state globally
+    if (PrimeFaces.env.prefersReducedMotion) {
+        PrimeFaces.utils.disableAnimations();
+        PrimeFaces.warn("Animations are disabled because OS has requested prefers-reduced-motion: reduce")
+    }
 }


Test patch:
diff --git a/primefaces-integration-tests/src/main/webapp/datatable/dataTable043.xhtml b/primefaces-integration-tests/src/main/webapp/datatable/dataTable043.xhtml
new file mode 100644
index 0000000000..e3cea53a5d
--- /dev/null
+++ b/primefaces-integration-tests/src/main/webapp/datatable/dataTable043.xhtml
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:h="http://xmlns.jcp.org/jsf/html"
+      xmlns:f="http://xmlns.jcp.org/jsf/core"
+      xmlns:p="http://primefaces.org/ui"
+      xmlns:composite="http://xmlns.jcp.org/jsf/composite/composite">
+
+<f:view contentType="text/html;charset=UTF-8" encoding="UTF-8">
+    <h:head>
+
+    </h:head>
+
+    <h:body>
+
+        <h:form id="form">
+            <p:dataTable id="datatable" widgetVar="wgtTable" value="#{dataTable001.progLanguages}" var="lang" paginator="true" rows="#{dataTable001.rows}"
+                         filteredValue="#{dataTable001.filteredProgLanguages}" 
+                         filterEvent="enter"
+                         globalFilterOnly="#{dataTable001.globalFilterOnly}"
+                         paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
+                         rowsPerPageTemplate="2,3,5,10">
+                <f:facet name="header">
+                    <div class="p-d-flex p-ai-center p-jc-between">
+                        <span>Composite Filter</span>
+                        <composite:dataTableFilter id="globalFilter"/>
+                    </div>
+                </f:facet>
+
+                <p:column headerText="ID" sortBy="#{lang.id}">
+                    <h:outputText value="#{lang.id}"/>
+                </p:column>
+
+                <p:column field="type" />
+
+                <p:column headerText="Name" sortBy="#{lang.name}" filterBy="#{lang.name}" filterMatchMode="contains">
+                    <h:outputText value="#{lang.name}"/>
+                    <f:facet name="filter">
+                       <composite:dataTableFilter id="nameFilter"/>
+                    </f:facet>
+                </p:column>
+
+                <p:column headerText="First appeared" sortBy="#{lang.firstAppeared}" filterBy="#{lang.firstAppeared}" filterMatchMode="gte">
+                    <h:outputText value="#{lang.firstAppeared}"/>
+                </p:column>
+            </p:dataTable>
+
+            <p:commandButton id="button" value="Submit" update="@form"/>
+            <p:commandButton id="buttonUpdate" value="Update" update="datatable"/>
+            <p:commandButton id="buttonResetTable" value="Reset" update="@form" action="#{dataTable001.resetTable()}"/>
+            <p:commandButton id="buttonGlobalFilterOnly" value="Global Filter Only" update="@form" action="#{dataTable001.toggleGlobalFilter()}"/>
+        </h:form>
+
+    </h:body>
+</f:view>
+
+</html>
diff --git a/primefaces-integration-tests/src/main/webapp/resources/composite/dataTableFilter.xhtml b/primefaces-integration-tests/src/main/webapp/resources/composite/dataTableFilter.xhtml
new file mode 100644
index 0000000000..0d0fec628f
--- /dev/null
+++ b/primefaces-integration-tests/src/main/webapp/resources/composite/dataTableFilter.xhtml
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:cc="http://xmlns.jcp.org/jsf/composite"
+      xmlns:p="http://primefaces.org/ui">
+
+<cc:interface>
+</cc:interface>
+
+<cc:implementation>
+    <p:inputText id="filter" type="search"
+                 onchange="PF('wgtTable').filter()"
+                 style="width:15rem; border-color: red;"
+                 placeholder="Composite filter..."/>
+</cc:implementation>
+</html>
diff --git a/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/datatable/DataTable043Test.java b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/datatable/DataTable043Test.java
new file mode 100644
index 0000000000..bb38a7c30c
--- /dev/null
+++ b/primefaces-integration-tests/src/test/java/org/primefaces/integrationtests/datatable/DataTable043Test.java
@@ -0,0 +1,175 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2023 PrimeTek Informatics
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.integrationtests.datatable;
+
+import org.json.JSONObject;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.Select;
+import org.primefaces.integrationtests.datatable.ProgrammingLanguage.ProgrammingLanguageType;
+import org.primefaces.selenium.AbstractPrimePage;
+import org.primefaces.selenium.PrimeSelenium;
+import org.primefaces.selenium.component.CommandButton;
+import org.primefaces.selenium.component.DataTable;
+import org.primefaces.selenium.component.InputText;
+
+import java.util.Comparator;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ *
+ * DataTable #11135: support composite as filter.
+ *
+ */
+class DataTable043Test extends AbstractDataTableTest {
+
+    @Test
+    @Order(1)
+    @DisplayName("DataTable #11135: support composite as filter")
+    void filter(Page page) {
+        // Arrange
+        DataTable dataTable = page.dataTable;
+        dataTable.selectPage(1);
+        dataTable.sort("Name");
+
+        // Act
+        dataTable.filter("Name", "Java");
+
+        // Assert
+        List<ProgrammingLanguage> langsFiltered = filterByName("Java");
+        assertRows(dataTable, langsFiltered);
+
+        // Act
+        page.buttonUpdate.click();
+
+        // Assert - filter must not be lost after update
+        assertRows(dataTable, langsFiltered);
+
+        assertConfiguration(dataTable.getWidgetConfiguration());
+    }
+
+    @Test
+    @Order(2)
+    @DisplayName("DataTable #11135: support composite as filter plus paging")
+    void filterPlusPaging(Page page) {
+        // Arrange
+        DataTable dataTable = page.dataTable;
+        dataTable.selectPage(1);
+        dataTable.sort("Name");
+        Select selectRowsPerPage = new Select(dataTable.getPaginatorWebElement().findElement(By.className("ui-paginator-rpp-options")));
+        PrimeSelenium.guardAjax(selectRowsPerPage).selectByValue("2");
+
+        // Act
+        dataTable.filter("Name", "t");
+
+        // Assert
+        List<ProgrammingLanguage> langsFiltered = languages.stream()
+                .sorted(Comparator.comparing(ProgrammingLanguage::getName))
+                .filter(l -> l.getName().toLowerCase().contains("t"))
+                .collect(Collectors.toList());
+        assertRows(dataTable, langsFiltered.stream().limit(2).collect(Collectors.toList()));
+
+        // Act
+        dataTable.selectPage(2);
+
+        // Assert - filter must not be lost after update
+        assertRows(dataTable, langsFiltered.stream().skip(2).limit(2).collect(Collectors.toList()));
+
+        assertConfiguration(dataTable.getWidgetConfiguration());
+    }
+
+    @Test
+    @Order(3)
+    @DisplayName("DataTable #11135: support composite global filter with globalFilterOnly=false")
+    void globalFilter(Page page) {
+        // Arrange
+        DataTable dataTable = page.dataTable;
+        WebElement globalFilter = page.globalFilter;
+        assertNotNull(globalFilter);
+        dataTable.selectPage(1);
+        dataTable.sort("Name");
+
+        // Act
+        filterGlobal(globalFilter, "Python");
+
+        // Assert
+        List<ProgrammingLanguage> langsFiltered = filterByName("Python");
+        assertRows(dataTable, langsFiltered);
+        assertConfiguration(dataTable.getWidgetConfiguration());
+    }
+
+    @Test
+    @Order(4)
+    @DisplayName("DataTable #11135: support composite global filter with globalFilterOnly=true")
+    void globalFilterIncludeNotDisplayedFilter(Page page) {
+        // Arrange
+        DataTable dataTable = page.dataTable;
+        WebElement globalFilter = page.globalFilter;
+        assertNotNull(globalFilter);
+        dataTable.selectPage(1);
+        dataTable.sort("Type");
+
+        // Act
+        page.buttonGlobalFilterOnly.click();
+        filterGlobal(globalFilter, ProgrammingLanguageType.INTERPRETED.name());
+
+        // Assert
+        List<ProgrammingLanguage> langsFiltered = filterByType(ProgrammingLanguageType.INTERPRETED);
+        assertRows(dataTable, langsFiltered);
+        assertConfiguration(dataTable.getWidgetConfiguration());
+    }
+
+
+    private void assertConfiguration(JSONObject cfg) {
+        assertNoJavascriptErrors();
+        System.out.println("DataTable Config = " + cfg);
+        assertTrue(cfg.has("paginator"));
+        assertEquals("wgtTable", cfg.getString("widgetVar"));
+        assertEquals(0, cfg.getInt("tabindex"));
+    }
+
+    public static class Page extends AbstractPrimePage {
+        @FindBy(id = "form:datatable")
+        DataTable dataTable;
+        @FindBy(id = "form:datatable:globalFilter:filter")
+        InputText globalFilter;
+        @FindBy(id = "form:buttonUpdate")
+        CommandButton buttonUpdate;
+        @FindBy(id = "form:buttonGlobalFilterOnly")
+        CommandButton buttonGlobalFilterOnly;
+
+        @Override
+        public String getLocation() {
+            return "datatable/dataTable043.xhtml";
+        }
+    }
+}
diff --git a/primefaces/src/test/java/org/primefaces/util/FileUploadUtilsTest.java b/primefaces/src/test/java/org/primefaces/util/FileUploadUtilsTest.java
index 9ca37091d3..9d9ad244dd 100644
--- a/primefaces/src/test/java/org/primefaces/util/FileUploadUtilsTest.java
+++ b/primefaces/src/test/java/org/primefaces/util/FileUploadUtilsTest.java
@@ -175,6 +175,31 @@ class FileUploadUtilsTest {
         }
     }
 
+    @Test
+    void requireValidFilePath_AbsoluteFileWithWindowsSpaces() {
+        // Unix systems can start with / but Windows cannot
+        String os = System.getProperty("os.name").toLowerCase();
+
+        // Arrange
+        String relativePath = "/D:/Development/OS%20Projects/primefaces/primefaces/target/test-classes/test.png";
+
+        // Act
+        if (os.contains("win")) {
+            try {
+                FileUploadUtils.requireValidFilePath(relativePath, false);
+                fail("File was absolute path and should have failed");
+            }
+            catch (ValidatorException e) {
+                // Assert
+                assertEquals("Invalid directory, name does not match the canonical path.", e.getFacesMessage().getDetail());
+            }
+        }
+        else {
+            String result = FileUploadUtils.requireValidFilePath(relativePath, false);
+            assertTrue(result.endsWith("test.png"));
+        }
+    }
+
     @Test
     void requireValidFilePath_PathTraversal() {
         // Arrange