Skip to content

Commit 1f1bd78

Browse files
committed
Merge branch 'develop' into release/v4.1.3
2 parents 4aa2df6 + ffe4738 commit 1f1bd78

9 files changed

Lines changed: 62 additions & 50 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ coverage/
1919
.env
2020
*.zip
2121
.qodo
22+
.playwright-mcp/

modules/remediation/actions/attribute.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ class Attribute extends Remediation_Base {
1717
public static string $type = 'attribute';
1818

1919
public function run() : ?DOMDocument {
20-
$element_node = $this->data['global']
21-
? $this->get_element_by_xpath_with_snippet_fallback( $this->data['xpath'], $this->data['find'] )
22-
: $this->get_element_by_xpath( $this->data['xpath'] );
20+
$element_node = $this->get_element_by_xpath_with_snippet_fallback(
21+
$this->data['xpath'] ?? null,
22+
$this->data['find'] ?? null
23+
);
2324

2425
if ( ! $element_node ) {
2526
$this->use_frontend = true;

modules/remediation/actions/replace.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ class Replace extends Remediation_Base {
1717
public static string $type = 'replace';
1818

1919
public function run() : ?DOMDocument {
20-
$element_node = $this->data['global']
21-
? $this->get_element_by_xpath_with_snippet_fallback( $this->data['xpath'], $this->data['find'] )
22-
: $this->get_element_by_xpath( $this->data['xpath'] );
20+
$element_node = $this->get_element_by_xpath_with_snippet_fallback(
21+
$this->data['xpath'] ?? null,
22+
$this->data['find'] ?? null
23+
);
2324

2425
if ( ! $element_node instanceof \DOMElement ) {
2526
$this->use_frontend = true;

modules/remediation/assets/js/actions/attribute.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,11 @@ export class AttributeRemediation extends RemediationBase {
88
action,
99
attribute_name: attributeName,
1010
attribute_value: attributeValue,
11-
global: isGlobal,
11+
find,
1212
} = this.data;
1313

1414
const xpath = originXpath.replace('svg', "*[name()='svg']");
15-
const el =
16-
isGlobal === '1'
17-
? this.getElementByXPathFallbackSnippet(find, xpath)
18-
: this.getElementByXPath(xpath);
15+
const el = this.getElementByXPathFallbackSnippet(find, xpath);
1916
if (!el) {
2017
return false;
2118
}

modules/remediation/assets/js/actions/base.js

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -37,35 +37,36 @@ export class RemediationBase {
3737
}
3838

3939
getElementByXPathFallbackSnippet(snippet, xpath = null) {
40-
// Try XPath first
41-
if (xpath) {
42-
const element = this.getElementByXPath(xpath);
43-
if (element) {
44-
// Pick the one whose outerHTML best matches the snippet
45-
const normalizedSnippet = snippet
46-
.replace(/\s+/g, ' ')
47-
.trim()
48-
.toLowerCase();
49-
const html = element.outerHTML
50-
.replace(/\s+/g, ' ')
51-
.trim()
52-
.toLowerCase();
53-
if (html.includes(normalizedSnippet)) {
54-
return element;
55-
}
40+
const xpathElement = xpath ? this.getElementByXPath(xpath) : null;
41+
const hasSnippet = typeof snippet === 'string' && snippet.trim() !== '';
42+
43+
if (xpathElement && hasSnippet) {
44+
const normalizedSnippet = snippet
45+
.replace(/\s+/g, ' ')
46+
.trim()
47+
.toLowerCase();
48+
const html = xpathElement.outerHTML
49+
.replace(/\s+/g, ' ')
50+
.trim()
51+
.toLowerCase();
52+
if (html.includes(normalizedSnippet)) {
53+
return xpathElement;
5654
}
5755
}
5856

57+
if (!hasSnippet) {
58+
return xpathElement || null;
59+
}
60+
5961
const parser = new DOMParser();
6062
const doc = parser.parseFromString(snippet.trim(), 'text/html');
6163
const parsed = doc.body.firstElementChild;
6264
if (!parsed) {
63-
return null;
65+
return xpathElement || null;
6466
}
6567

6668
const selectorParts = [parsed.tagName.toLowerCase()];
6769

68-
// Add id and class if present
6970
if (parsed.id) {
7071
selectorParts.push(`#${parsed.id}`);
7172
}
@@ -75,8 +76,7 @@ export class RemediationBase {
7576

7677
const selector = selectorParts.join('');
7778

78-
// Try to find it in the document
79-
return document.querySelector(selector);
79+
return document.querySelector(selector) || xpathElement || null;
8080
}
8181

8282
createElement(tag, attributes = [], content = '') {

modules/remediation/assets/js/actions/replace.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,8 @@ export class ReplaceRemediation extends RemediationBase {
1414
}
1515

1616
run() {
17-
const { xpath, find, replace, global: isGlobal } = this.data;
18-
const el =
19-
isGlobal === '1'
20-
? this.getElementByXPathFallbackSnippet(find, xpath)
21-
: this.getElementByXPath(xpath);
17+
const { xpath, find, replace } = this.data;
18+
const el = this.getElementByXPathFallbackSnippet(find, xpath);
2219

2320
if (!el) {
2421
return false;

modules/remediation/classes/remediation-base.php

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ public function exists(): bool {
3434
return $this->get_element_by_xpath( $this->data['xpath'] ) instanceof DOMElement;
3535
}
3636

37+
/**
38+
* Check if the element exists using XPath with a snippet-based fallback.
39+
*
40+
* @return boolean
41+
*/
42+
public function exists_with_fallback(): bool {
43+
return $this->get_element_by_xpath_with_snippet_fallback(
44+
$this->data['xpath'] ?? null,
45+
$this->data['find'] ?? null
46+
) instanceof DOMElement;
47+
}
48+
3749
/**
3850
* get_element_by_xpath
3951
* @param $xpath
@@ -60,18 +72,20 @@ public function get_element_by_xpath( $query ) {
6072
* @return DOMElement|null
6173
*/
6274
public function get_element_by_xpath_with_snippet_fallback( ?string $xpath, ?string $snippet ): ?DOMElement {
63-
if ( ! $xpath ) {
64-
return null;
75+
$element = $xpath ? $this->get_element_by_xpath( $xpath ) : null;
76+
77+
// Without a snippet we can't validate or fall back, so return whatever XPath found.
78+
if ( null === $snippet || '' === $snippet ) {
79+
return $element instanceof DOMElement ? $element : null;
6580
}
6681

67-
$element = $this->get_element_by_xpath( $xpath );
68-
if ( $element && ! $this->element_contains_snippet( $element, $snippet ) ) {
69-
// XPath result doesn't contain the snippet
82+
// If XPath found an element but its outer HTML doesn't contain the snippet, discard it.
83+
if ( $element instanceof DOMElement && ! $this->element_contains_snippet( $element, $snippet ) ) {
7084
$element = null;
7185
}
7286

73-
// Fallback to snippet-based search
74-
if ( ! $element ) {
87+
// Fallback to snippet-based search.
88+
if ( ! $element instanceof DOMElement ) {
7589
$element = $this->get_element_by_snippet( $snippet );
7690
}
7791

@@ -161,8 +175,9 @@ public function create_element( $data ) : DomElement {
161175
public function __construct( DOMDocument $dom, $data ) {
162176
$this->dom = $dom;
163177
$this->data = $data;
164-
// if it's not global and not styles and element does not exist, move the remediation to the Frontend
165-
if ( 'STYLES' !== $this->data['type'] && ! $this->data['global'] && ! $this->exists() ) {
178+
// If it's not styles and the element can't be found by XPath or the snippet fallback,
179+
// move the remediation to the Frontend so a later DOM state can pick it up.
180+
if ( 'STYLES' !== $this->data['type'] && ! $this->exists_with_fallback() ) {
166181
$this->use_frontend = true;
167182
return;
168183
}

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
"focus-trap-react": "^11.0.4",
6969
"get-xpath": "^3.3.0",
7070
"html-react-parser": "^5.2.2",
71-
"mixpanel-browser": "^2.58.0",
71+
"mixpanel-browser": "^2.80.0",
7272
"postcss": "^8.5.6",
7373
"prop-types": "^15.8.1",
7474
"react-colorful": "^5.6.1",

0 commit comments

Comments
 (0)