Repository: primefaces/primefaces
Pull request #13250 (2025-01-25 18:17:54)

#13230, #13224 - allowType error message improvements
- ignored regexp flags
- human readable for extensions regexps "zip|xls" -> ".zip, .xls"
- complex regexp returned as-is

Fix https://github.com/primefaces/primefaces/issues/13230
Fix https://github.com/primefaces/primefaces/issues/13224


Patch:
diff --git a/primefaces/src/main/java/org/primefaces/component/steps/Steps.java b/primefaces/src/main/java/org/primefaces/component/steps/Steps.java
index b6968005a4..83f11d5fcd 100644
--- a/primefaces/src/main/java/org/primefaces/component/steps/Steps.java
+++ b/primefaces/src/main/java/org/primefaces/component/steps/Steps.java
@@ -39,6 +39,7 @@ public class Steps extends StepsBase {
     public static final String INACTIVE_ITEM_CLASS = "ui-steps-item ui-state-default ui-state-disabled";
     public static final String ACTIVE_ITEM_CLASS = "ui-steps-item ui-state-highlight";
     public static final String VISITED_ITEM_CLASS = "ui-steps-item ui-state-default";
+    public static final String EXECUTABLE_ITEM_CLASS = "ui-steps-item-executable";
     public static final String STEP_NUMBER_CLASS = "ui-steps-number";
     public static final String STEP_NUMBER_ICON_CLASS = "ui-steps-number-icon";
     public static final String STEP_TITLE_CLASS = "ui-steps-title";
diff --git a/primefaces/src/main/java/org/primefaces/component/steps/StepsRenderer.java b/primefaces/src/main/java/org/primefaces/component/steps/StepsRenderer.java
index daa7f6a45a..5dc40ee1b9 100644
--- a/primefaces/src/main/java/org/primefaces/component/steps/StepsRenderer.java
+++ b/primefaces/src/main/java/org/primefaces/component/steps/StepsRenderer.java
@@ -28,6 +28,7 @@ import org.primefaces.component.menu.BaseMenuRenderer;
 import org.primefaces.model.menu.MenuElement;
 import org.primefaces.model.menu.MenuItem;
 import org.primefaces.util.LangUtils;
+import org.primefaces.util.StyleClassBuilder;
 
 import java.io.IOException;
 import java.util.List;
@@ -76,38 +77,31 @@ public class StepsRenderer extends BaseMenuRenderer {
 
     protected void encodeItem(FacesContext context, Steps steps, MenuItem item, int activeIndex, int index) throws IOException {
         ResponseWriter writer = context.getResponseWriter();
-        String itemClass;
+
+        String containerStyle = item.getContainerStyle();
+        StyleClassBuilder containerStyleClass = getStyleClassBuilder(context)
+                .add(item.getContainerStyleClass());
 
         if (steps.isReadonly()) {
-            itemClass = (index == activeIndex) ? Steps.ACTIVE_ITEM_CLASS : Steps.INACTIVE_ITEM_CLASS;
+            containerStyleClass.add(index == activeIndex, Steps.ACTIVE_ITEM_CLASS, Steps.INACTIVE_ITEM_CLASS);
         }
         else {
             if (index == activeIndex) {
-                if (steps.isActiveStepExecutable()) {
-                    itemClass = Steps.VISITED_ITEM_CLASS;
-                }
-                else {
-                    itemClass = Steps.ACTIVE_ITEM_CLASS;
-                }
+                containerStyleClass.add(Steps.ACTIVE_ITEM_CLASS);
+
+                containerStyleClass.add(steps.isActiveStepExecutable(), Steps.EXECUTABLE_ITEM_CLASS);
             }
             else if (index < activeIndex) {
-                itemClass = Steps.VISITED_ITEM_CLASS;
+                containerStyleClass.add(Steps.VISITED_ITEM_CLASS);
             }
             else {
-                itemClass = Steps.INACTIVE_ITEM_CLASS;
+                containerStyleClass.add(Steps.INACTIVE_ITEM_CLASS);
             }
         }
 
-        String containerStyle = item.getContainerStyle();
-        String containerStyleClass = item.getContainerStyleClass();
-
-        if (containerStyleClass != null) {
-            itemClass = itemClass + " " + containerStyleClass;
-        }
-
         //header container
         writer.startElement("li", null);
-        writer.writeAttribute("class", itemClass, null);
+        writer.writeAttribute("class", containerStyleClass.build(), null);
         writer.writeAttribute("role", "tab", null);
         if (containerStyle != null) {
             writer.writeAttribute("style", containerStyle, null);
@@ -138,7 +132,7 @@ public class StepsRenderer extends BaseMenuRenderer {
             writer.writeAttribute("style", style, null);
         }
 
-        boolean isDisabled = steps.isActiveStepExecutable() ?  activeIndex < index : activeIndex <= index;
+        boolean isDisabled = steps.isActiveStepExecutable() ? activeIndex < index : activeIndex <= index;
         if (steps.isReadonly() || menuitem.isDisabled() || isDisabled) {
             writer.writeAttribute("tabindex", "-1", null);
             writer.writeAttribute("href", "#", null);
diff --git a/primefaces/src/main/java/org/primefaces/util/FileUploadUtils.java b/primefaces/src/main/java/org/primefaces/util/FileUploadUtils.java
index 0ce595d659..23404ea79a 100644
--- a/primefaces/src/main/java/org/primefaces/util/FileUploadUtils.java
+++ b/primefaces/src/main/java/org/primefaces/util/FileUploadUtils.java
@@ -81,6 +81,11 @@ public class FileUploadUtils {
     private static final Pattern INVALID_FILENAME_LINUX = Pattern.compile(".*[/\0:*?\\\"<>|].*");
     private static final Pattern ENCODED_CHARS_PAT = Pattern.compile("(%)([0-9a-fA-F])([0-9a-fA-F])");
 
+    // pattern to format allowedTypes for FileUpload - formats like /.*\.(xls|xlsx|csv|txt)/
+    private static final Pattern ALLOW_TYPES_PATTERN =  Pattern.compile("\\/\\.\\*\\\\.\\(?(.*?)\\)?\\$?\\/");
+    // pattern to format allowedTypes for FileUpload - formats like /(\.|\/)(gif|jpeg|jpg|png)$/
+    private static final Pattern ALLOW_TYPES_PATTERN_2 = Pattern.compile("\\/\\(\\.|\\/\\)\\(?(.*?)\\)?\\$?\\/");
+
     private FileUploadUtils() {
         // private constructor to prevent instantiation
     }
@@ -413,9 +418,39 @@ public class FileUploadUtils {
      * @return The allowTypes formatted in a more human-friendly format.
      */
     public static String formatAllowTypes(String allowTypes) {
-        return allowTypes != null ? allowTypes.replace("/(\\.|\\/)(", "").replace(")$/", "") : null;
+        // emtpy or null
+        if (LangUtils.isBlank(allowTypes)) {
+            return allowTypes;
+        }
+
+        // not a correct regex pattern
+        if (!allowTypes.startsWith("/")) {
+            return allowTypes;
+        }
+
+        // pattern to format allowedTypes for FileUpload - formats like /.*\.(xls|xlsx|csv|txt)/
+        Matcher matcher1 = ALLOW_TYPES_PATTERN.matcher(allowTypes);
+        if (matcher1.find()) {
+            return "." + matcher1.group(1).replace("|", ", .");
+        }
+
+        // pattern to format allowedTypes for FileUpload - formats like /(\.|\/)(gif|jpeg|jpg|png)$/
+        Matcher matcher2 = ALLOW_TYPES_PATTERN_2.matcher(allowTypes);
+        if (matcher2.find()) {
+            return "." + matcher2.group(1).replace("|", ", .");
+        }
+
+        // rest return unchanged
+        return allowTypes;
     }
 
+    /**
+     * Formats the given data size in a more human-friendly format, e.g., `1.5 MB` etc.
+     * @param bytes File size in bytes to format
+     * @param locale The locale to use for number formatting
+     * @return The given file size, formatted in a more human-friendly format. Returns empty string if bytes is null,
+     *         or "N/A" if bytes is 0.
+     */
     public static String formatBytes(Long bytes, Locale locale) {
         if (bytes == null) {
             return "";
@@ -425,7 +460,7 @@ public class FileUploadUtils {
             return "N/A";
         }
 
-        String[] sizes = new String[] {"Bytes", "KB", "MB", "GB", "TB"};
+        String[] sizes = new String[] {"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
         int i = (int) Math.floor(Math.log(bytes) / Math.log(1024));
         if (i == 0) {
             return bytes + " " + sizes[i];
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 515acfab21..3f2ee45340 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
@@ -896,7 +896,29 @@ if (!PrimeFaces.utils) {
          * @return {string} The allowTypes formatted in a more human-friendly format.
          */
         formatAllowTypes: function(allowTypes) {
-            return allowTypes === undefined ? '' : allowTypes.replace("/(\\.|\\/)(", "").replace(")$/", "");
+            if (!allowTypes) {
+                return allowTypes;
+            }
+
+            // not a correct regex pattern
+            if (!allowTypes.startsWith('/')) {
+                return allowTypes;
+            }
+
+            // formats like /(\.|\/)(gif|jpeg|jpg|png)$/
+            let match = allowTypes.match(/\/\(\\\.\|\\\/\)\(?(.*?)\)?\$?\//);
+            if (match) {
+                return '.' + match[1].replace(/\|/g, ', .');
+            }
+
+            // formats like .*\.(xls|xlsx|csv|txt)
+            match = allowTypes.match(/\/\.\*\\\.\(?(.*?)\)?\$?\//);
+            if (match) {
+                return '.' + match[1].replace(/\|/g, ', .');
+            }
+
+            // others return unchanged
+            return allowTypes;
         },
 
         /**
@@ -911,7 +933,7 @@ if (!PrimeFaces.utils) {
             if (bytes === 0)
                 return 'N/A';
 
-            var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
+            var sizes = PrimeFaces.getLocaleLabel('fileSizeTypes');
             var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
             if (i === 0)
                 return bytes + ' ' + sizes[i];
diff --git a/primefaces/src/main/resources/META-INF/resources/primefaces/datepicker/0-datepicker.js b/primefaces/src/main/resources/META-INF/resources/primefaces/datepicker/0-datepicker.js
index c549e60f2b..4d48666faa 100644
--- a/primefaces/src/main/resources/META-INF/resources/primefaces/datepicker/0-datepicker.js
+++ b/primefaces/src/main/resources/META-INF/resources/primefaces/datepicker/0-datepicker.js
@@ -1333,7 +1333,7 @@
         renderMonthView: function() {
             var backwardNavigator = this.renderBackwardNavigator(this.options.locale.prevYear),
                 forwardNavigator = this.renderForwardNavigator(this.options.locale.nextYear),
-                yearElement = this.renderTitleYearElement(this.viewDate.getFullYear()),
+                yearElement = this.renderTitleYearElement(this.viewDate.getFullYear(), 0),
                 months = this.renderMonthViewMonths();
 
             return ('<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix">' +
diff --git a/primefaces/src/main/resources/META-INF/resources/primefaces/menu/menu.css b/primefaces/src/main/resources/META-INF/resources/primefaces/menu/menu.css
index 0c11932fb7..30db90905b 100644
--- a/primefaces/src/main/resources/META-INF/resources/primefaces/menu/menu.css
+++ b/primefaces/src/main/resources/META-INF/resources/primefaces/menu/menu.css
@@ -515,7 +515,7 @@ body .ui-menu .ui-divider.ui-divider-horizontal {
     text-align: center;
 }
 
-.ui-steps .ui-steps-item.ui-state-highlight .ui-menuitem-link,
+.ui-steps .ui-steps-item.ui-state-highlight:not(.ui-steps-item-executable) .ui-menuitem-link,
 .ui-steps .ui-steps-item.ui-state-disabled .ui-menuitem-link {
     cursor: default;
 }


Test patch:
diff --git a/primefaces/src/test/java/org/primefaces/util/FileUploadUtilsTest.java b/primefaces/src/test/java/org/primefaces/util/FileUploadUtilsTest.java
index afcbfbcedb..75cd56e5c0 100644
--- a/primefaces/src/test/java/org/primefaces/util/FileUploadUtilsTest.java
+++ b/primefaces/src/test/java/org/primefaces/util/FileUploadUtilsTest.java
@@ -33,6 +33,7 @@ import java.io.InputStream;
 import java.net.URISyntaxException;
 import java.nio.file.Paths;
 import java.util.Locale;
+import java.util.stream.Stream;
 
 import javax.faces.FacesException;
 import javax.faces.application.Application;
@@ -43,6 +44,9 @@ import javax.faces.validator.ValidatorException;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 
@@ -426,8 +430,8 @@ class FileUploadUtilsTest {
 
     @Test
     void formatBytes() {
-        assertEquals("1000 Bytes", FileUploadUtils.formatBytes(1000L, Locale.US));
-        assertEquals("1.0 KB", FileUploadUtils.formatBytes(1025L, Locale.US));
+        assertEquals("1000 B", FileUploadUtils.formatBytes(1000L, Locale.US));
+        assertEquals("1.0 kB", FileUploadUtils.formatBytes(1025L, Locale.US));
     }
 
     @Test
@@ -439,4 +443,36 @@ class FileUploadUtilsTest {
         assertThrows(FacesException.class, () -> FileUploadUtils.requireValidFilename(context, "someFil<eWithUmlautsßöäü.txt"));
     }
 
+    private static Stream<Arguments> allowTypesTestSource() {
+        return Stream.of(
+                Arguments.of("/(\\.|\\/)(gif|jpeg|jpg|png)$/", ".gif, .jpeg, .jpg, .png"),
+                Arguments.of("/(\\.|\\/)(gif|jpe?g|png)$/", ".gif, .jpe?g, .png"),
+                Arguments.of("/(\\.|\\/)(xls|xlsx)$/", ".xls, .xlsx"),
+                Arguments.of("/(\\.|\\/)(xls|xlsx)$/i", ".xls, .xlsx"),
+                Arguments.of("/(\\.|\\/)(xls)$/", ".xls"),
+                Arguments.of("/(\\.|\\/)(xls)$/i", ".xls"),
+                Arguments.of("/(\\.|\\/)(xls)/", ".xls"),
+                Arguments.of("/.*\\.csv/", ".csv"),
+                Arguments.of("/.*\\.(txt|lnc)/", ".txt, .lnc"),
+                Arguments.of("/.*\\.(xls|xlsx|csv|txt)$/", ".xls, .xlsx, .csv, .txt"),
+                Arguments.of("/.*\\.(zip|xlsx?|csv)/", ".zip, .xlsx?, .csv"),
+                Arguments.of(null, null),
+                Arguments.of("//", "//"),
+                Arguments.of("/.*/", "/.*/"),
+                Arguments.of("", ""),
+                Arguments.of("/(?i)(.*file.*)\\.(xml)/", "/(?i)(.*file.*)\\.(xml)/"),
+                Arguments.of("/[A-Za-z0-9]{2}E[0-9]+\\.[Zz][Ii][Pp]/", "/[A-Za-z0-9]{2}E[0-9]+\\.[Zz][Ii][Pp]/"),
+                Arguments.of("/localizationsImport_[0-9]{1,2}\\.[0-9]{1,2}\\.[0-9]{4}_[0-9]{1,2}\\.[0-9]{1,2}\\.[0-9]{4}\\.zip/",
+                        "/localizationsImport_[0-9]{1,2}\\.[0-9]{1,2}\\.[0-9]{4}_[0-9]{1,2}\\.[0-9]{1,2}\\.[0-9]{4}\\.zip/")
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("allowTypesTestSource")
+    void allowTypes(String input, String expected) {
+        assertEquals(expected, FileUploadUtils.formatAllowTypes(input));
+    }
+
+
+
 }
\ No newline at end of file