Repository: primefaces/primefaces (java)

Issue:
InputMask: Problem escaping characters and validateMask
### Describe the bug

The next example is not working if validateMask is true:
```xml
<p:inputMask value="#{bean.value}" mask="\9.99" validateMask="true" required="true"/>
```
The submitted value is set to empty because the server side validation doesn't support escaping:
https://github.com/primefaces/primefaces/blob/d678b4ce99a6a8f67b340297ac81b1b40c1fb2bf/primefaces/src/main/java/org/primefaces/component/inputmask/InputMaskRenderer.java#L102

To escape a character is supported by the plugin: https://github.com/RobinHerbots/Inputmask/issues/2601

### Reproducer

[primefaces-escape-character-inputmask.zip](https://github.com/primefaces/primefaces/files/14525122/primefaces-escape-character-inputmask.zip)

1. Run the project `mvn clean jetty:run -Pmojarra23`
2. Open http://localhost:8080/primefaces-test/
3. Enter a value and click first button, value is submitted without error when validateMask is false.
4. Enter a value and click second button, validation on server side fails if validateMask is true.

### Expected behavior

The entered value shouldn't fail the validation on server side.

### PrimeFaces edition

Community

### PrimeFaces version

13.0.7

### Theme

_No response_

### JSF implementation

Mojarra

### JSF version

2.3

### Java version

8

### Browser(s)

_No response_

Pull request:
Fix #11590: 13.0.8 InputMask handle escape character '\'
Fix #11590: 13.0.8 InputMask handle escape character '\'

Patch:
diff --git a/primefaces/src/main/java/org/primefaces/component/inputmask/InputMaskRenderer.java b/primefaces/src/main/java/org/primefaces/component/inputmask/InputMaskRenderer.java
index ddcbde13eb..5f9c697929 100644
--- a/primefaces/src/main/java/org/primefaces/component/inputmask/InputMaskRenderer.java
+++ b/primefaces/src/main/java/org/primefaces/component/inputmask/InputMaskRenderer.java
@@ -87,23 +87,31 @@ public class InputMaskRenderer extends InputRenderer {
 
     protected Pattern translateMaskIntoRegex(StringBuilder regex, String mask) {
         boolean optionalFound = false;
+        boolean escapeFound = false;
 
         for (char c : mask.toCharArray()) {
             if (c == '[' || c == ']') {
                 optionalFound = true;
             }
+            else if (c == '\\') {
+                escapeFound = true;
+            }
             else {
-                regex.append(translateMaskCharIntoRegex(c, optionalFound));
+                regex.append(translateMaskCharIntoRegex(c, optionalFound, escapeFound));
+                escapeFound = false;
             }
         }
         return Pattern.compile(regex.toString());
     }
 
-    protected String translateMaskCharIntoRegex(char c, boolean optional) {
+    protected String translateMaskCharIntoRegex(char c, boolean optional, boolean escapeFound) {
         String translated;
 
-        if (c == '[' || c == ']') {
-            return ""; //should be ignored
+        if (escapeFound) {
+            return String.valueOf(c);
+        }
+        else if (c == '[' || c == ']') {
+            return Constants.EMPTY_STRING; //should be ignored
         }
         else if (c == '9') {
             translated = "[0-9]";
diff --git a/primefaces/src/main/resources/META-INF/resources/primefaces/core/core.resources.js b/primefaces/src/main/resources/META-INF/resources/primefaces/core/core.resources.js
index 9f74802774..459b38ecfe 100644
--- a/primefaces/src/main/resources/META-INF/resources/primefaces/core/core.resources.js
+++ b/primefaces/src/main/resources/META-INF/resources/primefaces/core/core.resources.js
@@ -112,16 +112,26 @@ if (!PrimeFaces.resources) {
            * @return {string} The first JavasScript resource URI.
            */
           getResourceScriptURI : function() {
-             if (!PrimeFaces.resources.SCRIPT_URI) {
-                PrimeFaces.resources.SCRIPT_URI =
-                   $('script[src*="/' + PrimeFaces.RESOURCE_IDENTIFIER + '/"]').first().attr('src');
-
-                // portlet
-                if (!PrimeFaces.resources.SCRIPT_URI) {
-                   PrimeFaces.resources.SCRIPT_URI = $('script[src*="' + PrimeFaces.RESOURCE_IDENTIFIER + '="]').first().attr('src');
-                }
-             }
-             return PrimeFaces.resources.SCRIPT_URI;
+              if (!PrimeFaces.resources.SCRIPT_URI) {
+                  function findScriptWithVersionParam(scripts) {
+                      scripts.each(function() {
+                          var src = $(this).attr('src');
+                          if (src && src.indexOf('v=') !== -1) {
+                              PrimeFaces.resources.SCRIPT_URI = src;
+                              return false; // Exit the loop early
+                          }
+                      });
+                  }
+
+                  // normal '/showcase/javax.faces.resource/jquery/jquery.js.xhtml?ln=primefaces&v=13.0.5'
+                  findScriptWithVersionParam($('script[src*="/' + PrimeFaces.RESOURCE_IDENTIFIER + '/"]'));
+
+                  // portlet 'javax.faces.resource=jquery/jquery.js.xhtml?ln=primefaces&v=13.0.5'
+                  if (!PrimeFaces.resources.SCRIPT_URI) {
+                      findScriptWithVersionParam($('script[src*="' + PrimeFaces.RESOURCE_IDENTIFIER + '="]'));
+                  }
+              }
+              return PrimeFaces.resources.SCRIPT_URI;
           }
     };
 
diff --git a/primefaces/src/main/resources/META-INF/resources/primefaces/forms/forms.selectoneradio.js b/primefaces/src/main/resources/META-INF/resources/primefaces/forms/forms.selectoneradio.js
index eb39587b8a..dcf39bc692 100644
--- a/primefaces/src/main/resources/META-INF/resources/primefaces/forms/forms.selectoneradio.js
+++ b/primefaces/src/main/resources/META-INF/resources/primefaces/forms/forms.selectoneradio.js
@@ -130,7 +130,8 @@ PrimeFaces.widget.SelectOneRadio = PrimeFaces.widget.BaseWidget.extend({
         var $this = this;
 
         if (!this.cfg.readonly) {
-            this.outputs.filter(':not(.ui-state-disabled)').on('mouseenter.selectOneRadio', function() {
+            this.outputs.filter(':not(.ui-state-disabled)').off('mouseenter.selectOneRadio mouseleave.selectOneRadio click.selectOneRadio')
+            .on('mouseenter.selectOneRadio', function() {
                 $(this).addClass('ui-state-hover');
             })
             .on('mouseleave.selectOneRadio', function() {
@@ -161,7 +162,8 @@ PrimeFaces.widget.SelectOneRadio = PrimeFaces.widget.BaseWidget.extend({
                 e.preventDefault();
             });
 
-            this.labels.filter(':not(.ui-state-disabled)').on('click.selectOneRadio', function(e) {
+            this.labels.filter(':not(.ui-state-disabled)').off('click.selectOneRadio')
+            .on('click.selectOneRadio', function(e) {
                 var target = $(PrimeFaces.escapeClientId($(this).attr('for'))),
                 radio = null;
 
@@ -177,7 +179,8 @@ PrimeFaces.widget.SelectOneRadio = PrimeFaces.widget.BaseWidget.extend({
             });
         }
 
-        this.enabledInputs.on('focus.selectOneRadio', function() {
+        this.enabledInputs.off('focus.selectOneRadio blur.selectOneRadio keydown.selectOneRadio')
+        .on('focus.selectOneRadio', function() {
             var input = $(this),
             radio = input.parent().next();
 


Test patch:
diff --git a/primefaces/src/test/java/org/primefaces/component/inputmask/InputMaskRendererTest.java b/primefaces/src/test/java/org/primefaces/component/inputmask/InputMaskRendererTest.java
index 6ce37ea196..075957fe67 100644
--- a/primefaces/src/test/java/org/primefaces/component/inputmask/InputMaskRendererTest.java
+++ b/primefaces/src/test/java/org/primefaces/component/inputmask/InputMaskRendererTest.java
@@ -45,4 +45,18 @@ class InputMaskRendererTest {
         assertEquals("[0-9][0-9]?[0-9]?[0-9]?", result.pattern());
     }
 
+    @Test
+    void translateMaskIntoRegex_EscapeCharacter_GitHub11590() {
+        // Arrange
+        InputMaskRenderer comp = new InputMaskRenderer();
+        StringBuilder sb = new StringBuilder();
+        String mask = "\\G.9";
+
+        // Act
+        Pattern result = comp.translateMaskIntoRegex(sb, mask);
+
+        // Assert
+        assertEquals("G\\.[0-9]", result.pattern());
+    }
+
 }