File tree Expand file tree Collapse file tree 6 files changed +89
-6
lines changed
Expand file tree Collapse file tree 6 files changed +89
-6
lines changed Original file line number Diff line number Diff line change 66* Feat: Add Plugin options page.
77* Feat: Add Shortcut command (CMD + F).
88* Feat: Add custom hooks: ` afterSearchReplace ` , ` excludedPostTypes ` , ` regexPattern ` .
9+ * Fix: Make default search literal & regex opt-in.
910* Refactor: Use ` replaceInput ` in place of repeated instances of ` text ` .
1011* Test: Add e2e tests for plugin codebase.
1112* Tested up to WP 6.9.
Original file line number Diff line number Diff line change @@ -69,6 +69,7 @@ Want to add your personal touch? All of our documentation can be found [here](ht
6969* Feat: Add Plugin options page.
7070* Feat: Add Shortcut command (CMD + F).
7171* Feat: Add custom hooks: `afterSearchReplace`, `excludedPostTypes`, `regexPattern`.
72+ * Fix: Make default search literal & regex opt-in.
7273* Refactor: Use `replaceInput` in place of repeated instances of `text`.
7374* Test: Add e2e tests for plugin codebase.
7475* Tested up to WP 6.9.
Original file line number Diff line number Diff line change @@ -13,6 +13,8 @@ import {
1313
1414import { Shortcut } from './shortcut' ;
1515import {
16+ getPattern ,
17+ escapeRegex ,
1618 getAllowedBlocks ,
1719 getBlockEditorIframe ,
1820 ifIsCaseSensitiveBasedOnFilter ,
@@ -169,13 +171,27 @@ const SearchReplaceForBlockEditor = (): JSX.Element => {
169171 }
170172
171173 // Prepare raw string pattern.
172- const rawPattern = `(?<!<[^>]*)${ searchInput } (?<![^>]*<)` ;
174+ const rawPattern = getPattern (
175+ isRegexExpression ? searchInput : escapeRegex ( searchInput )
176+ ) ;
177+
178+ // Is Search case sensitive.
179+ const isSearchCaseSensitive =
180+ ifIsCaseSensitiveBasedOnFilter ( ) || isCaseSensitive ? 'g' : 'gi' ;
181+
182+ // Define pattern.
183+ let regexPattern : RegExp ;
173184
174185 // Get Regex search pattern.
175- const regexPattern : RegExp = new RegExp (
176- rawPattern ,
177- ifIsCaseSensitiveBasedOnFilter ( ) || isCaseSensitive ? 'g' : 'gi'
178- ) ;
186+ try {
187+ regexPattern = new RegExp ( rawPattern , isSearchCaseSensitive ) ;
188+ } catch ( error ) {
189+ // fallback to non-regex pattern.
190+ regexPattern = new RegExp (
191+ getPattern ( escapeRegex ( searchInput ) ) ,
192+ isSearchCaseSensitive
193+ ) ;
194+ }
179195
180196 /**
181197 * Filter the way we set the pattern, let users
Original file line number Diff line number Diff line change @@ -73,7 +73,7 @@ addFilter(
7373 ) ;
7474
7575 return {
76- newAttr : JSON . parse ( tableString ) ,
76+ newAttr : JSON . parse ( tableString || '{}' ) ,
7777 isChanged : tableString !== JSON . stringify ( oldAttr ) ,
7878 } ;
7979
Original file line number Diff line number Diff line change @@ -397,3 +397,34 @@ export const isAllowedForPostType = (): boolean => {
397397
398398 return true ;
399399} ;
400+
401+ /**
402+ * Escape user input for safe
403+ * literal RegExp usage.
404+ *
405+ * @since 1.10.0
406+ *
407+ * @param {string } value Raw user input.
408+ * @return {string } Escaped input.
409+ */
410+ export const escapeRegex = ( value : string ) : string => {
411+ if ( typeof value !== 'string' ) {
412+ return '' ;
413+ }
414+
415+ return value . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ;
416+ } ;
417+
418+ /**
419+ * Get Search Pattern.
420+ *
421+ * This function returns a string pattern
422+ * that targets only texts within valid HTML markup.
423+ *
424+ * @since 1.10.0
425+ *
426+ * @param {string } searchText Search Text.
427+ * @return {string } Search Pattern.
428+ */
429+ export const getPattern = ( searchText : string ) : string =>
430+ `(?<!<[^>]*)${ searchText } (?<![^>]*<)` ;
Original file line number Diff line number Diff line change @@ -391,3 +391,37 @@ describe( 'getShortcutEvent', () => {
391391 expect ( shortcutEvent ) . toBeInstanceOf ( KeyboardEvent ) ;
392392 } ) ;
393393} ) ;
394+
395+ describe ( 'escapeRegex' , ( ) => {
396+ beforeEach ( ( ) => {
397+ jest . resetModules ( ) ;
398+ } ) ;
399+
400+ it ( 'escapeRegex escapes regex metacharacters' , ( ) => {
401+ const { escapeRegex } = require ( '../../../src/core/utils' ) ;
402+
403+ const escaped = escapeRegex ( 'a.b+c*^$()[]{}|\\' ) ;
404+
405+ expect ( escaped ) . toBe ( 'a\\.b\\+c\\*\\^\\$\\(\\)\\[\\]\\{\\}\\|\\\\' ) ;
406+ } ) ;
407+
408+ it ( 'escapeRegex leaves plain text unchanged' , ( ) => {
409+ const { escapeRegex } = require ( '../../../src/core/utils' ) ;
410+
411+ const escaped = escapeRegex ( 'plain text' ) ;
412+
413+ expect ( escaped ) . toBe ( 'plain text' ) ;
414+ } ) ;
415+
416+ it ( 'escapeRegex returns empty string for non-string inputs' , ( ) => {
417+ const { escapeRegex } = require ( '../../../src/core/utils' ) ;
418+
419+ expect ( escapeRegex ( 123 ) ) . toBe ( '' ) ;
420+ } ) ;
421+
422+ it ( 'escapeRegex returns empty string for empty input' , ( ) => {
423+ const { escapeRegex } = require ( '../../../src/core/utils' ) ;
424+
425+ expect ( escapeRegex ( '' ) ) . toBe ( '' ) ;
426+ } ) ;
427+ } ) ;
You can’t perform that action at this time.
0 commit comments