String locator - Version 2.4.0

Version Description

  • Updated the editor screen, to a design which more closely adheres to the WordPress editor styles.
  • Added support for searching files, even if you are not able to edit them.
  • Added support for jumping to not just line number, but also location inside that line.
  • Added alternative to disable loopback checks when saving changes.
  • Improved performance by using transients instead of option entries (lower memory usage overall).
  • Improved handling of errors with links to some documentation when available.
  • Improved the amount of details about the current file that are shown in the editor.
  • Fixed the search results table to look like a normal table when restoring a search.
Download this release

Release Info

Developer Clorith
Plugin Icon 128x128 String locator
Version 2.4.0
Comparing to
See all releases

Code changes from version 2.3.1 to 2.4.0

changelog.txt CHANGED
@@ -1,121 +1,140 @@
1
- = 2.2.0 =
2
- * Fixed some lingering potential HTTPS issues.
3
- * Fixed result previews not cutting the excerpt making them excessively long.
4
- * Fixed archive file skipping not accounting for letter casing in extensions.
5
- * Introduced common media types to the file skipping procedure.
6
- * Added default timeout periods, the plugin will no longer allow indefinite execution to work around http proxies.
7
- * Added more translatable strings that were missed.
8
- * Added Must-Use Plugins to individual plugin search.
9
- * Made changes to the uninstall routine to ensure we remove any related database entries on removal.
10
-
11
- = 2.1.2 =
12
- * Fix for max execution times some times being interpreted as strings and failing when you it should be able to run indefinitely
13
- * Fix for regex being enabled when you return to the search results, but you hadn't performed a regex search
14
- * Resolved some code issues with functions being called improperly (future proofing)
15
-
16
- = 2.1.1 =
17
- * Improved error messages
18
- * Add regex pattern validation before performing a search
19
- * Fixed bug causing some searches to be identified as regex when they are not, leading to errors
20
- * Fixed a bug that could cause the first file in every search chunk from being ignored
21
-
22
- = 2.1.0 =
23
- * Add support for configurations with infinite execution times
24
- * Better code handling on RTL sites
25
- * Exclude archive files, that we can't modify any way, from searches
26
- * Display file path in the editor to identify which file is being modified
27
- * Add support for RegEx string searches
28
-
29
- = 2.0.3 =
30
- * Added support for HHVM
31
- * Improved inline documentation
32
-
33
- = 2.0.2 =
34
- * Fixed max memory indicators on hosts that do not use shorthands
35
-
36
- = 2.0.1 =
37
- * Fixed a bug where heavy sites would not run searches due to incorrect memory consumption calculations
38
- * Fixed a visual bug when warnings are displayed
39
- * Added error feedback if high execution times or memory consumption is detected before a search is started
40
-
41
- = 2.0.0 =
42
- * Performance enhancement, now also detects memory consumption to avoid exceeding memory limits
43
- * Fixed a warning incorrectly being shown saying files cannot be read
44
- * Better feedback during the search process
45
- * Fixed a longstanding bug with searching single file plugins
46
-
47
- = 1.9.1 =
48
- * Fixes a regression relating to support for older versions of PHP introduced in 1.9.0
49
-
50
- = 1.9.0 =
51
- * Perform batch searches on the server for as long as possible until we get close to the max execution time.
52
- * Fix previous searches not clearing if you don't navigate away.
53
-
54
- = 1.8.2 =
55
- * Compatibility fix for certain versions of PHP that would throw notices
56
- * Reset the search results when you start a new search
57
-
58
- = 1.8.1 =
59
- * Make sure we don't add extra linefeeds to the end of files to prevent sending early headers where files end with the `?>` PHP closing tag
60
-
61
- = 1.8.0 =
62
- * Search everything in $home/wp-content and ignore core files
63
- * Fix searches containing quotes
64
- * Search is now AJAX based to prevent max execution time errors for some users
65
- * Restore previous search also restores the search term and search locations
66
- * Removed the WordPress list tables, they didn't work too well for our purpose
67
- * Also search in file names
68
-
69
- = 1.7.0 =
70
- * Tested with WordPress 4.3
71
- * Made it uses WordPress list tables (because they look nice and I felt adventurous)
72
- * If the preview text is really long, an excerpt is pulled instead of making a massive text blob
73
- * Fixed a typo in a query argument
74
- * Reordered the search result list based on priority
75
-
76
- = 1.6.0 =
77
- * Revert edits if site health degrades as a direct cause of said edit
78
-
79
- = 1.5.0 =
80
- * Return to your search results from the editor, or restore the previous search if you closed the page
81
- * Multisite support
82
- * Made marked text more prominent in the editor for readability
83
- * Fixed rare notice outputs when searching within all plugins/all themes
84
- * Moved older changelog entries to changelog.txt
85
- * Updated translation files to use the correct text domain
86
-
87
- = 1.4.0 =
88
- * Added code references for WordPress function calls
89
- * Added the ability to search recursively from the WordPress root
90
- * Updated textdomain (translations) to use the actual plugin slug
91
-
92
- = 1.3.0 =
93
- * Added search all for themes and plugins
94
- * Refactored code
95
- * Added german translations
96
-
97
- = 1.2.1 =
98
- * Added missing i18n text strings
99
- * Added capability checks for edit screens
100
-
101
- = 1.2.0 =
102
- * Added custom code editor
103
- * Syntax highlighting
104
- * Code validation using Smart-Scan
105
- * Quick jump links to areas with errors detected
106
- * Replaced unused admin notice
107
- * Removed previously used WP Editor checks
108
-
109
- = 1.1.1 =
110
- * Added Spanish translation files added
111
- * Added Serbian translation files added
112
-
113
- = 1.1.0 =
114
- * Added link to the online editor for themes/plugins from search results
115
- * Added extra notification text on editor page when referenced by the plugin, makes it easier to find your search string
116
- * Screenshots and more plugin details added
117
- * Fixed column width for line number being way too large
118
- * Add missing inline comments/function references
119
-
120
- = 1.0.0 =
121
- * Initial release
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ = 2.3.1 =
2
+ This is a maintenance and security release, with thanks to [RIPS Technologies](https://www.ripstech.com) for the responsible disclosure of several security concerns.
3
+
4
+ * Fixed an escaped URL that should've allowed some HTML links.
5
+ * Patched a potential security vulnerability with file path traversals.
6
+ * Patched a potential security vulnerability that allowed writing to arbitrary files.
7
+ * Patched a few Cross Site Scripting (XSS) vulnerabilities.
8
+ * Removed unused code that might allow file creation.
9
+
10
+ = 2.3.0 =
11
+ * Upped version requirement to 4.9 as we now use the bundled CodeMirror in WordPress core.
12
+ * Converted translation functions to the escaping versions to avoid accidental output from translations.
13
+ * Removed bundled languages, these should be served by WordPress.org now.
14
+ * Improved behavior when a search failure happens, we were accidentally looping error messages for every file (whoops).
15
+ * Added more translatable strings.
16
+ * Added various filters:
17
+ ** `string_locator_bad_http_codes`
18
+ ** `string_locator_bad_file_types`
19
+
20
+ = 2.2.0 =
21
+ * Fixed some lingering potential HTTPS issues.
22
+ * Fixed result previews not cutting the excerpt making them excessively long.
23
+ * Fixed archive file skipping not accounting for letter casing in extensions.
24
+ * Introduced common media types to the file skipping procedure.
25
+ * Added default timeout periods, the plugin will no longer allow indefinite execution to work around http proxies.
26
+ * Added more translatable strings that were missed.
27
+ * Added Must-Use Plugins to individual plugin search.
28
+ * Made changes to the uninstall routine to ensure we remove any related database entries on removal.
29
+
30
+ = 2.1.2 =
31
+ * Fix for max execution times some times being interpreted as strings and failing when you it should be able to run indefinitely
32
+ * Fix for regex being enabled when you return to the search results, but you hadn't performed a regex search
33
+ * Resolved some code issues with functions being called improperly (future proofing)
34
+
35
+ = 2.1.1 =
36
+ * Improved error messages
37
+ * Add regex pattern validation before performing a search
38
+ * Fixed bug causing some searches to be identified as regex when they are not, leading to errors
39
+ * Fixed a bug that could cause the first file in every search chunk from being ignored
40
+
41
+ = 2.1.0 =
42
+ * Add support for configurations with infinite execution times
43
+ * Better code handling on RTL sites
44
+ * Exclude archive files, that we can't modify any way, from searches
45
+ * Display file path in the editor to identify which file is being modified
46
+ * Add support for RegEx string searches
47
+
48
+ = 2.0.3 =
49
+ * Added support for HHVM
50
+ * Improved inline documentation
51
+
52
+ = 2.0.2 =
53
+ * Fixed max memory indicators on hosts that do not use shorthands
54
+
55
+ = 2.0.1 =
56
+ * Fixed a bug where heavy sites would not run searches due to incorrect memory consumption calculations
57
+ * Fixed a visual bug when warnings are displayed
58
+ * Added error feedback if high execution times or memory consumption is detected before a search is started
59
+
60
+ = 2.0.0 =
61
+ * Performance enhancement, now also detects memory consumption to avoid exceeding memory limits
62
+ * Fixed a warning incorrectly being shown saying files cannot be read
63
+ * Better feedback during the search process
64
+ * Fixed a longstanding bug with searching single file plugins
65
+
66
+ = 1.9.1 =
67
+ * Fixes a regression relating to support for older versions of PHP introduced in 1.9.0
68
+
69
+ = 1.9.0 =
70
+ * Perform batch searches on the server for as long as possible until we get close to the max execution time.
71
+ * Fix previous searches not clearing if you don't navigate away.
72
+
73
+ = 1.8.2 =
74
+ * Compatibility fix for certain versions of PHP that would throw notices
75
+ * Reset the search results when you start a new search
76
+
77
+ = 1.8.1 =
78
+ * Make sure we don't add extra linefeeds to the end of files to prevent sending early headers where files end with the `?>` PHP closing tag
79
+
80
+ = 1.8.0 =
81
+ * Search everything in $home/wp-content and ignore core files
82
+ * Fix searches containing quotes
83
+ * Search is now AJAX based to prevent max execution time errors for some users
84
+ * Restore previous search also restores the search term and search locations
85
+ * Removed the WordPress list tables, they didn't work too well for our purpose
86
+ * Also search in file names
87
+
88
+ = 1.7.0 =
89
+ * Tested with WordPress 4.3
90
+ * Made it uses WordPress list tables (because they look nice and I felt adventurous)
91
+ * If the preview text is really long, an excerpt is pulled instead of making a massive text blob
92
+ * Fixed a typo in a query argument
93
+ * Reordered the search result list based on priority
94
+
95
+ = 1.6.0 =
96
+ * Revert edits if site health degrades as a direct cause of said edit
97
+
98
+ = 1.5.0 =
99
+ * Return to your search results from the editor, or restore the previous search if you closed the page
100
+ * Multisite support
101
+ * Made marked text more prominent in the editor for readability
102
+ * Fixed rare notice outputs when searching within all plugins/all themes
103
+ * Moved older changelog entries to changelog.txt
104
+ * Updated translation files to use the correct text domain
105
+
106
+ = 1.4.0 =
107
+ * Added code references for WordPress function calls
108
+ * Added the ability to search recursively from the WordPress root
109
+ * Updated textdomain (translations) to use the actual plugin slug
110
+
111
+ = 1.3.0 =
112
+ * Added search all for themes and plugins
113
+ * Refactored code
114
+ * Added german translations
115
+
116
+ = 1.2.1 =
117
+ * Added missing i18n text strings
118
+ * Added capability checks for edit screens
119
+
120
+ = 1.2.0 =
121
+ * Added custom code editor
122
+ * Syntax highlighting
123
+ * Code validation using Smart-Scan
124
+ * Quick jump links to areas with errors detected
125
+ * Replaced unused admin notice
126
+ * Removed previously used WP Editor checks
127
+
128
+ = 1.1.1 =
129
+ * Added Spanish translation files added
130
+ * Added Serbian translation files added
131
+
132
+ = 1.1.0 =
133
+ * Added link to the online editor for themes/plugins from search results
134
+ * Added extra notification text on editor page when referenced by the plugin, makes it easier to find your search string
135
+ * Screenshots and more plugin details added
136
+ * Fixed column width for line number being way too large
137
+ * Add missing inline comments/function references
138
+
139
+ = 1.0.0 =
140
+ * Initial release
editor.php CHANGED
@@ -1,213 +1,269 @@
1
- <?php
2
- if ( ! defined( 'ABSPATH' ) ) {
3
- die();
4
- }
5
-
6
- global $string_locator;
7
- $editor_content = "";
8
-
9
- // $file is validated in String_Locator::is_valid_location() before this page can be loaded through String_Locator::options_page().
10
- $file = $_GET['string-locator-path'];
11
-
12
- $details = array();
13
- $this_url = admin_url( ( is_multisite() ? 'network/admin.php' : 'tools.php' ) . '?page=string-locator' );
14
-
15
- if ( 'core' == $_GET['file-type'] ) {
16
- $details = array(
17
- 'name' => 'WordPress',
18
- 'version' => get_bloginfo( 'version' ),
19
- 'author' => array(
20
- 'uri' => 'https://wordpress.org/',
21
- 'name' => 'WordPress'
22
- ),
23
- /* translators: The WordPress description, used when a core file is opened in the editor. */
24
- 'description' => esc_html__( 'WordPress is web software you can use to create a beautiful website or blog. We like to say that WordPress is both free and priceless at the same time.', 'string-locator' )
25
- );
26
- }
27
- elseif ( 'theme' == $_GET['file-type'] ) {
28
- $themedata = wp_get_theme( $_GET['file-reference'] );
29
-
30
- $details = array(
31
- 'name' => $themedata->get( 'Name' ),
32
- 'version' => $themedata->get( 'Version' ),
33
- 'author' => array(
34
- 'uri' => $themedata->get( 'AuthorURI' ),
35
- 'name' => $themedata->get( 'Author' )
36
- ),
37
- 'description' => $themedata->get( 'Description' ),
38
- 'parent' => $themedata->get( 'parent' )
39
- );
40
- }
41
- else {
42
- $plugins = get_plugins();
43
-
44
- foreach( $plugins AS $pluginname => $plugindata ) {
45
- $pluginref = explode( '/', $pluginname );
46
-
47
- if ( $pluginref[0] == $_GET['file-reference'] ) {
48
- $details = array(
49
- 'name' => $plugindata['Name'],
50
- 'version' => $plugindata['Version'],
51
- 'author' => array(
52
- 'uri' => $plugindata['AuthorURI'],
53
- 'name' => $plugindata['Author']
54
- ),
55
- 'description' => $plugindata['Description']
56
- );
57
- }
58
- }
59
- }
60
-
61
- if ( ! $string_locator->failed_edit ) {
62
- $readfile = fopen( $file, "r" );
63
- if ( $readfile )
64
- {
65
- while ( ( $readline = fgets( $readfile ) ) !== false )
66
- {
67
- $editor_content .= $readline;
68
- }
69
- }
70
- }
71
- else {
72
- $editor_content = stripslashes( $_POST['string-locator-editor-content'] );
73
- }
74
- ?>
75
- <div class="wrap">
76
- <h1>
77
- <?php
78
- /* translators: Title on the editor page. */
79
- esc_html_e( 'String Locator - Code Editor', 'string-locator' );
80
- ?>
81
- <a href="<?php echo esc_url( $this_url . '&restore=true' ); ?>" class="button button-primary"><?php esc_html_e( 'Return to search results', 'string-locator' ); ?></a>
82
- </h1>
83
-
84
- <form action="<?php echo esc_url( String_Locator::get_edit_form_url() ); ?>" id="string-locator-edit-form" method="post">
85
- <div class="string-locator-edit-wrap">
86
- <textarea name="string-locator-editor-content" class="string-locator-editor" id="code-editor" data-editor-goto-line="<?php echo esc_attr( $_GET['string-locator-line'] ); ?>" data-editor-language="<?php echo esc_attr( $string_locator->string_locator_language ); ?>" autofocus="autofocus"><?php echo esc_html( $editor_content ); ?></textarea>
87
- </div>
88
-
89
- <div class="string-locator-sidebar-wrap">
90
- <div class="string-locator-details">
91
- <div class="string-locator-theme-details">
92
- <h2><?php echo esc_html( $details['name'] ); ?> <small>v. <?php echo esc_html( $details['version'] ); ?></small></h2>
93
- <p>
94
- <?php esc_html_e( 'By', 'string-locator' ); ?> <a href="<?php echo esc_url( $details['author']['uri'] ); ?>" target="_blank"><?php echo esc_html( $details['author']['name'] ); ?></a>
95
- </p>
96
- <p>
97
- <?php echo esc_html( $details['description'] ); ?>
98
- </p>
99
- </div>
100
-
101
- <div class="string-locator-actions">
102
- <?php wp_nonce_field( 'string-locator-edit_' . $_GET['edit-file'] ); ?>
103
- <p>
104
- <label>
105
- <input type="checkbox" name="string-locator-smart-edit" checked="checked">
106
- <?php esc_html_e( 'Enable a smart-scan of your code to help detect bracket mismatches before saving.', 'string-locator' ); ?>
107
- </label>
108
- </p>
109
-
110
- <?php if ( isset( $details['parent'] ) && ! $details['parent'] ) { ?>
111
- <div class="notice notice-warning inline below-h2">
112
- <p>
113
- <?php esc_html_e( 'It seems you are making direct edits to a theme.', 'string-locator' ); ?>
114
- </p>
115
-
116
- <p>
117
- <?php _e( 'When making changes to a theme, it is recommended you make a <a href="https://codex.wordpress.org/Child_Themes">Child Theme</a>.', 'string-locator' ); ?>
118
- </p>
119
- </div>
120
-
121
- <p>
122
-
123
- </p>
124
- <?php } ?>
125
-
126
- <?php if ( ! stristr( $file, 'wp-content' ) ) { ?>
127
- <div class="notice notice-warning inline below-h2">
128
- <p>
129
- <strong><?php esc_html_e( 'Warning:', 'string-locator' ); ?></strong> <?php esc_html_e( 'You appear to be editing a Core file.', 'string-locator' ); ?>
130
- </p>
131
- <p>
132
- <?php _e( 'Keep in mind that edits to core files will be lost when WordPress is updated. Please consider <a href="https://make.wordpress.org/core/handbook/">contributing to WordPress core</a> instead.', 'string-locator' ); ?>
133
- </p>
134
- </div>
135
- <?php } ?>
136
-
137
- <p class="submit">
138
- <input type="submit" name="submit" class="button button-primary" value="<?php esc_html_e( 'Save changes', 'string-locator' ); ?>">
139
- </p>
140
- </div>
141
- </div>
142
-
143
- <?php
144
- $function_info = get_defined_functions();
145
- $function_help = '';
146
-
147
- foreach( $function_info['user'] AS $user_func ) {
148
- if ( strstr( $editor_content, $user_func . '(' ) ) {
149
- $function_object = new ReflectionFunction( $user_func );
150
- $attrs = $function_object->getParameters();
151
-
152
- $attr_strings = array();
153
-
154
- foreach( $attrs AS $attr ) {
155
- $arg = '';
156
-
157
- if ( $attr->isPassedByReference() ) {
158
- $arg .= '&';
159
- }
160
-
161
- if ( $attr->isOptional() ) {
162
- $arg = sprintf(
163
- '[ %s$%s ]',
164
- $arg,
165
- $attr->getName()
166
- );
167
- } else {
168
- $arg = sprintf(
169
- '%s$%s',
170
- $arg,
171
- $attr->getName()
172
- );
173
- }
174
-
175
- $attr_strings[] = $arg;
176
- }
177
-
178
- $function_help .= sprintf(
179
- '<p><a href="%s" target="_blank">%s</a></p>',
180
- esc_url( sprintf( 'https://developer.wordpress.org/reference/functions/%s/', $user_func ) ),
181
- $user_func . '( ' . implode( ', ', $attr_strings ) . ' )'
182
- );
183
- }
184
- }
185
- ?>
186
-
187
- <div class="string-locator-details">
188
- <div class="string-locator-theme-details">
189
- <h2><?php esc_html_e( 'File information', 'string-locator' ); ?></h2>
190
-
191
- <p>
192
- <?php esc_html_e( 'File location:', 'string-locator' ); ?>
193
- <br />
194
- <span class="string-locator-italics">
195
- <?php echo esc_html( str_replace( ABSPATH, '', $file ) ); ?>
196
- </span>
197
- </p>
198
- </div>
199
- </div>
200
-
201
- <?php if ( ! empty( $function_help ) ) { ?>
202
- <div class="string-locator-details">
203
-
204
- <div class="string-locator-theme-details">
205
- <h2><?php esc_html_e( 'WordPress Functions', 'string-locator' ); ?></h2>
206
-
207
- <?php echo $function_help; ?>
208
- </div>
209
- </div>
210
- <?php }?>
211
- </div>
212
- </form>
213
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) {
3
+ die();
4
+ }
5
+
6
+ global $string_locator;
7
+ $editor_content = '';
8
+
9
+ // $file is validated in String_Locator::is_valid_location() before this page can be loaded through String_Locator::options_page().
10
+ $file = $_GET['string-locator-path'];
11
+
12
+ $details = array();
13
+ $this_url = admin_url( ( is_multisite() ? 'network/admin.php' : 'tools.php' ) . '?page=string-locator' );
14
+
15
+ if ( 'core' === $_GET['file-type'] ) {
16
+ $details = array(
17
+ 'name' => 'WordPress',
18
+ 'version' => get_bloginfo( 'version' ),
19
+ 'author' => array(
20
+ 'uri' => 'https://wordpress.org/',
21
+ 'name' => 'WordPress',
22
+ ),
23
+ /* translators: The WordPress description, used when a core file is opened in the editor. */
24
+ 'description' => esc_html__( 'WordPress is web software you can use to create a beautiful website or blog. We like to say that WordPress is both free and priceless at the same time.', 'string-locator' ),
25
+ );
26
+ } elseif ( 'theme' === $_GET['file-type'] ) {
27
+ $themedata = wp_get_theme( $_GET['file-reference'] );
28
+
29
+ $details = array(
30
+ 'name' => $themedata->get( 'Name' ),
31
+ 'version' => $themedata->get( 'Version' ),
32
+ 'author' => array(
33
+ 'uri' => $themedata->get( 'AuthorURI' ),
34
+ 'name' => $themedata->get( 'Author' ),
35
+ ),
36
+ 'description' => $themedata->get( 'Description' ),
37
+ 'parent' => $themedata->get( 'parent' ),
38
+ );
39
+ } else {
40
+ $plugins = get_plugins();
41
+
42
+ foreach ( $plugins as $pluginname => $plugindata ) {
43
+ $pluginref = explode( '/', $pluginname );
44
+
45
+ if ( $pluginref[0] === $_GET['file-reference'] ) {
46
+ $details = array(
47
+ 'name' => $plugindata['Name'],
48
+ 'version' => $plugindata['Version'],
49
+ 'author' => array(
50
+ 'uri' => $plugindata['AuthorURI'],
51
+ 'name' => $plugindata['Author'],
52
+ ),
53
+ 'description' => $plugindata['Description'],
54
+ );
55
+ }
56
+ }
57
+ }
58
+
59
+ if ( ! $string_locator->failed_edit ) {
60
+ $readfile = fopen( $file, 'r' );
61
+ if ( $readfile ) {
62
+ while ( ( $readline = fgets( $readfile ) ) !== false ) { // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
63
+ $editor_content .= $readline;
64
+ }
65
+ }
66
+ } else {
67
+ $editor_content = stripslashes( $_POST['string-locator-editor-content'] );
68
+ }
69
+ ?>
70
+ <form id="string-locator-edit-form" class="string-locator-editor-wrapper">
71
+ <?php wp_nonce_field( 'wp_rest' ); ?>
72
+
73
+ <h1 class="screen-reader-text">
74
+ <?php
75
+ /* translators: Title on the editor page. */
76
+ esc_html_e( 'String Locator - Code Editor', 'string-locator' );
77
+ ?>
78
+ </h1>
79
+
80
+ <?php String_Locator::edit_form_fields( true ); ?>
81
+
82
+ <div class="string-locator-header">
83
+ <div>
84
+ <span class="title">
85
+ <?php
86
+ printf(
87
+ // translators: %s: The name of the file being edited.
88
+ __( 'You are currently editing <em>%s</em>', 'string-locator' ),
89
+ esc_html( $_GET['file-reference'] )
90
+ );
91
+ ?>
92
+ </span>
93
+ </div>
94
+
95
+ <div>
96
+ <a href="<?php echo esc_url( $this_url . '&restore=true' ); ?>" class="button button-default"><?php esc_html_e( 'Return to search results', 'string-locator' ); ?></a>
97
+ <button type="submit" class="button button-primary"><?php esc_html_e( 'Save changes', 'string-locator' ); ?></button>
98
+ </div>
99
+ </div>
100
+
101
+ <div class="string-locator-editor">
102
+ <div id="string-locator-notices">
103
+ <?php if ( isset( $details['parent'] ) && ! $details['parent'] ) { ?>
104
+ <div class="row notice notice-warning inline below-h2 is-dismissible">
105
+ <p>
106
+ <?php esc_html_e( 'It seems you are making direct edits to a theme.', 'string-locator' ); ?>
107
+ </p>
108
+
109
+ <p>
110
+ <?php _e( 'When making changes to a theme, it is recommended you make a <a href="https://codex.wordpress.org/Child_Themes">Child Theme</a>.', 'string-locator' ); ?>
111
+ </p>
112
+ </div>
113
+ <?php } ?>
114
+
115
+ <?php if ( ! stristr( $file, 'wp-content' ) ) { ?>
116
+ <div class="row notice notice-warning inline below-h2 is-dismissible">
117
+ <p>
118
+ <strong><?php esc_html_e( 'Warning:', 'string-locator' ); ?></strong> <?php esc_html_e( 'You appear to be editing a Core file.', 'string-locator' ); ?>
119
+ </p>
120
+ <p>
121
+ <?php _e( 'Keep in mind that edits to core files will be lost when WordPress is updated. Please consider <a href="https://make.wordpress.org/core/handbook/">contributing to WordPress core</a> instead.', 'string-locator' ); ?>
122
+ </p>
123
+ </div>
124
+ <?php } ?>
125
+ </div>
126
+
127
+ <textarea
128
+ name="string-locator-editor-content"
129
+ class="string-locator-editor"
130
+ id="code-editor"
131
+ data-editor-goto-line="<?php echo esc_attr( $_GET['string-locator-line'] ); ?>:<?php echo esc_attr( $_GET['string-locator-linepos'] ); ?>"
132
+ data-editor-language="<?php echo esc_attr( $string_locator->string_locator_language ); ?>"
133
+ autofocus="autofocus"
134
+ ><?php echo esc_html( $editor_content ); ?></textarea>
135
+ </div>
136
+
137
+ <div class="string-locator-sidebar">
138
+ <div class="string-locator-panel">
139
+ <h2 class="title"><?php esc_html_e( 'Details', 'string-locator' ); ?></h2>
140
+ <div class="string-locator-panel-body">
141
+ <div class="row">
142
+ <?php echo esc_html( $details['name'] ); ?> <small>v. <?php echo esc_html( $details['version'] ); ?></small>
143
+ </div>
144
+ <div class="row">
145
+ <?php esc_html_e( 'By', 'string-locator' ); ?> <a href="<?php echo esc_url( $details['author']['uri'] ); ?>" target="_blank"><?php echo esc_html( $details['author']['name'] ); ?></a>
146
+ </div>
147
+ <div class="row">
148
+ <?php echo esc_html( $details['description'] ); ?>
149
+ </div>
150
+ </div>
151
+ </div>
152
+
153
+ <div class="string-locator-panel">
154
+ <h2 class="title"><?php esc_html_e( 'Save checks', 'string-locator' ); ?></h2>
155
+ <div class="string-locator-panel-body">
156
+ <div class="row">
157
+ <label>
158
+ <input type="checkbox" name="string-locator-smart-edit" checked="checked">
159
+ <?php esc_html_e( 'Enable a smart-scan of your code to help detect bracket mismatches before saving.', 'string-locator' ); ?>
160
+ </label>
161
+ </div>
162
+
163
+ <div class="row">
164
+ <label>
165
+ <input type="checkbox" name="string-locator-loopback-check" checked="checked">
166
+ <?php esc_html_e( 'Enable loopback tests after making a save.', 'string-locator' ); ?>
167
+ </label>
168
+ <br>
169
+ <em>
170
+ <?php esc_html_e( 'This feature is highly recommended, and is what WordPress does when using the plugin- or theme-editor.', 'string-locator' ); ?>
171
+ </em>
172
+ </div>
173
+ </div>
174
+ </div>
175
+
176
+ <div class="string-locator-panel">
177
+ <h2 class="title"><?php esc_html_e( 'File information', 'string-locator' ); ?></h2>
178
+ <div class="string-locator-panel-body">
179
+ <div class="row">
180
+ <?php esc_html_e( 'File location:', 'string-locator' ); ?>
181
+ <br />
182
+ <span class="string-locator-italics">
183
+ <?php echo esc_html( str_replace( ABSPATH, '', $file ) ); ?>
184
+ <span title="<?php echo esc_attr( sprintf( 'Full file path: %s', $file ) ); ?>" class="dashicons dashicons-editor-help"></span>
185
+ </span>
186
+ </div>
187
+
188
+ <div class="row">
189
+ <?php esc_html_e( 'File size:', 'string-locator' ); ?>
190
+ <br />
191
+ <span class="string-locator-italics">
192
+ <?php echo esc_html( size_format( filesize( $file ), 1 ) ); ?>
193
+ </span>
194
+ </div>
195
+
196
+ <div class="row">
197
+ <?php esc_html_e( 'Last modified:', 'string-locator' ); ?>
198
+ <br />
199
+ <span class="string-locator-italics">
200
+ <?php echo gmdate( 'Y-m-d H:i:s', filemtime( $file ) ); ?>
201
+ </span>
202
+ </div>
203
+ </div>
204
+ </div>
205
+
206
+ <?php
207
+ $function_info = get_defined_functions();
208
+ $function_help = '';
209
+
210
+ foreach ( $function_info['user'] as $user_func ) {
211
+ if ( strstr( $editor_content, $user_func . '(' ) ) {
212
+ $function_object = new ReflectionFunction( $user_func );
213
+ $attrs = $function_object->getParameters();
214
+
215
+ $attr_strings = array();
216
+
217
+ foreach ( $attrs as $attr ) {
218
+ $arg = '';
219
+
220
+ if ( $attr->isPassedByReference() ) {
221
+ $arg .= '&';
222
+ }
223
+
224
+ if ( $attr->isOptional() ) {
225
+ $arg = sprintf(
226
+ '[ %s$%s ]',
227
+ $arg,
228
+ $attr->getName()
229
+ );
230
+ } else {
231
+ $arg = sprintf(
232
+ '%s$%s',
233
+ $arg,
234
+ $attr->getName()
235
+ );
236
+ }
237
+
238
+ $attr_strings[] = $arg;
239
+ }
240
+
241
+ $function_help .= sprintf(
242
+ '<div class="row"><a href="%s" target="_blank">%s</a></div>',
243
+ esc_url( sprintf( 'https://developer.wordpress.org/reference/functions/%s/', $user_func ) ),
244
+ $user_func . '( ' . implode( ', ', $attr_strings ) . ' )'
245
+ );
246
+ }
247
+ }
248
+ ?>
249
+
250
+ <?php if ( ! empty( $function_help ) ) : ?>
251
+
252
+ <div class="string-locator-panel">
253
+ <h2 class="title"><?php esc_html_e( 'WordPress functions', 'string-locator' ); ?></h2>
254
+ <div class="string-locator-panel-body">
255
+ <?php echo $function_help; ?>
256
+ </div>
257
+ <?php endif; ?>
258
+
259
+ </div>
260
+
261
+ </div>
262
+
263
+ <script id="tmpl-string-locator-alert" type="text/template">
264
+ <div class="row notice notice-{{ data.type }} inline below-h2 is-dismissible">
265
+ {{{ data.message }}}
266
+
267
+ <button type="button" class="notice-dismiss"><span class="screen-reader-text">Dismiss this notice.</span></button>
268
+ </div>
269
+ </script>
includes/class-string-locator.php ADDED
@@ -0,0 +1,1431 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Class String_Locator
5
+ */
6
+ class String_Locator {
7
+ /**
8
+ * @var string $string_locator_language The code language used for the editing page.
9
+ * @var string $version String Locator version number.
10
+ * @var array $notice An array containing all notices to display.
11
+ * @var bool $failed_edit Has there been a failed edit.
12
+ * @var string $path_to_use The path to the currently editable file.
13
+ * @var array $bad_http_codes An array of HTTP status codes that will trigger the rollback feature.
14
+ * @var array $bad_file_types An array of file extensions that will be ignored by the scanner.
15
+ * @var int $excerpt_length The length of the excerpt from the line containing a match.
16
+ * @var int|null $max_execution_time The server-configured max time a script can run.
17
+ * @var int $start_execution_time The current time when our script started executing.
18
+ * @var int $max_memory_consumption The server-configured max amount of memory a script can use.
19
+ */
20
+ public $string_locator_language = '';
21
+ public $version = '2.4.0';
22
+ public $notice = array();
23
+ public $failed_edit = false;
24
+ private $path_to_use = '';
25
+ private $bad_http_codes = array( '500' );
26
+ private $bad_file_types = array( 'rar', '7z', 'zip', 'tar', 'gz', 'jpg', 'jpeg', 'png', 'gif', 'mp3', 'mp4', 'avi', 'wmv' );
27
+ private $excerpt_length = 25;
28
+ private $max_execution_time = null;
29
+ private $start_execution_timer = 0;
30
+ private $max_memory_consumption = 0;
31
+
32
+ private $rest_namespace = 'string-locator';
33
+
34
+ /**
35
+ * Construct the plugin
36
+ */
37
+ function __construct() {
38
+ $this->init();
39
+ }
40
+
41
+ /**
42
+ * The plugin initialization, ready as a stand alone function so it can be instantiated in other
43
+ * scenarios as well.
44
+ *
45
+ * @since 2.2.0
46
+ *
47
+ * @return void
48
+ */
49
+ public function init() {
50
+ /**
51
+ * Define class variables requiring expressions
52
+ */
53
+ $this->path_to_use = ( is_multisite() ? 'network/admin.php' : 'tools.php' );
54
+ $this->excerpt_length = apply_filters( 'string_locator_excerpt_length', 25 );
55
+
56
+ $this->max_execution_time = absint( ini_get( 'max_execution_time' ) );
57
+ $this->start_execution_timer = microtime( true );
58
+
59
+ if ( $this->max_execution_time > 30 ) {
60
+ $this->max_execution_time = 30;
61
+ }
62
+
63
+ $this->set_memory_limit();
64
+
65
+ add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
66
+
67
+ add_action( 'admin_menu', array( $this, 'populate_menu' ) );
68
+ add_action( 'network_admin_menu', array( $this, 'populate_network_menu' ) );
69
+
70
+ add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ), 11 );
71
+
72
+ add_action( 'plugins_loaded', array( $this, 'load_i18n' ) );
73
+
74
+ add_action( 'wp_ajax_string-locator-get-directory-structure', array( $this, 'ajax_get_directory_structure' ) );
75
+ add_action( 'wp_ajax_string-locator-search', array( $this, 'ajax_file_search' ) );
76
+ add_action( 'wp_ajax_string-locator-clean', array( $this, 'ajax_clean_search' ) );
77
+
78
+ add_filter( 'plugin_row_meta', array( $this, 'plugin_row_meta' ), 10, 2 );
79
+
80
+ add_action( 'rest_api_init', array( $this, 'add_rest_route' ) );
81
+ }
82
+
83
+ public function add_rest_route() {
84
+ register_rest_route(
85
+ sprintf(
86
+ '%s/v1',
87
+ $this->rest_namespace
88
+ ),
89
+ '/save',
90
+ array(
91
+ 'methods' => 'POST',
92
+ 'callback' => array( $this, 'editor_save' ),
93
+ 'permission_callback' => function() {
94
+ return current_user_can( 'edit_themes' );
95
+ },
96
+ )
97
+ );
98
+ }
99
+
100
+ /**
101
+ * Sets up the memory limit variables.
102
+ *
103
+ * @since 2.0.0
104
+ *
105
+ * @return void
106
+ */
107
+ function set_memory_limit() {
108
+ $memory_limit = ini_get( 'memory_limit' );
109
+
110
+ $this->max_memory_consumption = absint( $memory_limit );
111
+
112
+ if ( strstr( $memory_limit, 'k' ) ) {
113
+ $this->max_memory_consumption = ( str_replace( 'k', '', $memory_limit ) * 1000 );
114
+ }
115
+ if ( strstr( $memory_limit, 'M' ) ) {
116
+ $this->max_memory_consumption = ( str_replace( 'M', '', $memory_limit ) * 1000000 );
117
+ }
118
+ if ( strstr( $memory_limit, 'G' ) ) {
119
+ $this->max_memory_consumption = ( str_replace( 'G', '', $memory_limit ) * 1000000000 );
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Add a donation link to the plugins page.
125
+ *
126
+ * @param array $meta An array of meta links for this plugin.
127
+ * @param string $plugin_file The main plugin file name, used to identify our own plugin.
128
+ *
129
+ * @return array
130
+ */
131
+ function plugin_row_meta( $meta, $plugin_file ) {
132
+ if ( 'string-locator/string-locator.php' === $plugin_file ) {
133
+ $meta[] = sprintf(
134
+ '<a href="https://www.paypal.me/clorith">%s</a>',
135
+ esc_html__( 'Donate to this plugin', 'string-locator' )
136
+ );
137
+ }
138
+
139
+ return $meta;
140
+ }
141
+
142
+ /**
143
+ * Create a set of drop-down options for picking one of the available themes.
144
+ *
145
+ * @param string $current The current selection option to match against.
146
+ *
147
+ * @return string
148
+ */
149
+ public static function get_themes_options( $current = null ) {
150
+ $options = sprintf(
151
+ '<option value="%s" %s>&mdash; %s &mdash;</option>',
152
+ 't--',
153
+ ( 't--' === $current ? 'selected="selected"' : '' ),
154
+ esc_html( __( 'All themes', 'string-locator' ) )
155
+ );
156
+
157
+ $string_locate_themes = wp_get_themes();
158
+
159
+ foreach ( $string_locate_themes as $string_locate_theme_slug => $string_locate_theme ) {
160
+ $string_locate_theme_data = wp_get_theme( $string_locate_theme_slug );
161
+ $string_locate_value = 't-' . $string_locate_theme_slug;
162
+
163
+ $options .= sprintf(
164
+ '<option value="%s" %s>%s</option>',
165
+ $string_locate_value,
166
+ ( $current === $string_locate_value ? 'selected="selected"' : '' ),
167
+ $string_locate_theme_data->Name // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
168
+ );
169
+ }
170
+
171
+ return $options;
172
+ }
173
+
174
+ public static function get_edit_form_url() {
175
+ $url_query = String_Locator::edit_form_fields();
176
+
177
+ return admin_url(
178
+ sprintf(
179
+ 'tools.php?%s',
180
+ build_query( $url_query )
181
+ )
182
+ );
183
+ }
184
+
185
+ public static function edit_form_fields( $echo = false ) {
186
+ $fields = array(
187
+ 'page' => ( isset( $_GET['page'] ) ? $_GET['page'] : '' ),
188
+ 'edit-file' => ( isset( $_GET['edit-file'] ) ? $_GET['edit-file'] : '' ),
189
+ 'file-reference' => ( isset( $_GET['file-reference'] ) ? $_GET['file-reference'] : '' ),
190
+ 'file-type' => ( isset( $_GET['file-type'] ) ? $_GET['file-type'] : '' ),
191
+ 'string-locator-line' => ( isset( $_GET['string-locator-line'] ) ? $_GET['string-locator-line'] : '' ),
192
+ 'string-locator-path' => ( isset( $_GET['string-locator-path'] ) ? $_GET['string-locator-path'] : '' ),
193
+ );
194
+
195
+ $field_output = array();
196
+
197
+ foreach ( $fields as $label => $value ) {
198
+ $field_output[] = sprintf(
199
+ '<input type="hidden" name="%s" value="%s">',
200
+ esc_attr( $label ),
201
+ esc_attr( $value )
202
+ );
203
+ }
204
+
205
+ if ( $echo ) {
206
+ echo implode( "\n", $field_output );
207
+ }
208
+
209
+ return $field_output;
210
+ }
211
+
212
+ /**
213
+ * Create a set of drop-down options for picking one of the available plugins.
214
+ *
215
+ * @param string $current The current selection option to match against.
216
+ *
217
+ * @return string
218
+ */
219
+ public static function get_plugins_options( $current = null ) {
220
+ $options = sprintf(
221
+ '<option value="%s" %s>&mdash; %s &mdash;</option>',
222
+ 'p--',
223
+ ( 'p--' === $current ? 'selected="selected"' : '' ),
224
+ esc_html( __( 'All plugins', 'string-locator' ) )
225
+ );
226
+
227
+ $string_locate_plugins = get_plugins();
228
+
229
+ foreach ( $string_locate_plugins as $string_locate_plugin_path => $string_locate_plugin ) {
230
+ $string_locate_value = 'p-' . $string_locate_plugin_path;
231
+
232
+ $options .= sprintf(
233
+ '<option value="%s" %s>%s</option>',
234
+ $string_locate_value,
235
+ ( $current === $string_locate_value ? 'selected="selected"' : '' ),
236
+ $string_locate_plugin['Name']
237
+ );
238
+ }
239
+
240
+ return $options;
241
+ }
242
+
243
+ /**
244
+ * Create a set of drop-down options for picking one of the available must-use plugins.
245
+ *
246
+ * @param string $current The current selection option to match against.
247
+ *
248
+ * @return string
249
+ */
250
+ public static function get_mu_plugins_options( $current = null ) {
251
+ $options = sprintf(
252
+ '<option value="%s" %s>&mdash; %s &mdash;</option>',
253
+ 'mup--',
254
+ ( 'mup--' === $current ? 'selected="selected"' : '' ),
255
+ esc_html__( 'All must-use plugins', 'string-locator' )
256
+ );
257
+
258
+ $string_locate_plugins = get_mu_plugins();
259
+
260
+ foreach ( $string_locate_plugins as $string_locate_plugin_path => $string_locate_plugin ) {
261
+ $string_locate_value = 'mup-' . $string_locate_plugin_path;
262
+
263
+ $options .= sprintf(
264
+ '<option value="%s" %s>%s</option>',
265
+ $string_locate_value,
266
+ ( $current === $string_locate_value ? 'selected="selected"' : '' ),
267
+ $string_locate_plugin['Name']
268
+ );
269
+ }
270
+
271
+ return $options;
272
+ }
273
+
274
+ /**
275
+ * Check if there are Must-Use plugins available on this WordPress install.
276
+ *
277
+ * @since 2.2.0
278
+ *
279
+ * @return bool
280
+ */
281
+ public static function has_mu_plugins() {
282
+ $mu_plugin_count = get_mu_plugins();
283
+
284
+ if ( count( $mu_plugin_count ) >= 1 ) {
285
+ return true;
286
+ }
287
+
288
+ return false;
289
+ }
290
+
291
+ /**
292
+ * Handles the AJAX request to prepare the search hierarchy.
293
+ *
294
+ * @return void
295
+ */
296
+ function ajax_get_directory_structure() {
297
+ if ( ! check_ajax_referer( 'string-locator-search', 'nonce', false ) ) {
298
+ wp_send_json_error( __( 'Authentication failed', 'string-locator' ) );
299
+ }
300
+
301
+ $scan_path = $this->prepare_scan_path( $_POST['directory'] );
302
+ if ( is_file( $scan_path->path ) ) {
303
+ $files = array( $scan_path->path );
304
+ } else {
305
+ $files = $this->ajax_scan_path( $scan_path->path );
306
+ }
307
+
308
+ /*
309
+ * Make sure each chunk of file arrays never exceeds 500 files
310
+ * This is to prevent the SQL string from being too large and crashing everything
311
+ */
312
+ $back_compat_filter = apply_filters( 'string-locator-files-per-array', 500 ); //phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
313
+
314
+ $file_chunks = array_chunk( $files, apply_filters( 'string_locator_files_per_array', $back_compat_filter ), true );
315
+
316
+ $store = (object) array(
317
+ 'scan_path' => $scan_path,
318
+ 'search' => wp_unslash( $_POST['search'] ),
319
+ 'directory' => $_POST['directory'],
320
+ 'chunks' => count( $file_chunks ),
321
+ 'regex' => $_POST['regex'],
322
+ );
323
+
324
+ $response = array(
325
+ 'total' => count( $files ),
326
+ 'current' => 0,
327
+ 'directory' => $scan_path,
328
+ 'chunks' => count( $file_chunks ),
329
+ 'regex' => $_POST['regex'],
330
+ );
331
+
332
+ set_transient( 'string-locator-search-overview', $store );
333
+ set_transient( 'string-locator-search-history', array() );
334
+
335
+ foreach ( $file_chunks as $count => $file_chunk ) {
336
+ set_transient( 'string-locator-search-files-' . $count, $file_chunk );
337
+ }
338
+
339
+ wp_send_json_success( $response );
340
+ }
341
+
342
+ /**
343
+ * Check if the script is about to exceed the max execution time.
344
+ *
345
+ * @since 1.9.0
346
+ *
347
+ * @return bool
348
+ */
349
+ function nearing_execution_limit() {
350
+ // Max execution time is 0 or -1 (infinite) in server config
351
+ if ( 0 === $this->max_execution_time || - 1 === $this->max_execution_time ) {
352
+ return false;
353
+ }
354
+
355
+ $back_compat_filter = apply_filters( 'string-locator-extra-search-delay', 2 ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
356
+
357
+ $built_in_delay = apply_filters( 'string_locator_extra_search_delay', $back_compat_filter );
358
+ $execution_time = ( microtime( true ) - $this->start_execution_timer + $built_in_delay );
359
+
360
+ if ( $execution_time >= $this->max_execution_time ) {
361
+ return $execution_time;
362
+ }
363
+
364
+ return false;
365
+ }
366
+
367
+ /**
368
+ * Check if the script is about to exceed the server memory limit.
369
+ *
370
+ * @since 2.0.0
371
+ *
372
+ * @return bool
373
+ */
374
+ function nearing_memory_limit() {
375
+ // Check if the memory limit is set t o0 or -1 (infinite) in server config
376
+ if ( 0 === $this->max_memory_consumption || - 1 === $this->max_memory_consumption ) {
377
+ return false;
378
+ }
379
+
380
+ // We give our selves a 256k memory buffer, as we need to close off the script properly as well
381
+ $back_compat_filter = apply_filters( 'string-locator-extra-memory-buffer', 256000 ); //phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
382
+ $built_in_buffer = apply_filters( 'string_locator_extra_memory_buffer', $back_compat_filter );
383
+ $memory_use = ( memory_get_usage( true ) + $built_in_buffer );
384
+
385
+ if ( $memory_use >= $this->max_memory_consumption ) {
386
+ return $memory_use;
387
+ }
388
+
389
+ return false;
390
+ }
391
+
392
+ public static function absbool( $value ) {
393
+ if ( is_bool( $value ) ) {
394
+ $bool = $value;
395
+ } else {
396
+ if ( 'false' === $value ) {
397
+ $bool = false;
398
+ } else {
399
+ $bool = true;
400
+ }
401
+ }
402
+
403
+ return $bool;
404
+ }
405
+
406
+ /**
407
+ * Search an individual file supplied via AJAX.
408
+ *
409
+ * @since 1.9.0
410
+ *
411
+ * @return void
412
+ */
413
+ function ajax_file_search() {
414
+ if ( ! check_ajax_referer( 'string-locator-search', 'nonce', false ) ) {
415
+ wp_send_json_error( __( 'Authentication failed', 'string-locator' ) );
416
+ }
417
+
418
+ $back_compat_filter = apply_filters( 'string-locator-files-per-array', 500 ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
419
+
420
+ $files_per_chunk = apply_filters( 'string_locator_files_per_array', $back_compat_filter );
421
+ $response = array(
422
+ 'search' => array(),
423
+ 'filenum' => absint( $_POST['filenum'] ),
424
+ );
425
+
426
+ $filenum = absint( $_POST['filenum'] );
427
+ $next_file = $filenum + 1;
428
+
429
+ $next_chunk = ( ceil( ( $next_file ) / $files_per_chunk ) - 1 );
430
+ $chunk = ( ceil( $filenum / $files_per_chunk ) - 1 );
431
+ if ( $chunk < 0 ) {
432
+ $chunk = 0;
433
+ }
434
+ if ( $next_chunk < 0 ) {
435
+ $next_chunk = 0;
436
+ }
437
+
438
+ $scan_data = get_transient( 'string-locator-search-overview' );
439
+ $file_data = get_transient( 'string-locator-search-files-' . $chunk );
440
+
441
+ if ( ! isset( $file_data[ $filenum ] ) ) {
442
+ wp_send_json_error(
443
+ array(
444
+ 'continue' => false,
445
+ 'message' => sprintf(
446
+ /* translators: %d: The numbered reference to a file being searched. */
447
+ esc_html__( 'The file-number, %d, that was sent could not be found.', 'string-locator' ),
448
+ $filenum
449
+ ),
450
+ )
451
+ );
452
+ }
453
+
454
+ if ( $this->nearing_execution_limit() ) {
455
+ wp_send_json_error(
456
+ array(
457
+ 'continue' => false,
458
+ 'message' => sprintf(
459
+ /* translators: %1$d: The time a PHP file can run, as defined by the server configuration. %2$d: The amount of time used by the PHP file so far. */
460
+ esc_html__( 'The maximum time your server allows a script to run (%1$d) is too low for the plugin to run as intended, at startup %2$d seconds have passed', 'string-locator' ),
461
+ $this->max_execution_time,
462
+ $this->nearing_execution_limit()
463
+ ),
464
+ )
465
+ );
466
+ }
467
+ if ( $this->nearing_memory_limit() ) {
468
+ wp_send_json_error(
469
+ array(
470
+ 'continue' => false,
471
+ 'message' => sprintf(
472
+ /* translators: %1$d: Current amount of used system memory resources. %2$d: The maximum available system memory. */
473
+ esc_html__( 'The memory limit is about to be exceeded before the search has started, this could be an early indicator that your site may soon struggle as well, unfortunately this means the plugin is unable to perform any searches. Current memory consumption: %1$d of %2$d bytes', 'string-locator' ),
474
+ $this->nearing_memory_limit(),
475
+ $this->max_memory_consumption
476
+ ),
477
+ )
478
+ );
479
+ }
480
+
481
+ $is_regex = false;
482
+ if ( isset( $scan_data->regex ) ) {
483
+ $is_regex = $this->absbool( $scan_data->regex );
484
+ }
485
+
486
+ if ( $is_regex ) {
487
+ if ( false === @preg_match( $scan_data->search, '' ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
488
+ wp_send_json_error(
489
+ array(
490
+ 'continue' => false,
491
+ 'message' => sprintf(
492
+ /* translators: %s: The search string used. */
493
+ __( 'Your search string, <strong>%s</strong>, is not a valid pattern, and the search has been aborted.', 'string-locator' ),
494
+ esc_html( $scan_data->search )
495
+ ),
496
+ )
497
+ );
498
+ }
499
+ }
500
+
501
+ while ( ! $this->nearing_execution_limit() && ! $this->nearing_memory_limit() && isset( $file_data[ $filenum ] ) ) {
502
+ $filenum = absint( $_POST['filenum'] );
503
+ $search_results = null;
504
+ $next_file = $filenum + 1;
505
+
506
+ $next_chunk = ( ceil( ( $next_file ) / $files_per_chunk ) - 1 );
507
+ $chunk = ( ceil( $filenum / $files_per_chunk ) - 1 );
508
+ if ( $chunk < 0 ) {
509
+ $chunk = 0;
510
+ }
511
+ if ( $next_chunk < 0 ) {
512
+ $next_chunk = 0;
513
+ }
514
+
515
+ if ( ! isset( $file_data[ $filenum ] ) ) {
516
+ $chunk ++;
517
+ $file_data = get_transient( 'string-locator-search-files-' . $chunk );
518
+ continue;
519
+ }
520
+
521
+ $file_name = explode( '/', $file_data[ $filenum ] );
522
+ $file_name = end( $file_name );
523
+
524
+ /*
525
+ * Check the file type, if it's an unsupported type, we skip it
526
+ */
527
+ $file_type = explode( '.', $file_name );
528
+ $file_type = strtolower( end( $file_type ) );
529
+
530
+ /*
531
+ * Scan the file and look for our string, but only if it's an approved file extension
532
+ */
533
+ $bad_file_types = apply_filters( 'string_locator_bad_file_types', $this->bad_file_types );
534
+ if ( ! in_array( $file_type, $bad_file_types, true ) ) {
535
+ $search_results = $this->scan_file( $file_data[ $filenum ], $scan_data->search, $file_data[ $filenum ], $scan_data->scan_path->type, '', $is_regex );
536
+ }
537
+
538
+ $response['last_file'] = $file_data[ $filenum ];
539
+ $response['filenum'] = $filenum;
540
+ $response['filename'] = $file_name;
541
+ if ( $search_results ) {
542
+ $response['search'][] = $search_results;
543
+ }
544
+
545
+ if ( $next_chunk !== $chunk ) {
546
+ $file_data = get_transient( 'string-locator-search-files-' . $next_chunk );
547
+ }
548
+
549
+ $response['next_file'] = ( isset( $file_data[ $next_file ] ) ? $file_data[ $next_file ] : '' );
550
+
551
+ if ( ! empty( $search_results ) ) {
552
+ $history = get_transient( 'string-locator-search-history' );
553
+ if ( false === $history ) {
554
+ $history = array();
555
+ }
556
+ $history = array_merge( $history, $search_results );
557
+ set_transient( 'string-locator-search-history', serialize( $history ) );
558
+ }
559
+
560
+ $_POST['filenum'] ++;
561
+ }
562
+
563
+ wp_send_json_success( $response );
564
+ }
565
+
566
+ /**
567
+ * Clean up our options used to help during the search.
568
+ *
569
+ * @return void
570
+ */
571
+ function ajax_clean_search() {
572
+ if ( ! check_ajax_referer( 'string-locator-search', 'nonce', false ) ) {
573
+ wp_send_json_error( __( 'Authentication failed', 'string-locator' ) );
574
+ }
575
+
576
+ $scan_data = get_transient( 'string-locator-search-overview' );
577
+ for ( $i = 0; $i < $scan_data->chunks; $i ++ ) {
578
+ delete_transient( 'string-locator-search-files-' . $i );
579
+ }
580
+
581
+ wp_send_json_success( true );
582
+ }
583
+
584
+ /**
585
+ * Create a table row for insertion into the search results list.
586
+ *
587
+ * @param array|object $item The table row item.
588
+ *
589
+ * @return string
590
+ */
591
+ public static function prepare_table_row( $item ) {
592
+ if ( ! is_object( $item ) ) {
593
+ $item = (object) $item;
594
+ }
595
+
596
+ return sprintf(
597
+ '<tr>
598
+ <td>
599
+ %s
600
+ <div class="row-actions">
601
+ %s
602
+ </div>
603
+ </td>
604
+ <td>
605
+ %s
606
+ </td>
607
+ <td>
608
+ %d
609
+ </td>
610
+ <td>
611
+ %d
612
+ </td>
613
+ </tr>',
614
+ $item->stringresult,
615
+ ( ! current_user_can( 'edit_themes' ) ? '' : sprintf(
616
+ '<span class="edit"><a href="%s" aria-label="Edit">%s</a></span>',
617
+ esc_url( $item->editurl ),
618
+ // translators: The row-action edit link label.
619
+ esc_html__( 'Edit', 'string-locator' )
620
+ ) ),
621
+ ( ! current_user_can( 'edit_themes' ) ? $item->filename_raw : sprintf(
622
+ '<a href="%s">%s</a>',
623
+ esc_url( $item->editurl ),
624
+ esc_html( $item->filename_raw )
625
+ ) ),
626
+ esc_html( $item->linenum ),
627
+ esc_html( $item->linepos )
628
+ );
629
+ }
630
+
631
+ /**
632
+ * Create a full table populated with the supplied items.
633
+ *
634
+ * @param array $items An array of table rows.
635
+ * @param array $table_class An array of items to append to the table class along with the defaults.
636
+ *
637
+ * @return string
638
+ */
639
+ public static function prepare_full_table( $items, $table_class = array() ) {
640
+ $table_class = array_merge(
641
+ $table_class,
642
+ array(
643
+ 'wp-list-table',
644
+ 'widefat',
645
+ 'fixed',
646
+ 'striped',
647
+ 'tools_page_string-locator',
648
+ )
649
+ );
650
+
651
+ $table_columns = sprintf(
652
+ '<tr>
653
+ <th scope="col" class="manage-column column-stringresult column-primary">%s</th>
654
+ <th scope="col" class="manage-column column-filename">%s</th>
655
+ <th scope="col" class="manage-column column-linenum">%s</th>
656
+ <th scope="col" class="manage-column column-linepos">%s</th>
657
+ </tr>',
658
+ esc_html( __( 'String', 'string-locator' ) ),
659
+ esc_html( __( 'File', 'string-locator' ) ),
660
+ esc_html( __( 'Line number', 'string-locator' ) ),
661
+ esc_html( __( 'Line position', 'string-locator' ) )
662
+ );
663
+
664
+ $table_rows = array();
665
+ foreach ( $items as $item ) {
666
+ $table_rows[] = self::prepare_table_row( $item );
667
+ }
668
+
669
+ $table = sprintf(
670
+ '<div class="tablenav top"><br class="clear"></div><table class="%s"><thead>%s</thead><tbody>%s</tbody><tfoot>%s</tfoot></table>',
671
+ implode( ' ', $table_class ),
672
+ $table_columns,
673
+ implode( "\n", $table_rows ),
674
+ $table_columns
675
+ );
676
+
677
+ return $table;
678
+ }
679
+
680
+ /**
681
+ * Create an admin edit link for the supplied path.
682
+ *
683
+ * @param string $path Path to the file we'er adding a link for.
684
+ * @param int $line The line in the file where our search result was found.
685
+ * @param int $linepos The positin in the line where the search result was found.
686
+ *
687
+ * @return string
688
+ */
689
+ function create_edit_link( $path, $line = 0, $linepos = 0 ) {
690
+ $file_type = 'core';
691
+ $file_slug = '';
692
+ $content_path = str_replace( '\\', '/', WP_CONTENT_DIR );
693
+
694
+ $path = str_replace( '\\', '/', $path );
695
+ $paths = explode( '/', $path );
696
+
697
+ $url_args = array(
698
+ 'page=string-locator',
699
+ 'edit-file=' . end( $paths ),
700
+ );
701
+
702
+ switch ( true ) {
703
+ case ( in_array( 'wp-content', $paths, true ) && in_array( 'plugins', $paths, true ) ):
704
+ $file_type = 'plugin';
705
+ $content_path .= '/plugins/';
706
+ break;
707
+ case ( in_array( 'wp-content', $paths, true ) && in_array( 'themes', $paths, true ) ):
708
+ $file_type = 'theme';
709
+ $content_path .= '/themes/';
710
+ break;
711
+ }
712
+
713
+ $rel_path = str_replace( $content_path, '', $path );
714
+ $rel_paths = explode( '/', $rel_path );
715
+
716
+ if ( 'core' !== $file_type ) {
717
+ $file_slug = $rel_paths[0];
718
+ }
719
+
720
+ $url_args[] = 'file-reference=' . $file_slug;
721
+ $url_args[] = 'file-type=' . $file_type;
722
+ $url_args[] = 'string-locator-line=' . absint( $line );
723
+ $url_args[] = 'string-locator-linepos=' . absint( $linepos );
724
+ $url_args[] = 'string-locator-path=' . urlencode( str_replace( '/', DIRECTORY_SEPARATOR, $path ) );
725
+
726
+ $url = admin_url( $this->path_to_use . '?' . implode( '&', $url_args ) );
727
+
728
+ return $url;
729
+ }
730
+
731
+ /**
732
+ * Parse the search option to determine what kind of search we are performing and what directory to start in.
733
+ *
734
+ * @param string $option The search-type identifier.
735
+ *
736
+ * @return bool|object
737
+ */
738
+ function prepare_scan_path( $option ) {
739
+ $data = array(
740
+ 'path' => '',
741
+ 'type' => '',
742
+ 'slug' => '',
743
+ );
744
+
745
+ switch ( true ) {
746
+ case ( 't--' === $option ):
747
+ $data['path'] = WP_CONTENT_DIR . '/themes/';
748
+ $data['type'] = 'theme';
749
+ break;
750
+ case ( strlen( $option ) > 3 && 't-' === substr( $option, 0, 2 ) ):
751
+ $data['path'] = WP_CONTENT_DIR . '/themes/' . substr( $option, 2 );
752
+ $data['type'] = 'theme';
753
+ $data['slug'] = substr( $option, 2 );
754
+ break;
755
+ case ( 'p--' === $option ):
756
+ $data['path'] = WP_CONTENT_DIR . '/plugins/';
757
+ $data['type'] = 'plugin';
758
+ break;
759
+ case ( 'mup--' === $option ):
760
+ $data['path'] = WP_CONTENT_DIR . '/mu-plugins/';
761
+ $data['type'] = 'mu-plugin';
762
+ break;
763
+ case ( strlen( $option ) > 3 && 'p-' === substr( $option, 0, 2 ) ):
764
+ $slug = explode( '/', substr( $option, 2 ) );
765
+
766
+ $data['path'] = WP_CONTENT_DIR . '/plugins/' . $slug[0];
767
+ $data['type'] = 'plugin';
768
+ $data['slug'] = $slug[0];
769
+ break;
770
+ case ( 'core' === $option ):
771
+ $data['path'] = ABSPATH;
772
+ $data['type'] = 'core';
773
+ break;
774
+ case ( 'wp-content' === $option ):
775
+ $data['path'] = WP_CONTENT_DIR;
776
+ $data['type'] = 'core';
777
+ break;
778
+ }
779
+
780
+ if ( empty( $data['path'] ) ) {
781
+ return false;
782
+ }
783
+
784
+ return (object) $data;
785
+ }
786
+
787
+ /**
788
+ * Check if a file path is valid for editing.
789
+ *
790
+ * @param string $path Path to file.
791
+ *
792
+ * @return bool
793
+ */
794
+ function is_valid_location( $path ) {
795
+ $valid = true;
796
+ $path = str_replace( array( '/' ), array( DIRECTORY_SEPARATOR ), stripslashes( $path ) );
797
+ $abspath = str_replace( array( '/' ), array( DIRECTORY_SEPARATOR ), ABSPATH );
798
+
799
+ // Check that it is a valid file we are trying to access as well.
800
+ if ( ! file_exists( $path ) ) {
801
+ $valid = false;
802
+ }
803
+
804
+ if ( empty( $path ) ) {
805
+ $valid = false;
806
+ }
807
+ if ( stristr( $path, '..' ) ) {
808
+ $valid = false;
809
+ }
810
+ if ( ! stristr( $path, $abspath ) ) {
811
+ $valid = false;
812
+ }
813
+
814
+ return $valid;
815
+ }
816
+
817
+ /**
818
+ * Set the text domain for translated plugin content.
819
+ *
820
+ * @return void
821
+ */
822
+ function load_i18n() {
823
+ $i18n_dir = 'string-locator/languages/';
824
+ load_plugin_textdomain( 'string-locator', false, $i18n_dir );
825
+ }
826
+
827
+ /**
828
+ * Load up JavaScript and CSS for our plugin on the appropriate admin pages.
829
+ *
830
+ * @return void
831
+ */
832
+ function admin_enqueue_scripts( $hook ) {
833
+ // Break out early if we are not on a String Locator page
834
+ if ( 'tools_page_string-locator' !== $hook && 'toplevel_page_string-locator' !== $hook ) {
835
+ return;
836
+ }
837
+
838
+ if ( ! wp_script_is( 'react', 'registered' ) ) {
839
+ wp_register_script( 'react', trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'resources/js/react.js', array() );
840
+ }
841
+
842
+ if ( ! wp_script_is( 'react-dom', 'registered' ) ) {
843
+ wp_register_script( 'react-dom', trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'resources/js/react-dom.js', array() );
844
+ }
845
+
846
+ /**
847
+ * String Locator Styles
848
+ */
849
+ wp_enqueue_style( 'string-locator', trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'resources/css/string-locator.css', array(), $this->version );
850
+
851
+ if ( ! isset( $_GET['edit-file'] ) || ! current_user_can( 'edit_themes' ) ) {
852
+ /**
853
+ * String Locator Scripts
854
+ */
855
+ wp_enqueue_script( 'string-locator-search', trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'resources/js/string-locator-search.js', array( 'jquery', 'wp-util' ), $this->version, true );
856
+
857
+ wp_localize_script(
858
+ 'string-locator-search',
859
+ 'string_locator',
860
+ array(
861
+ 'ajax_url' => admin_url( 'admin-ajax.php' ),
862
+ 'search_nonce' => wp_create_nonce( 'string-locator-search' ),
863
+ 'search_current_prefix' => __( 'Next file: ', 'string-locator' ),
864
+ 'saving_results_string' => __( 'Saving search results&hellip;', 'string-locator' ),
865
+ 'search_preparing' => __( 'Preparing search&hellip;', 'string-locator' ),
866
+ 'search_started' => __( 'Preparations completed, search started&hellip;', 'string-locator' ),
867
+ 'search_error' => __( 'The above error was returned by your server, for more details please consult your servers error logs.', 'string-locator' ),
868
+ 'search_no_results' => __( 'Your search was completed, but no results were found..', 'string-locator' ),
869
+ 'warning_title' => __( 'Warning', 'string-locator' ),
870
+ )
871
+ );
872
+
873
+ } else {
874
+ $code_mirror = wp_enqueue_code_editor(
875
+ array(
876
+ 'file' => $_GET['edit-file'],
877
+ )
878
+ );
879
+
880
+ /**
881
+ * String Locator Scripts
882
+ */
883
+ wp_enqueue_script( 'string-locator-editor', trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'resources/js/string-locator.js', array( 'jquery', 'code-editor', 'wp-util' ), $this->version, true );
884
+
885
+ wp_localize_script(
886
+ 'string-locator-editor',
887
+ 'string_locator',
888
+ array(
889
+ 'CodeMirror' => $code_mirror,
890
+ 'goto_line' => absint( $_GET['string-locator-line'] ),
891
+ 'goto_linepos' => absint( $_GET['string-locator-linepos'] ),
892
+ 'save_url' => get_rest_url( null, 'string-locator/v1/save' ),
893
+ )
894
+ );
895
+ }
896
+ }
897
+
898
+ /**
899
+ * Add our plugin to the 'Tools' menu.
900
+ *
901
+ * @return void
902
+ */
903
+ function populate_menu() {
904
+ if ( is_multisite() ) {
905
+ return;
906
+ }
907
+ $page_title = __( 'String Locator', 'string-locator' );
908
+ $menu_title = __( 'String Locator', 'string-locator' );
909
+ $capability = 'update_core';
910
+ $parent_slug = 'tools.php';
911
+ $menu_slug = 'string-locator';
912
+ $function = array( $this, 'options_page' );
913
+
914
+ add_submenu_page( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $function );
915
+ }
916
+
917
+ /**
918
+ * Add our plugin to the main menu in the Network Admin.
919
+ *
920
+ * @return void
921
+ */
922
+ function populate_network_menu() {
923
+ $page_title = __( 'String Locator', 'string-locator' );
924
+ $menu_title = __( 'String Locator', 'string-locator' );
925
+ $capability = 'update_core';
926
+ $menu_slug = 'string-locator';
927
+ $function = array( $this, 'options_page' );
928
+
929
+ add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $function, 'dashicons-edit' );
930
+ }
931
+
932
+ /**
933
+ * Function for including the actual plugin Admin UI page.
934
+ *
935
+ * @return mixed
936
+ */
937
+ function options_page() {
938
+ /**
939
+ * Don't load anything if the user can't edit themes any way
940
+ */
941
+ if ( ! current_user_can( 'update_core' ) ) {
942
+ return false;
943
+ }
944
+
945
+ /**
946
+ * Show the edit page if;
947
+ * - The edit file path query var is set
948
+ * - The edit file path query var isn't empty
949
+ * - The edit file path query var does not contains double dots (used to traverse directories)
950
+ * - The user is capable of editing files.
951
+ */
952
+ if ( isset( $_GET['string-locator-path'] ) && $this->is_valid_location( $_GET['string-locator-path'] ) && current_user_can( 'edit_themes' ) ) {
953
+ include_once( dirname( __FILE__ ) . '/../editor.php' );
954
+ } else {
955
+ include_once( dirname( __FILE__ ) . '/../options.php' );
956
+ }
957
+ }
958
+
959
+ function admin_body_class( $class ) {
960
+ if ( isset( $_GET['string-locator-path'] ) && $this->is_valid_location( $_GET['string-locator-path'] ) && current_user_can( 'edit_themes' ) ) {
961
+ $class .= ' file-edit-screen';
962
+ }
963
+
964
+ return $class;
965
+ }
966
+
967
+ /**
968
+ * Check for inconsistencies in brackets and similar.
969
+ *
970
+ * @param string $start Start delimited.
971
+ * @param string $end End delimiter.
972
+ * @param string $string The string to scan.
973
+ *
974
+ * @return array
975
+ */
976
+ function smart_scan( $start, $end, $string ) {
977
+ $opened = array();
978
+
979
+ $lines = explode( "\n", $string );
980
+ for ( $i = 0; $i < count( $lines ); $i ++ ) {
981
+ if ( stristr( $lines[ $i ], $start ) ) {
982
+ $opened[] = $i;
983
+ }
984
+ if ( stristr( $lines[ $i ], $end ) ) {
985
+ array_pop( $opened );
986
+ }
987
+ }
988
+
989
+ return $opened;
990
+ }
991
+
992
+ /**
993
+ * Handler for storing the content of the code editor.
994
+ *
995
+ * Also runs over the Smart-Scan if enabled.
996
+ *
997
+ * @return void|array
998
+ */
999
+ function editor_save( $request ) {
1000
+ $_POST = $request->get_params();
1001
+
1002
+ $check_loopback = isset( $_POST['string-locator-loopback-check'] );
1003
+ $do_smart_scan = isset( $_POST['string-locator-smart-edit'] );
1004
+
1005
+ if ( $this->is_valid_location( $_POST['string-locator-path'] ) ) {
1006
+ $path = urldecode( $_POST['string-locator-path'] );
1007
+ $content = stripslashes( $_POST['string-locator-editor-content'] );
1008
+
1009
+ /**
1010
+ * Send an error notice if the file isn't writable
1011
+ */
1012
+ if ( ! is_writeable( $path ) ) {
1013
+ $this->notice[] = array(
1014
+ 'type' => 'error',
1015
+ 'message' => __( 'The file could not be written to, please check file permissions or edit it manually.', 'string-locator' ),
1016
+ );
1017
+
1018
+ return array(
1019
+ 'notices' => $this->notice,
1020
+ );
1021
+ }
1022
+
1023
+ /**
1024
+ * If enabled, run the Smart-Scan on the content before saving it
1025
+ */
1026
+ if ( $do_smart_scan ) {
1027
+ $open_brace = substr_count( $content, '{' );
1028
+ $close_brace = substr_count( $content, '}' );
1029
+ if ( $open_brace !== $close_brace ) {
1030
+ $this->failed_edit = true;
1031
+
1032
+ $opened = $this->smart_scan( '{', '}', $content );
1033
+
1034
+ foreach ( $opened as $line ) {
1035
+ $this->notice[] = array(
1036
+ 'type' => 'error',
1037
+ 'message' => sprintf(
1038
+ // translators: 1: Line number with an error.
1039
+ __( 'There is an inconsistency in the opening and closing braces, { and }, of your file on line %s', 'string-locator' ),
1040
+ '<a href="#" class="string-locator-edit-goto" data-goto-line="' . ( $line + 1 ) . '">' . ( $line + 1 ) . '</a>'
1041
+ ),
1042
+ );
1043
+ }
1044
+ }
1045
+
1046
+ $open_bracket = substr_count( $content, '[' );
1047
+ $close_bracket = substr_count( $content, ']' );
1048
+ if ( $open_bracket !== $close_bracket ) {
1049
+ $this->failed_edit = true;
1050
+
1051
+ $opened = $this->smart_scan( '[', ']', $content );
1052
+
1053
+ foreach ( $opened as $line ) {
1054
+ $this->notice[] = array(
1055
+ 'type' => 'error',
1056
+ 'message' => sprintf(
1057
+ // translators: 1: Line number with an error.
1058
+ __( 'There is an inconsistency in the opening and closing braces, [ and ], of your file on line %s', 'string-locator' ),
1059
+ '<a href="#" class="string-locator-edit-goto" data-goto-line="' . ( $line + 1 ) . '">' . ( $line + 1 ) . '</a>'
1060
+ ),
1061
+ );
1062
+ }
1063
+ }
1064
+
1065
+ $open_parenthesis = substr_count( $content, '(' );
1066
+ $close_parenthesis = substr_count( $content, ')' );
1067
+ if ( $open_parenthesis !== $close_parenthesis ) {
1068
+ $this->failed_edit = true;
1069
+
1070
+ $opened = $this->smart_scan( '(', ')', $content );
1071
+
1072
+ foreach ( $opened as $line ) {
1073
+ $this->notice[] = array(
1074
+ 'type' => 'error',
1075
+ 'message' => sprintf(
1076
+ // translators: 1: Line number with an error.
1077
+ __( 'There is an inconsistency in the opening and closing braces, ( and ), of your file on line %s', 'string-locator' ),
1078
+ '<a href="#" class="string-locator-edit-goto" data-goto-line="' . ( $line + 1 ) . '">' . ( $line + 1 ) . '</a>'
1079
+ ),
1080
+ );
1081
+ }
1082
+ }
1083
+
1084
+ if ( $this->failed_edit ) {
1085
+ return array(
1086
+ 'notices' => $this->notice,
1087
+ );
1088
+ }
1089
+ }
1090
+
1091
+ $original = file_get_contents( $path );
1092
+
1093
+ $this->write_file( $path, $content );
1094
+
1095
+ /**
1096
+ * Check the status of the site after making our edits.
1097
+ * If the site fails, revert the changes to return the sites to its original state
1098
+ */
1099
+ if ( $check_loopback ) {
1100
+ $header = wp_remote_head( site_url() );
1101
+
1102
+ if ( ! is_wp_error( $header ) && 301 === (int) $header['response']['code'] ) {
1103
+ $header = wp_remote_head( $header['headers']['location'] );
1104
+ }
1105
+
1106
+ $bad_http_check = apply_filters( 'string_locator_bad_http_codes', $this->bad_http_codes );
1107
+ }
1108
+
1109
+ if ( $check_loopback && is_wp_error( $header ) ) {
1110
+ $this->failed_edit = true;
1111
+ $this->write_file( $path, $original );
1112
+
1113
+ // Likely loopback error, so be useful in our errors.
1114
+ if ( 'http_request_failed' === $header->get_error_code() ) {
1115
+ return array(
1116
+ 'notices' => array(
1117
+ array(
1118
+ 'type' => 'error',
1119
+ 'message' => __( 'Your changes were not saved, as a check of your site could not be completed afterwards. This may be due to a <a href="https://wordpress.org/support/article/loopbacks/">loopback</a> error.', 'string-locator' ),
1120
+ ),
1121
+ ),
1122
+ );
1123
+ }
1124
+
1125
+ // Fallback error message here.
1126
+ return array(
1127
+ 'notices' => array(
1128
+ array(
1129
+ 'type' => 'error',
1130
+ 'message' => $header->get_error_message(),
1131
+ ),
1132
+ ),
1133
+ );
1134
+ } elseif ( $check_loopback && in_array( $header['response']['code'], $bad_http_check, true ) ) {
1135
+ $this->failed_edit = true;
1136
+ $this->write_file( $path, $original );
1137
+
1138
+ return array(
1139
+ 'notices' => array(
1140
+ array(
1141
+ 'type' => 'error',
1142
+ 'message' => __( 'A 500 server error was detected on your site after updating your file. We have restored the previous version of the file for you.', 'string-locator' ),
1143
+ ),
1144
+ ),
1145
+ );
1146
+ } else {
1147
+ return array(
1148
+ 'notices' => array(
1149
+ array(
1150
+ 'type' => 'success',
1151
+ 'message' => __( 'The file has been saved', 'string-locator' ),
1152
+ ),
1153
+ ),
1154
+ );
1155
+ }
1156
+ } else {
1157
+ return array(
1158
+ 'notices' => array(
1159
+ array(
1160
+ 'type' => 'error',
1161
+ 'message' => sprintf(
1162
+ // translators: %s: The file location that was sent.
1163
+ __( 'The file location provided, <strong>%s</strong>, is not valid.', 'string-locator' ),
1164
+ $_POST['string-locator-path']
1165
+ ),
1166
+ ),
1167
+ ),
1168
+ );
1169
+ }
1170
+ }
1171
+
1172
+ /**
1173
+ * When editing a file, this is where we write all the new content.
1174
+ * We will break early if the user isn't allowed to edit files.
1175
+ *
1176
+ * @param string $path The path to the file.
1177
+ * @param string $content The content to write to the file.
1178
+ *
1179
+ * @return void
1180
+ */
1181
+ private function write_file( $path, $content ) {
1182
+ if ( ! current_user_can( 'edit_themes' ) ) {
1183
+ return;
1184
+ }
1185
+
1186
+ // Verify the location is valid before we try using it.
1187
+ if ( ! $this->is_valid_location( $path ) ) {
1188
+ return;
1189
+ }
1190
+
1191
+ $back_compat_filter = apply_filters( 'string-locator-filter-closing-php-tags', true ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
1192
+
1193
+ if ( apply_filters( 'string_locator_filter_closing_php_tags', $back_compat_filter ) ) {
1194
+ $content = preg_replace( '/\?>$/si', '', trim( $content ), - 1, $replaced_strings );
1195
+
1196
+ if ( $replaced_strings >= 1 ) {
1197
+ $this->notice[] = array(
1198
+ 'type' => 'error',
1199
+ 'message' => __( 'We detected a PHP code tag ending, this has been automatically stripped out to help prevent errors in your code.', 'string-locator' ),
1200
+ );
1201
+ }
1202
+ }
1203
+
1204
+ $file = fopen( $path, 'w' );
1205
+ $lines = explode( "\n", str_replace( array( "\r\n", "\r" ), "\n", $content ) );
1206
+ $total_lines = count( $lines );
1207
+
1208
+ for ( $i = 0; $i < $total_lines; $i ++ ) {
1209
+ $write_line = $lines[ $i ];
1210
+
1211
+ if ( ( $i + 1 ) < $total_lines ) {
1212
+ $write_line .= PHP_EOL;
1213
+ }
1214
+
1215
+ fwrite( $file, $write_line );
1216
+ }
1217
+
1218
+ fclose( $file );
1219
+ }
1220
+
1221
+ /**
1222
+ * Hook the admin notices and loop over any notices we've registered in the plugin.
1223
+ *
1224
+ * @return void
1225
+ */
1226
+ function admin_notice() {
1227
+ if ( ! empty( $this->notice ) ) {
1228
+ foreach ( $this->notice as $note ) {
1229
+ printf(
1230
+ '<div class="%s"><p>%s</p></div>',
1231
+ esc_attr( $note['type'] ),
1232
+ $note['message']
1233
+ );
1234
+ }
1235
+ }
1236
+ }
1237
+
1238
+ /**
1239
+ * Scan through an individual file to look for occurrences of £string.
1240
+ *
1241
+ * @param string $filename The path to the file.
1242
+ * @param string $string The search string.
1243
+ * @param mixed $location The file location object/string.
1244
+ * @param string $type File type.
1245
+ * @param string $slug The plugin/theme slug of the file.
1246
+ * @param boolean $regex Should a regex search be performed.
1247
+ *
1248
+ * @return mixed
1249
+ */
1250
+ function scan_file( $filename, $string, $location, $type, $slug, $regex = false ) {
1251
+ if ( empty( $string ) || ! is_file( $filename ) ) {
1252
+ return false;
1253
+ }
1254
+ $output = array();
1255
+ $linenum = 0;
1256
+ $match_count = 0;
1257
+
1258
+ if ( ! is_object( $location ) ) {
1259
+ $path = $location;
1260
+ $location = explode( DIRECTORY_SEPARATOR, $location );
1261
+ $file = end( $location );
1262
+ } else {
1263
+ $path = $location->getPathname();
1264
+ $file = $location->getFilename();
1265
+ }
1266
+
1267
+ /*
1268
+ * Check if the filename matches our search pattern
1269
+ */
1270
+ if ( stristr( $file, $string ) || ( $regex && preg_match( $string, $file ) ) ) {
1271
+ $relativepath = str_replace(
1272
+ array(
1273
+ ABSPATH,
1274
+ '\\',
1275
+ '/',
1276
+ ),
1277
+ array(
1278
+ '',
1279
+ DIRECTORY_SEPARATOR,
1280
+ DIRECTORY_SEPARATOR,
1281
+ ),
1282
+ $path
1283
+ );
1284
+ $match_count ++;
1285
+
1286
+ $editurl = $this->create_edit_link( $path, $linenum );
1287
+
1288
+ $path_string = sprintf(
1289
+ '<a href="%s">%s</a>',
1290
+ esc_url( $editurl ),
1291
+ esc_html( $relativepath )
1292
+ );
1293
+
1294
+ $output[] = array(
1295
+ 'ID' => $match_count,
1296
+ 'linenum' => sprintf(
1297
+ '[%s]',
1298
+ esc_html__( 'Filename matches search', 'string-locator' )
1299
+ ),
1300
+ 'linepos' => '',
1301
+ 'path' => $path,
1302
+ 'filename' => $path_string,
1303
+ 'filename_raw' => $relativepath,
1304
+ 'editurl' => ( current_user_can( 'edit_themes' ) ? $editurl : false ),
1305
+ 'stringresult' => $file,
1306
+ );
1307
+ }
1308
+
1309
+ $readfile = @fopen( $filename, 'r' );
1310
+ if ( $readfile ) {
1311
+ while ( ( $readline = fgets( $readfile ) ) !== false ) { // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
1312
+ $string_preview_is_cut = false;
1313
+ $linenum ++;
1314
+ /**
1315
+ * If our string is found in this line, output the line number and other data
1316
+ */
1317
+ if ( ( ! $regex && stristr( $readline, $string ) ) || ( $regex && preg_match( $string, $readline, $match, PREG_OFFSET_CAPTURE ) ) ) {
1318
+ /**
1319
+ * Prepare the visual path for the end user
1320
+ * Removes path leading up to WordPress root and ensures consistent directory separators
1321
+ */
1322
+ $relativepath = str_replace(
1323
+ array(
1324
+ ABSPATH,
1325
+ '\\',
1326
+ '/',
1327
+ ),
1328
+ array(
1329
+ '',
1330
+ DIRECTORY_SEPARATOR,
1331
+ DIRECTORY_SEPARATOR,
1332
+ ),
1333
+ $path
1334
+ );
1335
+ $match_count ++;
1336
+
1337
+ if ( $regex ) {
1338
+ $str_pos = $match[0][1];
1339
+ } else {
1340
+ $str_pos = stripos( $readline, $string );
1341
+ }
1342
+
1343
+ /**
1344
+ * Create the URL to take the user to the editor
1345
+ */
1346
+ $editurl = $this->create_edit_link( $path, $linenum, $str_pos );
1347
+
1348
+ $string_preview = $readline;
1349
+ if ( strlen( $string_preview ) > ( strlen( $string ) + $this->excerpt_length ) ) {
1350
+ $string_location = strpos( $string_preview, $string );
1351
+
1352
+ $string_location_start = $string_location - $this->excerpt_length;
1353
+ if ( $string_location_start < 0 ) {
1354
+ $string_location_start = 0;
1355
+ }
1356
+
1357
+ $string_location_end = ( strlen( $string ) + ( $this->excerpt_length * 2 ) );
1358
+ if ( $string_location_end > strlen( $string_preview ) ) {
1359
+ $string_location_end = strlen( $string_preview );
1360
+ }
1361
+
1362
+ $string_preview = substr( $string_preview, $string_location_start, $string_location_end );
1363
+ $string_preview_is_cut = true;
1364
+ }
1365
+
1366
+ if ( $regex ) {
1367
+ $string_preview = preg_replace( preg_replace( '/\/(.+)\//', '/($1)/', $string ), '<strong>$1</strong>', esc_html( $string_preview ) );
1368
+ } else {
1369
+ $string_preview = str_ireplace( $string, '<strong>' . $string . '</strong>', esc_html( $string_preview ) );
1370
+ }
1371
+ if ( $string_preview_is_cut ) {
1372
+ $string_preview = sprintf(
1373
+ '&hellip;%s&hellip;',
1374
+ $string_preview
1375
+ );
1376
+ }
1377
+
1378
+ $path_string = sprintf(
1379
+ '<a href="%s">%s</a>',
1380
+ esc_url( $editurl ),
1381
+ esc_html( $relativepath )
1382
+ );
1383
+
1384
+ $output[] = array(
1385
+ 'ID' => $match_count,
1386
+ 'linenum' => $linenum,
1387
+ 'linepos' => $str_pos,
1388
+ 'path' => $path,
1389
+ 'filename' => $path_string,
1390
+ 'filename_raw' => $relativepath,
1391
+ 'editurl' => ( current_user_can( 'edit_themes' ) ? $editurl : false ),
1392
+ 'stringresult' => $string_preview,
1393
+ );
1394
+ }
1395
+ }
1396
+
1397
+ fclose( $readfile );
1398
+ } else {
1399
+ /**
1400
+ * The file was unreadable, give the user a friendly notification
1401
+ */
1402
+ $output[] = array(
1403
+ 'linenum' => '#',
1404
+ // translators: 1: Filename.
1405
+ 'filename' => esc_html( sprintf( __( 'Could not read file: %s', 'string-locator' ), $filename ) ),
1406
+ 'stringresult' => '',
1407
+ );
1408
+ }
1409
+
1410
+ return $output;
1411
+ }
1412
+
1413
+ function ajax_scan_path( $path ) {
1414
+ $files = array();
1415
+
1416
+ $paths = new RecursiveIteratorIterator(
1417
+ new RecursiveDirectoryIterator( $path ),
1418
+ RecursiveIteratorIterator::SELF_FIRST
1419
+ );
1420
+
1421
+ foreach ( $paths as $name => $location ) {
1422
+ if ( is_dir( $location->getPathname() ) ) {
1423
+ continue;
1424
+ }
1425
+
1426
+ $files[] = $location->getPathname();
1427
+ }
1428
+
1429
+ return $files;
1430
+ }
1431
+ }
options.php CHANGED
@@ -1,82 +1,138 @@
1
- <?php
2
- if ( ! defined( 'ABSPATH' ) ) {
3
- die();
4
- }
5
-
6
- $this_url = admin_url( ( is_multisite() ? 'network/admin.php' : 'tools.php' ) . '?page=string-locator' );
7
-
8
- $search_string = '';
9
- $search_location = '';
10
- $search_regex = false;
11
-
12
- if ( isset( $_POST['string-locator-string'] ) ) {
13
- $search_string = $_POST['string-locator-string'];
14
- }
15
- if ( isset( $_POST['string-locator-search'] ) ) {
16
- $search_location = $_POST['string-locator-search'];
17
- }
18
-
19
- if ( isset( $_GET['restore'] ) ) {
20
- $restore = unserialize( get_option( 'string-locator-search-overview' ) );
21
-
22
- $search_string = $restore->search;
23
- $search_location = $restore->directory;
24
- $search_regex = String_Locator::absbool( $restore->regex );
25
- }
26
- ?>
27
- <div class="wrap">
28
- <h2>
29
- <?php esc_html_e( 'String Locator', 'string-locator' ); ?>
30
- </h2>
31
-
32
- <form action="<?php echo esc_url( $this_url ); ?>" method="post" id="string-locator-search-form">
33
- <label for="string-locator-search"><?php esc_html_e( 'Search through', 'string-locator' ); ?></label>
34
- <select name="string-locator-search" id="string-locator-search">
35
- <optgroup label="<?php esc_attr_e( 'Core', 'string-locator' ); ?>">
36
- <option value="core"><?php esc_html_e( 'The whole WordPress directory', 'string-locator' ); ?></option>
37
- <option value="wp-content"><?php esc_html_e( 'Everything under wp-content', 'string-locator' ); ?></option>
38
- </optgroup>
39
- <optgroup label="<?php esc_attr_e( 'Themes', 'string-locator' ); ?>">
40
- <?php echo String_Locator::get_themes_options( $search_location ); ?>
41
- </optgroup>
42
- <?php if ( String_Locator::has_mu_plugins() ) : ?>
43
- <optgroup label="<?php esc_attr_e( 'Must Use Plugins', 'string-locator' ); ?>">
44
- <?php echo String_Locator::get_mu_plugins_options( $search_location ); ?>
45
- </optgroup>
46
- <?php endif; ?>
47
- <optgroup label="<?php esc_attr_e( 'Plugins', 'string-locator' ); ?>">
48
- <?php echo String_Locator::get_plugins_options( $search_location ); ?>
49
- </optgroup>
50
- </select>
51
-
52
- <label for="string-locator-string"><?php esc_html_e( 'Search string', 'string-locator' ); ?></label>
53
- <input type="text" name="string-locator-string" id="string-locator-string" value="<?php echo esc_attr( $search_string ); ?>" />
54
-
55
- <label><input type="checkbox" name="string-locator-regex" id="string-locator-regex"<?php echo ( $search_regex ? ' checked="checked"' : '' ); ?>'> <?php esc_html_e( 'RegEx search', 'string-locator' ); ?></label>
56
-
57
- <p>
58
- <input type="submit" name="submit" id="submit" class="button button-primary" value="<?php esc_html_e( 'Search', 'string-locator' ); ?>">
59
- <a href="<?php echo esc_url( $this_url . '&restore=true' ); ?>" class="button button-primary"><?php esc_html_e( 'Restore last search', 'string-locator' ); ?></a>
60
- </p>
61
- </form>
62
-
63
- <div class="notices"></div>
64
-
65
- <div class="string-locator-feedback hide">
66
- <progress id="string-locator-search-progress" max="100"></progress>
67
- <span id="string-locator-feedback-text"><?php esc_html_e( 'Preparing search&hellip;', 'string-locator' ); ?></span>
68
- </div>
69
-
70
- <div class="table-wrapper">
71
- <?php
72
- if ( isset( $_GET['restore'] ) ) {
73
- $items = maybe_unserialize( get_option( 'string-locator-search-history', array() ) );
74
-
75
- echo String_Locator::prepare_full_table( $items, array( 'restore' ) );
76
- }
77
- else {
78
- echo String_Locator::prepare_full_table( array() );
79
- }
80
- ?>
81
- </div>
82
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) {
3
+ die();
4
+ }
5
+
6
+ $this_url = admin_url( ( is_multisite() ? 'network/admin.php' : 'tools.php' ) . '?page=string-locator' );
7
+
8
+ $search_string = '';
9
+ $search_location = '';
10
+ $search_regex = false;
11
+
12
+ if ( isset( $_POST['string-locator-string'] ) ) {
13
+ $search_string = $_POST['string-locator-string'];
14
+ }
15
+ if ( isset( $_POST['string-locator-search'] ) ) {
16
+ $search_location = $_POST['string-locator-search'];
17
+ }
18
+
19
+ if ( isset( $_GET['restore'] ) ) {
20
+ $restore = get_transient( 'string-locator-search-overview' );
21
+
22
+ if ( false !== $restore ) {
23
+ $search_string = $restore->search;
24
+ $search_location = $restore->directory;
25
+ $search_regex = String_Locator::absbool( $restore->regex );
26
+ } else {
27
+ ?>
28
+ <div class="notice notice-large notice-warning">No previous searches could be restored.</div>
29
+ <?php
30
+ }
31
+ }
32
+ ?>
33
+ <div class="wrap">
34
+ <h1>
35
+ <?php esc_html_e( 'String Locator', 'string-locator' ); ?>
36
+ </h1>
37
+
38
+ <?php if ( ! current_user_can( 'edit_themes' ) ) : ?>
39
+ <div class="notice notice-warning inline">
40
+ <p>
41
+ <strong>
42
+ <?php esc_html_e( 'String Locator is limited to search mode only.', 'string-locator' ); ?>
43
+ </strong>
44
+ </p>
45
+ <p>
46
+ <?php esc_html_e( 'Because this site is configured to not allow direct file editing, the String Locator plugin has limited functionality and will only allow you to search for files with your string in them.', 'string-locator' ); ?>
47
+ </p>
48
+ </div>
49
+ <?php endif; ?>
50
+
51
+ <form action="<?php echo esc_url( $this_url ); ?>" method="post" id="string-locator-search-form">
52
+ <label for="string-locator-search"><?php esc_html_e( 'Search through', 'string-locator' ); ?></label>
53
+ <select name="string-locator-search" id="string-locator-search">
54
+ <optgroup label="<?php esc_attr_e( 'Core', 'string-locator' ); ?>">
55
+ <option value="core"><?php esc_html_e( 'The whole WordPress directory', 'string-locator' ); ?></option>
56
+ <option value="wp-content"><?php esc_html_e( 'Everything under wp-content', 'string-locator' ); ?></option>
57
+ </optgroup>
58
+ <optgroup label="<?php esc_attr_e( 'Themes', 'string-locator' ); ?>">
59
+ <?php echo String_Locator::get_themes_options( $search_location ); ?>
60
+ </optgroup>
61
+ <?php if ( String_Locator::has_mu_plugins() ) : ?>
62
+ <optgroup label="<?php esc_attr_e( 'Must Use Plugins', 'string-locator' ); ?>">
63
+ <?php echo String_Locator::get_mu_plugins_options( $search_location ); ?>
64
+ </optgroup>
65
+ <?php endif; ?>
66
+ <optgroup label="<?php esc_attr_e( 'Plugins', 'string-locator' ); ?>">
67
+ <?php echo String_Locator::get_plugins_options( $search_location ); ?>
68
+ </optgroup>
69
+ </select>
70
+
71
+ <label for="string-locator-string"><?php esc_html_e( 'Search string', 'string-locator' ); ?></label>
72
+ <input type="text" name="string-locator-string" id="string-locator-string" value="<?php echo esc_attr( $search_string ); ?>" />
73
+
74
+ <label><input type="checkbox" name="string-locator-regex" id="string-locator-regex"<?php echo ( $search_regex ? ' checked="checked"' : '' ); ?>> <?php esc_html_e( 'RegEx search', 'string-locator' ); ?></label>
75
+
76
+ <p>
77
+ <input type="submit" name="submit" id="submit" class="button button-primary" value="<?php esc_html_e( 'Search', 'string-locator' ); ?>">
78
+ <a href="<?php echo esc_url( $this_url . '&restore=true' ); ?>" class="button button-primary"><?php esc_html_e( 'Restore last search', 'string-locator' ); ?></a>
79
+ </p>
80
+ </form>
81
+
82
+ <div class="notices"></div>
83
+
84
+ <div class="string-locator-feedback hide">
85
+ <progress id="string-locator-search-progress" max="100"></progress>
86
+ <span id="string-locator-feedback-text"><?php esc_html_e( 'Preparing search&hellip;', 'string-locator' ); ?></span>
87
+ </div>
88
+
89
+ <div class="table-wrapper">
90
+ <?php
91
+ if ( isset( $_GET['restore'] ) ) {
92
+ $items = get_transient( 'string-locator-search-history' );
93
+ if ( false === $items ) {
94
+ $items = array();
95
+ }
96
+ $items = maybe_unserialize( $items );
97
+
98
+ echo String_Locator::prepare_full_table( $items, array( 'restore' ) );
99
+ } else {
100
+ echo String_Locator::prepare_full_table( array() );
101
+ }
102
+ ?>
103
+ </div>
104
+ </div>
105
+
106
+ <script id="tmpl-string-locator-search-result" type="text/template">
107
+ <tr>
108
+ <td>
109
+ {{{ data.stringresult }}}
110
+
111
+ <div class="row-actions">
112
+ <# if ( data.editurl ) { #>
113
+ <span class="edit">
114
+ <a href="{{ data.editurl }}" aria-label="Edit">
115
+ Edit
116
+ </a>
117
+ </span>
118
+ <# } #>
119
+ </div>
120
+ </td>
121
+ <td>
122
+ <# if ( data.editurl ) { #>
123
+ <a href="{{ data.editurl }}">
124
+ {{ data.filename_raw }}
125
+ </a>
126
+ <# } #>
127
+ <# if ( ! data.editurl ) { #>
128
+ {{ data.filename_raw }}
129
+ <# } #>
130
+ </td>
131
+ <td>
132
+ {{ data.linenum }}
133
+ </td>
134
+ <td>
135
+ {{ data.linepos }}
136
+ </td>
137
+ </tr>
138
+ </script>
readme.txt CHANGED
@@ -1,68 +1,59 @@
1
- === String locator ===
2
- Contributors: Clorith
3
- Author URI: http://www.clorith.net
4
- Plugin URI: http://wordpress.org/plugins/string-locator/
5
- Donate link: https://www.paypal.me/clorith
6
- Tags: theme, plugin, text, search, find, editor, syntax, highlight
7
- Requires at least: 4.9
8
- Tested up to: 4.9
9
- Stable tag: 2.3.1
10
- License: GPLv2 or later
11
- License URI: http://www.gnu.org/licenses/gpl-2.0.html
12
-
13
- Find and edit code or texts in your themes and plugins
14
-
15
- == Description ==
16
-
17
- When working on themes and plugins you often notice a piece of text that appears hardcoded into the files, you need to modify it, but you don't know what theme or plugin it's in, and certainly not which individual file to look in.
18
-
19
- Easily search through your themes, plugins or even WordPress core and be presented with a list of files, the matched text and what line of the file matched your search.
20
- You can then quickly make edits directly in your browser by clicking the link from the search results.
21
-
22
- By default a consistency check is performed when making edits to files, this will look for inconsistencies with braces, brackets and parenthesis that are often accidentally left in.
23
- This drastically reduces the risk of breaking your site when making edits, but is in no way an absolute guarantee.
24
-
25
-
26
- == Frequently asked questions ==
27
-
28
- = Will Smart-Scan guarantee my site is safe when making edits? =
29
- Although it will do it's best at detecting incorrect usage of the commonly used symbols (parenthesis, brackets and braces), there is no guarantee every possible error is detected. The best safe guard is to keep consistent backups of your site (even when not making edits).
30
-
31
- As of version 1.6, the plugin will check your site health after performing an edit. If the site is returning a site breaking error code, we'll revert to the previous version of the file.
32
-
33
- = My search is failing and I am told that my search is an invalid pattern =
34
- This error is only related to regex searches, and is based off how PHP reads your regex string.
35
-
36
- When writing your search string, make sure to wrap your search in forward slashes (`/`), directly followed by any modifiers like case insensitive (`i`) that you may want to use.
37
-
38
-
39
- == Screenshots ==
40
-
41
- 1. Searching through the Twenty Fourteen theme for the string 'not found'
42
- 2. Having clicked the link for one of the results and being taken to the editor in the browser
43
- 3. Smart-Scan has detected an inconsistency in the use of braces
44
-
45
- == Changelog ==
46
-
47
- = 2.3.1 =
48
- This is a maintenance and security release, with thanks to [RIPS Technologies](https://www.ripstech.com) for the responsible disclosure of several security concerns.
49
-
50
- * Fixed an escaped URL that should've allowed some HTML links.
51
- * Patched a potential security vulnerability with file path traversals.
52
- * Patched a potential security vulnerability that allowed writing to arbitrary files.
53
- * Patched a few Cross Site Scripting (XSS) vulnerabilities.
54
- * Removed unused code that might allow file creation.
55
-
56
- = 2.3.0 =
57
- * Upped version requirement to 4.9 as we now use the bundled CodeMirror in WordPress core.
58
- * Converted translation functions to the escaping versions to avoid accidental output from translations.
59
- * Removed bundled languages, these should be served by WordPress.org now.
60
- * Improved behavior when a search failure happens, we were accidentally looping error messages for every file (whoops).
61
- * Added more translatable strings.
62
- * Added various filters:
63
- ** `string_locator_bad_http_codes`
64
- ** `string_locator_bad_file_types`
65
- **
66
-
67
- = Older entries =
68
- See changelog.txt for the version history
1
+ === String locator ===
2
+ Contributors: Clorith
3
+ Author URI: http://www.clorith.net
4
+ Plugin URI: http://wordpress.org/plugins/string-locator/
5
+ Donate link: https://www.paypal.me/clorith
6
+ Tags: theme, plugin, text, search, find, editor, syntax, highlight
7
+ Requires at least: 4.9
8
+ Tested up to: 5.4
9
+ Stable tag: 2.4.0
10
+ License: GPLv2 or later
11
+ License URI: http://www.gnu.org/licenses/gpl-2.0.html
12
+
13
+ Find and edit code or texts in your themes and plugins
14
+
15
+ == Description ==
16
+
17
+ When working on themes and plugins you often notice a piece of text that appears hardcoded into the files, you need to modify it, but you don't know what theme or plugin it's in, and certainly not which individual file to look in.
18
+
19
+ Easily search through your themes, plugins or even WordPress core and be presented with a list of files, the matched text and what line of the file matched your search.
20
+ You can then quickly make edits directly in your browser by clicking the link from the search results.
21
+
22
+ By default a consistency check is performed when making edits to files, this will look for inconsistencies with braces, brackets and parenthesis that are often accidentally left in.
23
+ This drastically reduces the risk of breaking your site when making edits, but is in no way an absolute guarantee.
24
+
25
+
26
+ == Frequently asked questions ==
27
+
28
+ = Will Smart-Scan guarantee my site is safe when making edits? =
29
+ Although it will do it's best at detecting incorrect usage of the commonly used symbols (parenthesis, brackets and braces), there is no guarantee every possible error is detected. The best safe guard is to keep consistent backups of your site (even when not making edits).
30
+
31
+ As of version 1.6, the plugin will check your site health after performing an edit. If the site is returning a site breaking error code, we'll revert to the previous version of the file.
32
+
33
+ = My search is failing and I am told that my search is an invalid pattern =
34
+ This error is only related to regex searches, and is based off how PHP reads your regex string.
35
+
36
+ When writing your search string, make sure to wrap your search in forward slashes (`/`), directly followed by any modifiers like case insensitive (`i`) that you may want to use.
37
+
38
+
39
+ == Screenshots ==
40
+
41
+ 1. Searching WordPress for the string `hello dolly`.
42
+ 2. Search screen when editing is disabled.
43
+ 3. Having clicked the link for one of the results and being taken to the editor in the browser.
44
+ 4. Smart-Scan has detected an inconsistency in the use of braces.
45
+
46
+ == Changelog ==
47
+
48
+ = 2.4.0 =
49
+ * Updated the editor screen, to a design which more closely adheres to the WordPress editor styles.
50
+ * Added support for searching files, even if you are not able to edit them.
51
+ * Added support for jumping to not just line number, but also location inside that line.
52
+ * Added alternative to disable loopback checks when saving changes.
53
+ * Improved performance by using transients instead of option entries (lower memory usage overall).
54
+ * Improved handling of errors with links to some documentation when available.
55
+ * Improved the amount of details about the current file that are shown in the editor.
56
+ * Fixed the search results table to look like a normal table when restoring a search.
57
+
58
+ = Older entries =
59
+ See changelog.txt for the version history
 
 
 
 
 
 
 
 
 
resources/css/string-locator.css CHANGED
@@ -1,92 +1,2 @@
1
- /* Basic styling */
2
- .wrap > h2 {
3
- margin-bottom: 15px;
4
- }
5
-
6
- .string-locator-feedback {
7
- background: #fff;
8
- display: inline-block;
9
- width: 100%;
10
- text-align: center;
11
- }
12
- .string-locator-feedback progress {
13
- width: 100%;
14
- height: 1.5em;
15
- }
16
- .string-locator-feedback #string-locator-feedback-text {
17
- display: inline-block;
18
- text-align: center;
19
- width: 100%;
20
- }
21
- .string-locator-feedback.hide {
22
- display: none;
23
- }
24
- table.tools_page_string-locator {
25
- display: none;
26
- }
27
- table.tools_page_string-locator.restore {
28
- display: block;
29
- }
30
-
31
- .string-locator-edit-wrap {
32
- box-sizing: border-box;
33
- display: inline-block;
34
- margin-right: 3%;
35
- vertical-align: top;
36
- width: 70%;
37
- direction: ltr;
38
- }
39
-
40
- .string-locator-sidebar-wrap {
41
- display: inline-block;
42
- vertical-align: top;
43
- width: 25%;
44
- }
45
- .string-locator-details {
46
- background: #fff;
47
- border: 1px solid #e5e5e5;
48
- box-shadow: 0 1px 1px rgba( 0, 0, 0, 0.04 );
49
- box-sizing: border-box;
50
- display: block;
51
- margin-top: 2em;
52
- }
53
- .string-locator-sidebar-wrap > div:first-of-type {
54
- margin-top: 0;
55
- }
56
- .string-locator-theme-details {
57
- box-sizing: border-box;
58
- height: auto;
59
- padding: 10px;
60
- width: 100%;
61
- }
62
- .string-locator-theme-details h2 small {
63
- font-size: 50%;
64
- }
65
-
66
- .string-locator-actions {
67
- background: #f5f5f5;
68
- border-top: 1px solid #ddd;
69
- box-sizing: border-box;
70
- display: inline-block;
71
- padding: 10px;
72
- width: 100%;
73
- }
74
-
75
- .string-locator-italics {
76
- font-style: italic;
77
- }
78
-
79
- /* Media Queries for responsive behavior */
80
- @media (max-width: 1065px) {
81
- .string-locator-edit-wrap {
82
- display: block;
83
- margin-bottom: 20px;
84
- width: 99%;
85
- }
86
-
87
- .string-locator-sidebar-wrap {
88
- display: block;
89
- margin-bottom: 20px;
90
- width: 99%;
91
- }
92
- }
1
+ .wrap>h1{margin-bottom:15px}.string-locator-italics{font-style:italic}.string-locator-feedback{background:#fff;display:inline-block;width:100%;text-align:center}.string-locator-feedback.hide{display:none}.string-locator-feedback progress{width:100%;height:1.5em}.string-locator-feedback #string-locator-feedback-text{display:inline-block;text-align:center;width:100%}body.tools_page_string-locator.file-edit-screen #wpcontent{padding-left:0}body.tools_page_string-locator.file-edit-screen #wpfooter{display:none}body.tools_page_string-locator.file-edit-screen #wpbody-content{padding-bottom:0}table.tools_page_string-locator{display:none}table.tools_page_string-locator.restore{display:table}.string-locator-editor-wrapper{width:100%;height:100%;display:grid;grid-gap:0;grid-template-columns:80% 20%}.string-locator-editor-wrapper .notice,.string-locator-editor-wrapper .string-locator-header{height:40px;padding:4px 2px;border-bottom:1px solid #e2e4e7;background:#fff;display:flex;flex-direction:row;align-items:stretch;justify-content:space-between;z-index:30;right:0;left:0;top:0;position:sticky}@media (min-width: 600px){.string-locator-editor-wrapper .notice,.string-locator-editor-wrapper .string-locator-header{position:fixed;padding:8px;top:46px}}@media (min-width: 782px){.string-locator-editor-wrapper .notice,.string-locator-editor-wrapper .string-locator-header{top:32px;left:160px}}.string-locator-editor-wrapper .notice .title,.string-locator-editor-wrapper .string-locator-header .title{font-size:16px}.string-locator-editor-wrapper .notice>div,.string-locator-editor-wrapper .string-locator-header>div{display:inline-flex;align-items:center}.string-locator-editor-wrapper .notice .button,.string-locator-editor-wrapper .string-locator-header .button{margin:0 3px 0 12px}.string-locator-editor-wrapper .notice{height:fit-content;margin:0;top:89px;display:block}.string-locator-editor-wrapper .notice.is-dismissible{position:sticky}.string-locator-editor-wrapper .string-locator-editor{margin-top:57px}.string-locator-editor-wrapper .string-locator-sidebar{margin-top:57px;background:#fff;border-left:1px solid #e2e4e7}.string-locator-editor-wrapper .string-locator-sidebar .string-locator-panel{border-top:1px solid #e2e4e7;padding-bottom:10px}.string-locator-editor-wrapper .string-locator-sidebar .string-locator-panel:first-of-type{border-top:none}.string-locator-editor-wrapper .string-locator-sidebar .string-locator-panel .title{color:#191e23;border:none;box-shadow:none;font-weight:600;padding:15px;margin:0}.string-locator-editor-wrapper .string-locator-sidebar .string-locator-panel .row{padding:5px 15px}.string-locator-editor-wrapper .CodeMirror .CodeMirror-activeline .CodeMirror-activeline-background{background-color:#cfe4ff}.string-locator-editor-wrapper .CodeMirror .CodeMirror-activeline .CodeMirror-gutter-background{background-color:#cfe4ff}
2
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
resources/js/string-locator-search.js CHANGED
@@ -1,148 +1 @@
1
- jQuery(document).ready(function ($) {
2
- var string_locator_search_active = false;
3
-
4
- var add_notice = function( title, message, format ) {
5
- $(".notices").append( '<div class="notice notice-' + format + ' is-dismissible"><p><strong>' + title + '</strong><br />' + message + '</p></div>' );
6
- };
7
-
8
- var throw_error = function( title, message ) {
9
- string_locator_search_active = false;
10
- $(".string-locator-feedback").hide();
11
- add_notice( title, message, 'error' );
12
- };
13
-
14
- var finalize_string_locator_search = function() {
15
- string_locator_search_active = false;
16
-
17
- $("#string-locator-feedback-text").text('');
18
-
19
- var search_finalized = {
20
- action : 'string-locator-clean',
21
- nonce : string_locator.search_nonce
22
- };
23
-
24
- $.post(
25
- string_locator.ajax_url,
26
- search_finalized,
27
- function( response ) {
28
- $(".string-locator-feedback").hide();
29
- if ( $("tbody", ".tools_page_string-locator").is(":empty") ) {
30
- $("tbody", ".tools_page_string-locator").html( '<tr><td colspan="3">' + string_locator.search_no_results + '</td></tr>' );
31
- }
32
- }
33
- ).fail(function(xhr, textStatus, errorThrown) {
34
- throw_error( xhr.status + ' ' + errorThrown, string_locator.search_error );
35
- });
36
- };
37
-
38
- var clear_string_locator_result_area = function() {
39
- $(".notices").html('');
40
- $("#string-locator-search-progress").removeAttr( 'value' );
41
- $("tbody", ".tools_page_string-locator").html('');
42
- };
43
-
44
- var perform_string_locator_single_search = function( maxCount, thisCount ) {
45
- if ( thisCount >= maxCount || ! string_locator_search_active ) {
46
- $("#string-locator-feedback-text").html( string_locator.saving_results_string );
47
- finalize_string_locator_search();
48
- return false;
49
- }
50
-
51
- var search_request = {
52
- action : 'string-locator-search',
53
- filenum : thisCount,
54
- nonce : string_locator.search_nonce
55
- };
56
-
57
- $.post(
58
- string_locator.ajax_url,
59
- search_request,
60
- function ( response ) {
61
- if ( ! response.success ) {
62
- if ( false === response.data.continue ) {
63
- throw_error( string_locator.warning_title, response.data.message );
64
- return false;
65
- } else {
66
- add_notice( string_locator.warning_title, response.data.message, 'warning' );
67
- }
68
- }
69
-
70
- if ( undefined !== response.data.search ) {
71
- $("#string-locator-search-progress").val( response.data.filenum );
72
- $("#string-locator-feedback-text").html( string_locator.search_current_prefix + response.data.next_file );
73
-
74
- string_locator_append_result( response.data.search );
75
- }
76
- var nextCount = response.data.filenum + 1;
77
- perform_string_locator_single_search( maxCount, nextCount );
78
- },
79
- 'json'
80
- ).fail(function(xhr, textStatus, errorThrown) {
81
- throw_error( xhr.status + ' ' + errorThrown, string_locator.search_error );
82
- });
83
- };
84
-
85
- var string_locator_append_result = function( total_entries ) {
86
- if ( $(".no-items", ".tools_page_string-locator").is(':visible') ) {
87
- $(".no-items", ".tools_page_string-locator").hide();
88
- }
89
- if ( Array !== total_entries.constructor ) {
90
- return false;
91
- }
92
-
93
- total_entries.forEach( function ( entries ) {
94
- if ( entries ) {
95
- for (var i = 0, amount = entries.length; i < amount; i++) {
96
-
97
- var entry = entries[i];
98
-
99
- if (undefined !== entry.stringresult) {
100
- var builtHTML = '<tr>' +
101
- '<td>' + entry.stringresult + '<div class="row-actions"><span class="edit"><a href="' + entry.editurl + '" aria-label="Edit">Edit</a></span></div></td>' +
102
- '<td>' + entry.filename + '</td>' +
103
- '<td>' + entry.linenum + '</td>' +
104
- '</tr>';
105
-
106
- $("tbody", ".tools_page_string-locator").append(builtHTML);
107
- }
108
- }
109
- }
110
- } );
111
- };
112
-
113
-
114
- $("#string-locator-search-form").on( 'submit', function (e) {
115
- e.preventDefault();
116
- $("#string-locator-feedback-text").text(string_locator.search_preparing );
117
- $(".string-locator-feedback").show();
118
- string_locator_search_active = true;
119
- clear_string_locator_result_area();
120
-
121
- var directory_request = {
122
- action : 'string-locator-get-directory-structure',
123
- directory : $("#string-locator-search").val(),
124
- search : $("#string-locator-string").val(),
125
- regex : $("#string-locator-regex").is(':checked'),
126
- nonce : string_locator.search_nonce
127
- };
128
-
129
- $("table.tools_page_string-locator").show();
130
-
131
- $.post(
132
- string_locator.ajax_url,
133
- directory_request,
134
- function ( response ) {
135
- if ( ! response.success ) {
136
- add_notice( response.data, 'alert' );
137
- return;
138
- }
139
- $("#string-locator-search-progress").attr( 'max', response.data.total ).val( response.data.current );
140
- $("#string-locator-feedback-text").text(string_locator.search_started );
141
- perform_string_locator_single_search( response.data.total, 0 );
142
- },
143
- 'json'
144
- ).fail(function(xhr, textStatus, errorThrown) {
145
- throw_error( xhr.status + ' ' + errorThrown, string_locator.search_error );
146
- });
147
- });
148
- });
1
+ !function(t){var r={};function o(e){if(r[e])return r[e].exports;var n=r[e]={i:e,l:!1,exports:{}};return t[e].call(n.exports,n,n.exports,o),n.l=!0,n.exports}o.m=t,o.c=r,o.d=function(t,r,e){o.o(t,r)||Object.defineProperty(t,r,{enumerable:!0,get:e})},o.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},o.t=function(t,r){if(1&r&&(t=o(t)),8&r)return t;if(4&r&&"object"==typeof t&&t&&t.__esModule)return t;var e=Object.create(null);if(o.r(e),Object.defineProperty(e,"default",{enumerable:!0,value:t}),2&r&&"string"!=typeof t)for(var n in t)o.d(e,n,function(r){return t[r]}.bind(null,n));return e},o.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return o.d(r,"a",r),r},o.o=function(t,r){return Object.prototype.hasOwnProperty.call(t,r)},o.p="",o(o.s=3)}([,,,function(t,r,o){t.exports=o(4)},function(t,r){jQuery(document).ready((function(t){var r=!1,o=wp.template("string-locator-search-result");function e(r,o,e){t(".notices").append('<div class="notice notice-'+e+' is-dismissible"><p><strong>'+r+"</strong><br />"+o+"</p></div>")}function n(o,n){r=!1,t(".string-locator-feedback").hide(),e(o,n,"error")}function a(s,c){if(c>=s||!r)return t("#string-locator-feedback-text").html(string_locator.saving_results_string),function(){r=!1,t("#string-locator-feedback-text").text("");var o={action:"string-locator-clean",nonce:string_locator.search_nonce};t.post(string_locator.ajax_url,o,(function(){t(".string-locator-feedback").hide(),t("tbody",".tools_page_string-locator").is(":empty")&&t("tbody",".tools_page_string-locator").html('<tr><td colspan="3">'+string_locator.search_no_results+"</td></tr>")})).fail((function(t,r,o){n(t.status+" "+o,string_locator.search_error)}))}(),!1;var i={action:"string-locator-search",filenum:c,nonce:string_locator.search_nonce};t.post(string_locator.ajax_url,i,(function(r){if(!r.success){if(!1===r.data.continue)return n(string_locator.warning_title,r.data.message),!1;e(string_locator.warning_title,r.data.message,"warning")}void 0!==r.data.search&&(t("#string-locator-search-progress").val(r.data.filenum),t("#string-locator-feedback-text").html(string_locator.search_current_prefix+r.data.next_file),function(r){t(".no-items",".tools_page_string-locator").is(":visible")&&t(".no-items",".tools_page_string-locator").hide();if(Array!==r.constructor)return!1;r.forEach((function(r){if(r)for(var e=0,n=r.length;e<n;e++){var a=r[e];void 0!==a.stringresult&&t("tbody",".tools_page_string-locator").append(o(a))}}))}(r.data.search));var c=r.data.filenum+1;a(s,c)}),"json").fail((function(t,r,o){n(t.status+" "+o,string_locator.search_error)}))}t("#string-locator-search-form").on("submit",(function(o){o.preventDefault(),t("#string-locator-feedback-text").text(string_locator.search_preparing),t(".string-locator-feedback").show(),r=!0,t(".notices").html(""),t("#string-locator-search-progress").removeAttr("value"),t("tbody",".tools_page_string-locator").html("");var s={action:"string-locator-get-directory-structure",directory:t("#string-locator-search").val(),search:t("#string-locator-string").val(),regex:t("#string-locator-regex").is(":checked"),nonce:string_locator.search_nonce};t("table.tools_page_string-locator").show(),t.post(string_locator.ajax_url,s,(function(r){r.success?(t("#string-locator-search-progress").attr("max",r.data.total).val(r.data.current),t("#string-locator-feedback-text").text(string_locator.search_started),a(r.data.total,0)):e(r.data,"alert")}),"json").fail((function(t,r,o){n(t.status+" "+o,string_locator.search_error)}))}))}))}]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
resources/js/string-locator.js CHANGED
@@ -1,27 +1 @@
1
- jQuery(document).ready(function ($) {
2
- var StringLocator;
3
- if ( false !== string_locator.CodeMirror && '' !== string_locator.CodeMirror ) {
4
- StringLocator = wp.codeEditor.initialize('code-editor', string_locator.CodeMirror);
5
-
6
- function resizeEditor(editor) {
7
- console.dir(editor);
8
- var setEditorSize = ( Math.max(document.documentElement.clientHeight, window.innerHeight || 0) - 177 );
9
- editor.setSize(null, parseInt(setEditorSize));
10
- }
11
-
12
- $(".string-locator-edit-goto").click(function (e) {
13
- e.preventDefault();
14
- StringLocator.codemirror.scrollIntoView(parseInt($(this).data('goto-line')));
15
- StringLocator.codemirror.setCursor(parseInt($(this).data('goto-line') - 1), 0);
16
- });
17
-
18
- resizeEditor(StringLocator.codemirror);
19
- StringLocator.codemirror.scrollIntoView(parseInt(string_locator.goto_line));
20
- StringLocator.codemirror.setCursor(parseInt(string_locator.goto_line - 1), 0);
21
- } else {
22
- StringLocator = $("#code-editor");
23
-
24
- StringLocator.css( 'width', $(".string-locator-edit-wrap").width() );
25
- StringLocator.css( 'height', parseInt( ( Math.max(document.documentElement.clientHeight, window.innerHeight || 0) - 177 ) ) );
26
- }
27
- });
1
+ !function(t){var e={};function o(r){if(e[r])return e[r].exports;var n=e[r]={i:r,l:!1,exports:{}};return t[r].call(n.exports,n,n.exports,o),n.l=!0,n.exports}o.m=t,o.c=e,o.d=function(t,e,r){o.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},o.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},o.t=function(t,e){if(1&e&&(t=o(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(o.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var n in t)o.d(r,n,function(e){return t[e]}.bind(null,n));return r},o.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return o.d(e,"a",e),e},o.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},o.p="",o(o.s=0)}([function(t,e,o){o(1),t.exports=o(2)},function(t,e){jQuery(document).ready((function(t){var e;if(!1!==string_locator.CodeMirror&&""!==string_locator.CodeMirror){e=wp.codeEditor.initialize("code-editor",string_locator.CodeMirror);var o=wp.template("string-locator-alert");function r(t){var e=Math.max(document.documentElement.clientHeight,window.innerHeight||0)-89;t.setSize(null,parseInt(e))}t(".string-locator-editor").on("click",".string-locator-edit-goto",(function(o){o.preventDefault(),e.codemirror.scrollIntoView(parseInt(t(this).data("goto-line"))),e.codemirror.setCursor(parseInt(t(this).data("goto-line")-1),t(this).data("goto-linepos"))})),t("body").on("submit","#string-locator-edit-form",(function(e){var r=t("#string-locator-notices");return t.post(string_locator.save_url,t(this).serialize()).always((function(e){void 0===e.notices?r.append(o({type:"error",message:e.responseText})):t.each(e.notices,(function(){r.append(o(this))}))})),e.preventDefault(),!1})),r(e.codemirror),e.codemirror.scrollIntoView(parseInt(string_locator.goto_line)),e.codemirror.setCursor(parseInt(string_locator.goto_line-1),parseInt(string_locator.goto_linepos)),window.addEventListener("resize",r(e.codemirror))}else(e=t("#code-editor")).css("width",t(".string-locator-edit-wrap").width()),e.css("height",parseInt(Math.max(document.documentElement.clientHeight,window.innerHeight||0)-89));t("#string-locator-notices").on("click",".notice-dismiss",(function(e){return t(this).closest(".notice").slideUp(400,"swing",(function(){t(this).remove()})),e.preventDefault(),!1}))}))},function(t,e,o){}]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
string-locator.php CHANGED
@@ -1,1275 +1,40 @@
1
- <?php
2
- /**
3
- * Plugin Name: String Locator
4
- * Plugin URI: http://www.clorith.net/wordpress-string-locator/
5
- * Description: Scan through theme and plugin files looking for text strings
6
- * Version: 2.3.1
7
- * Author: Clorith
8
- * Author URI: http://www.clorith.net
9
- * Text Domain: string-locator
10
- * License: GPL2
11
- *
12
- * Copyright 2013 Marius Jensen (email : marius@jits.no)
13
- *
14
- * This program is free software; you can redistribute it and/or modify
15
- * it under the terms of the GNU General Public License, version 2, as
16
- * published by the Free Software Foundation.
17
- *
18
- * This program is distributed in the hope that it will be useful,
19
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
- * GNU General Public License for more details.
22
- *
23
- * You should have received a copy of the GNU General Public License
24
- * along with this program; if not, write to the Free Software
25
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26
- */
27
-
28
- if ( ! defined( 'ABSPATH' ) ) {
29
- die();
30
- }
31
-
32
- /**
33
- * Class String_Locator
34
- */
35
- class String_Locator {
36
- /**
37
- * @var string $string_locator_language The code language used for the editing page.
38
- * @var string $version String Locator version number.
39
- * @var array $notice An array containing all notices to display.
40
- * @var bool $failed_edit Has there been a failed edit.
41
- * @var string $plugin_url The URL to the plugins directory.
42
- * @var string $path_to_use The path to the currently editable file.
43
- * @var array $bad_http_codes An array of HTTP status codes that will trigger the rollback feature.
44
- * @var array $bad_file_types An array of file extensions that will be ignored by the scanner.
45
- * @var int $excerpt_length The length of the excerpt from the line containing a match.
46
- * @var int|null $max_execution_time The server-configured max time a script can run.
47
- * @var int $start_execution_time The current time when our script started executing.
48
- * @var int $max_memory_consumption The server-configured max amount of memory a script can use.
49
- */
50
- public $string_locator_language = '';
51
- public $version = '2.3.1';
52
- public $notice = array();
53
- public $failed_edit = false;
54
- private $plugin_url = '';
55
- private $path_to_use = '';
56
- private $bad_http_codes = array( '500' );
57
- private $bad_file_types = array( 'rar', '7z', 'zip', 'tar', 'gz', 'jpg', 'jpeg', 'png', 'gif', 'mp3', 'mp4', 'avi', 'wmv' );
58
- private $excerpt_length = 25;
59
- private $max_execution_time = null;
60
- private $start_execution_timer = 0;
61
- private $max_memory_consumption = 0;
62
-
63
- /**
64
- * Construct the plugin
65
- */
66
- function __construct() {
67
- $this->init();
68
- }
69
-
70
- /**
71
- * The plugin initialization, ready as a stand alone function so it can be instantiated in other
72
- * scenarios as well.
73
- *
74
- * @since 2.2.0
75
- *
76
- * @return void
77
- */
78
- public function init() {
79
- /**
80
- * Define class variables requiring expressions
81
- */
82
- $this->plugin_url = plugin_dir_url( __FILE__ );
83
- $this->path_to_use = ( is_multisite() ? 'network/admin.php' : 'tools.php' );
84
- $this->excerpt_length = apply_filters( 'string_locator_excerpt_length', 25 );
85
-
86
- $this->max_execution_time = absint( ini_get( 'max_execution_time' ) );
87
- $this->start_execution_timer = microtime( true );
88
-
89
- if ( $this->max_execution_time > 30 ) {
90
- $this->max_execution_time = 30;
91
- }
92
-
93
- $this->set_memory_limit();
94
-
95
- add_action( 'admin_menu', array( $this, 'populate_menu' ) );
96
- add_action( 'network_admin_menu', array( $this, 'populate_network_menu' ) );
97
-
98
- add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ), 11 );
99
-
100
- add_action( 'plugins_loaded', array( $this, 'load_i18n' ) );
101
-
102
- add_action( 'admin_init', array( $this, 'editor_save' ) );
103
- add_action( 'admin_notices', array( $this, 'admin_notice' ) );
104
-
105
- add_action( 'wp_ajax_string-locator-get-directory-structure', array( $this, 'ajax_get_directory_structure' ) );
106
- add_action( 'wp_ajax_string-locator-search', array( $this, 'ajax_file_search' ) );
107
- add_action( 'wp_ajax_string-locator-clean', array( $this, 'ajax_clean_search' ) );
108
-
109
- add_filter( 'plugin_row_meta', array( $this, 'plugin_row_meta' ), 10, 2 );
110
- }
111
-
112
- /**
113
- * Sets up the memory limit variables.
114
- *
115
- * @since 2.0.0
116
- *
117
- * @return void
118
- */
119
- function set_memory_limit() {
120
- $memory_limit = ini_get( 'memory_limit' );
121
-
122
- $this->max_memory_consumption = absint( $memory_limit );
123
-
124
- if ( strstr( $memory_limit, 'k' ) ) {
125
- $this->max_memory_consumption = ( str_replace( 'k', '', $memory_limit ) * 1000 );
126
- }
127
- if ( strstr( $memory_limit, 'M' ) ) {
128
- $this->max_memory_consumption = ( str_replace( 'M', '', $memory_limit ) * 1000000 );
129
- }
130
- if ( strstr( $memory_limit, 'G' ) ) {
131
- $this->max_memory_consumption = ( str_replace( 'G', '', $memory_limit ) * 1000000000 );
132
- }
133
- }
134
-
135
- /**
136
- * Add a donation link to the plugins page.
137
- *
138
- * @param array $meta An array of meta links for this plugin.
139
- * @param string $plugin_file The main plugin file name, used to identify our own plugin.
140
- *
141
- * @return array
142
- */
143
- function plugin_row_meta( $meta, $plugin_file ) {
144
- if ( 'string-locator/string-locator.php' == $plugin_file ) {
145
- $meta[] = sprintf(
146
- '<a href="https://www.paypal.me/clorith">%s</a>',
147
- esc_html__( 'Donate to this plugin', 'string-locator' )
148
- );
149
- }
150
-
151
- return $meta;
152
- }
153
-
154
- /**
155
- * Create a set of drop-down options for picking one of the available themes.
156
- *
157
- * @param string $current The current selection option to match against.
158
- *
159
- * @return string
160
- */
161
- public static function get_themes_options( $current = null ) {
162
- $options = sprintf(
163
- '<option value="%s" %s>&mdash; %s &mdash;</option>',
164
- 't--',
165
- ( $current == 't--' ? 'selected="selected"' : '' ),
166
- esc_html( __( 'All themes', 'string-locator' ) )
167
- );
168
-
169
- $string_locate_themes = wp_get_themes();
170
-
171
- foreach ( $string_locate_themes AS $string_locate_theme_slug => $string_locate_theme ) {
172
- $string_locate_theme_data = wp_get_theme( $string_locate_theme_slug );
173
- $string_locate_value = 't-' . $string_locate_theme_slug;
174
-
175
- $options .= sprintf(
176
- '<option value="%s" %s>%s</option>',
177
- $string_locate_value,
178
- ( $current == $string_locate_value ? 'selected="selected"' : '' ),
179
- $string_locate_theme_data->Name
180
- );
181
- }
182
-
183
- return $options;
184
- }
185
-
186
- public static function get_edit_form_url() {
187
- $url_query = array(
188
- 'page' => ( isset( $_GET['page'] ) ? $_GET['page'] : '' ),
189
- 'edit-file' => ( isset( $_GET['edit-file'] ) ? $_GET['edit-file'] : '' ),
190
- 'file-reference' => ( isset( $_GET['file-reference'] ) ? $_GET['file-reference'] : '' ),
191
- 'file-type' => ( isset( $_GET['file-type'] ) ? $_GET['file-type'] : '' ),
192
- 'string-locator-line' => ( isset( $_GET['string-locator-line'] ) ? $_GET['string-locator-line'] : '' ),
193
- 'string-locator-path' => ( isset( $_GET['string-locator-path'] ) ? $_GET['string-locator-path'] : '' ),
194
- );
195
-
196
- return admin_url( sprintf(
197
- 'tools.php?%s',
198
- build_query( $url_query )
199
- ) );
200
- }
201
-
202
- /**
203
- * Create a set of drop-down options for picking one of the available plugins.
204
- *
205
- * @param string $current The current selection option to match against.
206
- *
207
- * @return string
208
- */
209
- public static function get_plugins_options( $current = null ) {
210
- $options = sprintf(
211
- '<option value="%s" %s>&mdash; %s &mdash;</option>',
212
- 'p--',
213
- ( $current == 'p--' ? 'selected="selected"' : '' ),
214
- esc_html( __( 'All plugins', 'string-locator' ) )
215
- );
216
-
217
- $string_locate_plugins = get_plugins();
218
-
219
- foreach ( $string_locate_plugins AS $string_locate_plugin_path => $string_locate_plugin ) {
220
- $string_locate_value = 'p-' . $string_locate_plugin_path;
221
-
222
- $options .= sprintf(
223
- '<option value="%s" %s>%s</option>',
224
- $string_locate_value,
225
- ( $current == $string_locate_value ? 'selected="selected"' : '' ),
226
- $string_locate_plugin['Name']
227
- );
228
- }
229
-
230
- return $options;
231
- }
232
-
233
- /**
234
- * Create a set of drop-down options for picking one of the available must-use plugins.
235
- *
236
- * @param string $current The current selection option to match against.
237
- *
238
- * @return string
239
- */
240
- public static function get_mu_plugins_options( $current = null ) {
241
- $options = sprintf(
242
- '<option value="%s" %s>&mdash; %s &mdash;</option>',
243
- 'mup--',
244
- ( 'mup--' == $current ? 'selected="selected"' : '' ),
245
- esc_html__( 'All must-use plugins', 'string-locator' )
246
- );
247
-
248
- $string_locate_plugins = get_mu_plugins();
249
-
250
- foreach ( $string_locate_plugins AS $string_locate_plugin_path => $string_locate_plugin ) {
251
- $string_locate_value = 'mup-' . $string_locate_plugin_path;
252
-
253
- $options .= sprintf(
254
- '<option value="%s" %s>%s</option>',
255
- $string_locate_value,
256
- ( $current == $string_locate_value ? 'selected="selected"' : '' ),
257
- $string_locate_plugin['Name']
258
- );
259
- }
260
-
261
- return $options;
262
- }
263
-
264
- /**
265
- * Check if there are Must-Use plugins available on this WordPress install.
266
- *
267
- * @since 2.2.0
268
- *
269
- * @return bool
270
- */
271
- public static function has_mu_plugins() {
272
- $mu_plugin_count = get_mu_plugins();
273
-
274
- if ( count( $mu_plugin_count ) >= 1 ) {
275
- return true;
276
- }
277
-
278
- return false;
279
- }
280
-
281
- /**
282
- * Handles the AJAX request to prepare the search hierarchy.
283
- *
284
- * @return void
285
- */
286
- function ajax_get_directory_structure() {
287
- if ( ! check_ajax_referer( 'string-locator-search', 'nonce', false ) ) {
288
- wp_send_json_error( __( 'Authentication failed', 'string-locator' ) );
289
- }
290
-
291
- $scan_path = $this->prepare_scan_path( $_POST['directory'] );
292
- if ( is_file( $scan_path->path ) ) {
293
- $files = array( $scan_path->path );
294
- } else {
295
- $files = $this->ajax_scan_path( $scan_path->path );
296
- }
297
-
298
- /*
299
- * Make sure each chunk of file arrays never exceeds 500 files
300
- * This is to prevent the SQL string from being too large and crashing everything
301
- */
302
- $file_chunks = array_chunk( $files, apply_filters( 'string-locator-files-per-array', 500 ), true );
303
-
304
- $store = (object) array(
305
- 'scan_path' => $scan_path,
306
- 'search' => wp_unslash( $_POST['search'] ),
307
- 'directory' => $_POST['directory'],
308
- 'chunks' => count( $file_chunks ),
309
- 'regex' => $_POST['regex']
310
- );
311
-
312
- $response = array(
313
- 'total' => count( $files ),
314
- 'current' => 0,
315
- 'directory' => $scan_path,
316
- 'chunks' => count( $file_chunks ),
317
- 'regex' => $_POST['regex']
318
- );
319
-
320
- update_option( 'string-locator-search-overview', serialize( $store ), true );
321
- update_option( 'string-locator-search-history', serialize( array() ) );
322
-
323
- foreach ( $file_chunks AS $count => $file_chunk ) {
324
- update_option( 'string-locator-search-files-' . $count, serialize( $file_chunk ) );
325
- }
326
-
327
- wp_send_json_success( $response );
328
- }
329
-
330
- /**
331
- * Check if the script is about to exceed the max execution time.
332
- *
333
- * @since 1.9.0
334
- *
335
- * @return bool
336
- */
337
- function nearing_execution_limit() {
338
- // Max execution time is 0 or -1 (infinite) in server config
339
- if ( 0 === $this->max_execution_time || - 1 === $this->max_execution_time ) {
340
- return false;
341
- }
342
-
343
- $built_in_delay = apply_filters( 'string-locator-extra-search-delay', 2 );
344
- $execution_time = ( microtime( true ) - $this->start_execution_timer + $built_in_delay );
345
-
346
- if ( $execution_time >= $this->max_execution_time ) {
347
- return $execution_time;
348
- }
349
-
350
- return false;
351
- }
352
-
353
- /**
354
- * Check if the script is about to exceed the server memory limit.
355
- *
356
- * @since 2.0.0
357
- *
358
- * @return bool
359
- */
360
- function nearing_memory_limit() {
361
- // Check if the memory limit is set t o0 or -1 (infinite) in server config
362
- if ( 0 === $this->max_memory_consumption || - 1 === $this->max_memory_consumption ) {
363
- return false;
364
- }
365
-
366
- // We give our selves a 256k memory buffer, as we need to close off the script properly as well
367
- $built_in_buffer = apply_filters( 'string-locator-extra-memory-buffer', 256000 );
368
- $memory_use = ( memory_get_usage( true ) + $built_in_buffer );
369
-
370
- if ( $memory_use >= $this->max_memory_consumption ) {
371
- return $memory_use;
372
- }
373
-
374
- return false;
375
- }
376
-
377
- public static function absbool( $value ) {
378
- if ( is_bool( $value ) ) {
379
- $bool = $value;
380
- } else {
381
- if ( 'false' == $value ) {
382
- $bool = false;
383
- } else {
384
- $bool = true;
385
- }
386
- }
387
-
388
- return $bool;
389
- }
390
-
391
- /**
392
- * Search an individual file supplied via AJAX.
393
- *
394
- * @since 1.9.0
395
- *
396
- * @return void
397
- */
398
- function ajax_file_search() {
399
- if ( ! check_ajax_referer( 'string-locator-search', 'nonce', false ) ) {
400
- wp_send_json_error( __( 'Authentication failed', 'string-locator' ) );
401
- }
402
-
403
- $files_per_chunk = apply_filters( 'string-locator-files-per-array', 500 );
404
- $response = array(
405
- 'search' => array(),
406
- 'filenum' => absint( $_POST['filenum'] )
407
- );
408
-
409
- $filenum = absint( $_POST['filenum'] );
410
- $next_file = $filenum + 1;
411
-
412
- $next_chunk = ( ceil( ( $next_file ) / $files_per_chunk ) - 1 );
413
- $chunk = ( ceil( $filenum / $files_per_chunk ) - 1 );
414
- if ( $chunk < 0 ) {
415
- $chunk = 0;
416
- }
417
- if ( $next_chunk < 0 ) {
418
- $next_chunk = 0;
419
- }
420
-
421
- $scan_data = unserialize( get_option( 'string-locator-search-overview' ) );
422
- $file_data = unserialize( get_option( 'string-locator-search-files-' . $chunk ) );
423
-
424
- if ( ! isset( $file_data[ $filenum ] ) ) {
425
- wp_send_json_error(
426
- array(
427
- 'continue' => false,
428
- 'message' => sprintf(
429
- /* translators: %d: The numbered reference to a file being searched. */
430
- esc_html__( 'The file-number, %d, that was sent could not be found.', 'string-locator' ),
431
- $filenum
432
- )
433
- )
434
- );
435
- }
436
-
437
- if ( $this->nearing_execution_limit() ) {
438
- wp_send_json_error(
439
- array(
440
- 'continue' => false,
441
- 'message' => sprintf(
442
- /* translators: %1$d: The time a PHP file can run, as defined by the server configuration. %2$d: The amount of time used by the PHP file so far. */
443
- esc_html__( 'The maximum time your server allows a script to run (%1$d) is too low for the plugin to run as intended, at startup %2$d seconds have passed', 'string-locator' ),
444
- $this->max_execution_time,
445
- $this->nearing_execution_limit()
446
- )
447
- )
448
- );
449
- }
450
- if ( $this->nearing_memory_limit() ) {
451
- wp_send_json_error(
452
- array(
453
- 'continue' => false,
454
- 'message' => sprintf(
455
- /* translators: %1$d: Current amount of used system memory resources. %2$d: The maximum available system memory. */
456
- esc_html__( 'The memory limit is about to be exceeded before the search has started, this could be an early indicator that your site may soon struggle as well, unfortunately this means the plugin is unable to perform any searches. Current memory consumption: %1$d of %2$d bytes', 'string-locator' ),
457
- $this->nearing_memory_limit(),
458
- $this->max_memory_consumption
459
- )
460
- )
461
- );
462
- }
463
-
464
- $is_regex = false;
465
- if ( isset( $scan_data->regex ) ) {
466
- $is_regex = $this->absbool( $scan_data->regex );
467
- }
468
-
469
- if ( $is_regex ) {
470
- if ( false === @preg_match( $scan_data->search, '' ) ) {
471
- wp_send_json_error(
472
- array(
473
- 'continue' => false,
474
- 'message' => sprintf(
475
- /* translators: %s: The search string used. */
476
- __( 'Your search string, <strong>%s</strong>, is not a valid pattern, and the search has been aborted.', 'string-locator' ),
477
- esc_html( $scan_data->search )
478
- )
479
- )
480
- );
481
- }
482
- }
483
-
484
- while ( ! $this->nearing_execution_limit() && ! $this->nearing_memory_limit() && isset( $file_data[ $filenum ] ) ) {
485
- $filenum = absint( $_POST['filenum'] );
486
- $search_results = null;
487
- $next_file = $filenum + 1;
488
-
489
- $next_chunk = ( ceil( ( $next_file ) / $files_per_chunk ) - 1 );
490
- $chunk = ( ceil( $filenum / $files_per_chunk ) - 1 );
491
- if ( $chunk < 0 ) {
492
- $chunk = 0;
493
- }
494
- if ( $next_chunk < 0 ) {
495
- $next_chunk = 0;
496
- }
497
-
498
- if ( ! isset( $file_data[ $filenum ] ) ) {
499
- $chunk ++;
500
- $file_data = unserialize( get_option( 'string-locator-search-files-' . $chunk ) );
501
- continue;
502
- }
503
-
504
- $file_name = explode( "/", $file_data[ $filenum ] );
505
- $file_name = end( $file_name );
506
-
507
- /*
508
- * Check the file type, if it's an unsupported type, we skip it
509
- */
510
- $file_type = explode( '.', $file_name );
511
- $file_type = strtolower( end( $file_type ) );
512
-
513
- /*
514
- * Scan the file and look for our string, but only if it's an approved file extension
515
- */
516
- $bad_file_types = apply_filters( 'string_locator_bad_file_types', $this->bad_file_types );
517
- if ( ! in_array( $file_type, $bad_file_types ) ) {
518
- $search_results = $this->scan_file( $file_data[ $filenum ], $scan_data->search, $file_data[ $filenum ], $scan_data->scan_path->type, '', $is_regex );
519
- }
520
-
521
- $response['last_file'] = $file_data[ $filenum ];
522
- $response['filenum'] = $filenum;
523
- $response['filename'] = $file_name;
524
- if ( $search_results ) {
525
- $response['search'][] = $search_results;
526
- }
527
-
528
- if ( $next_chunk != $chunk ) {
529
- $file_data = unserialize( get_option( 'string-locator-search-files-' . $next_chunk ) );
530
- }
531
-
532
- $response['next_file'] = ( isset( $file_data[ $next_file ] ) ? $file_data[ $next_file ] : '' );
533
-
534
- if ( ! empty( $search_results ) ) {
535
- $history = maybe_unserialize( get_option( 'string-locator-search-history', array() ) );
536
- $history = array_merge( $history, $search_results );
537
- update_option( 'string-locator-search-history', serialize( $history ), false );
538
- }
539
-
540
- $_POST['filenum'] ++;
541
- }
542
-
543
- wp_send_json_success( $response );
544
- }
545
-
546
- /**
547
- * Clean up our options used to help during the search.
548
- *
549
- * @return void
550
- */
551
- function ajax_clean_search() {
552
- if ( ! check_ajax_referer( 'string-locator-search', 'nonce', false ) ) {
553
- wp_send_json_error( __( 'Authentication failed', 'string-locator' ) );
554
- }
555
-
556
- $scan_data = unserialize( get_option( 'string-locator-search-overview' ) );
557
- for ( $i = 0; $i < $scan_data->chunks; $i ++ ) {
558
- delete_option( 'string-locator-search-files-' . $i );
559
- }
560
-
561
- wp_send_json_success( true );
562
- }
563
-
564
- /**
565
- * Create a table row for insertion into the search results list.
566
- *
567
- * @param array|object $item The table row item.
568
- *
569
- * @return string
570
- */
571
- public static function prepare_table_row( $item ) {
572
- if ( ! is_object( $item ) ) {
573
- $item = (object) $item;
574
- }
575
-
576
- return sprintf(
577
- '<tr>
578
- <td>%1$s<div class="row-actions"><span class="edit"><a href="%2$s" aria-label="Edit">Edit</a></span></div></td>
579
- <td><a href="%2$s">%3$s</a></td>
580
- <td>%4$d</td>
581
- </tr>',
582
- $item->stringresult,
583
- esc_url( $item->editurl ),
584
- esc_html( $item->filename_raw ),
585
- esc_html( $item->linenum )
586
- );
587
- }
588
-
589
- /**
590
- * Create a full table populated with the supplied items.
591
- *
592
- * @param array $items An array of table rows.
593
- * @param array $table_class An array of items to append to the table class along with the defaults.
594
- *
595
- * @return string
596
- */
597
- public static function prepare_full_table( $items, $table_class = array() ) {
598
- $table_class = array_merge( $table_class, array(
599
- 'wp-list-table',
600
- 'widefat',
601
- 'fixed',
602
- 'striped',
603
- 'tools_page_string-locator'
604
- ) );
605
-
606
- $table_columns = sprintf(
607
- '<tr>
608
- <th scope="col" class="manage-column column-stringresult column-primary">%s</th>
609
- <th scope="col" class="manage-column column-filename">%s</th>
610
- <th scope="col" class="manage-column column-linenum">%s</th>
611
- </tr>',
612
- esc_html( __( 'String', 'string-locator' ) ),
613
- esc_html( __( 'File', 'string-locator' ) ),
614
- esc_html( __( 'Line number', 'string-locator' ) )
615
- );
616
-
617
- $table_rows = array();
618
- foreach ( $items AS $item ) {
619
- $table_rows[] = self::prepare_table_row( $item );
620
- }
621
-
622
- $table = sprintf(
623
- '<div class="tablenav top"><br class="clear"></div><table class="%s"><thead>%s</thead><tbody>%s</tbody><tfoot>%s</tfoot></table>',
624
- implode( ' ', $table_class ),
625
- $table_columns,
626
- implode( "\n", $table_rows ),
627
- $table_columns
628
- );
629
-
630
- return $table;
631
- }
632
-
633
- /**
634
- * Create an admin edit link for the supplied path.
635
- *
636
- * @param string $path Path to the file we'er adding a link for.
637
- * @param int $line The line in the file where our search result was found.
638
- *
639
- * @return string
640
- */
641
- function create_edit_link( $path, $line = 0 ) {
642
- $file_type = 'core';
643
- $file_slug = '';
644
- $content_path = str_replace( '\\', '/', WP_CONTENT_DIR );
645
-
646
- $path = str_replace( '\\', '/', $path );
647
- $paths = explode( '/', $path );
648
-
649
- $url_args = array(
650
- 'page=string-locator',
651
- 'edit-file=' . end( $paths )
652
- );
653
-
654
- switch ( true ) {
655
- case ( in_array( 'wp-content', $paths ) && in_array( 'plugins', $paths ) ) :
656
- $file_type = 'plugin';
657
- $content_path .= '/plugins/';
658
- break;
659
- case ( in_array( 'wp-content', $paths ) && in_array( 'themes', $paths ) ) :
660
- $file_type = 'theme';
661
- $content_path .= '/themes/';
662
- break;
663
- }
664
-
665
- $rel_path = str_replace( $content_path, '', $path );
666
- $rel_paths = explode( '/', $rel_path );
667
-
668
- if ( 'core' != $file_type ) {
669
- $file_slug = $rel_paths[0];
670
- }
671
-
672
- $url_args[] = 'file-reference=' . $file_slug;
673
- $url_args[] = 'file-type=' . $file_type;
674
- $url_args[] = 'string-locator-line=' . absint( $line );
675
- $url_args[] = 'string-locator-path=' . urlencode( str_replace( '/', DIRECTORY_SEPARATOR, $path ) );
676
-
677
- $url = admin_url( $this->path_to_use . '?' . implode( '&', $url_args ) );
678
-
679
- return $url;
680
- }
681
-
682
- /**
683
- * Parse the search option to determine what kind of search we are performing and what directory to start in.
684
- *
685
- * @param string $option The search-type identifier.
686
- *
687
- * @return bool|object
688
- */
689
- function prepare_scan_path( $option ) {
690
- $data = array(
691
- 'path' => '',
692
- 'type' => '',
693
- 'slug' => ''
694
- );
695
-
696
- switch ( true ) {
697
- case ( 't--' == $option ):
698
- $data['path'] = WP_CONTENT_DIR . '/themes/';
699
- $data['type'] = 'theme';
700
- break;
701
- case ( strlen( $option ) > 3 && 't-' == substr( $option, 0, 2 ) ):
702
- $data['path'] = WP_CONTENT_DIR . '/themes/' . substr( $option, 2 );
703
- $data['type'] = 'theme';
704
- $data['slug'] = substr( $option, 2 );
705
- break;
706
- case ( 'p--' == $option ):
707
- $data['path'] = WP_CONTENT_DIR . '/plugins/';
708
- $data['type'] = 'plugin';
709
- break;
710
- case ( 'mup--' == $option ):
711
- $data['path'] = WP_CONTENT_DIR . '/mu-plugins/';
712
- $data['type'] = 'mu-plugin';
713
- break;
714
- case ( strlen( $option ) > 3 && 'p-' == substr( $option, 0, 2 ) ):
715
- $slug = explode( '/', substr( $option, 2 ) );
716
-
717
- $data['path'] = WP_CONTENT_DIR . '/plugins/' . $slug[0];
718
- $data['type'] = 'plugin';
719
- $data['slug'] = $slug[0];
720
- break;
721
- case ( 'core' == $option ):
722
- $data['path'] = ABSPATH;
723
- $data['type'] = 'core';
724
- break;
725
- case ( 'wp-content' == $option ):
726
- $data['path'] = WP_CONTENT_DIR;
727
- $data['type'] = 'core';
728
- break;
729
- }
730
-
731
- if ( empty( $data['path'] ) ) {
732
- return false;
733
- }
734
-
735
- return (object) $data;
736
- }
737
-
738
- /**
739
- * Check if a file path is valid for editing.
740
- *
741
- * @param string $path Path to file.
742
- *
743
- * @return bool
744
- */
745
- function is_valid_location( $path ) {
746
- $valid = true;
747
- $path = str_replace( array( '/' ), array( DIRECTORY_SEPARATOR ), stripslashes( $path ) );
748
- $abspath = str_replace( array( '/' ), array( DIRECTORY_SEPARATOR ), ABSPATH );
749
-
750
- // Check that it is a valid file we are trying to access as well.
751
- if ( ! file_exists( $path ) ) {
752
- $valid = false;
753
- }
754
-
755
- if ( empty( $path ) ) {
756
- $valid = false;
757
- }
758
- if ( stristr( $path, '..' ) ) {
759
- $valid = false;
760
- }
761
- if ( ! stristr( $path, $abspath ) ) {
762
- $valid = false;
763
- }
764
-
765
- return $valid;
766
- }
767
-
768
- /**
769
- * Set the text domain for translated plugin content.
770
- *
771
- * @return void
772
- */
773
- function load_i18n() {
774
- $i18n_dir = 'string-locator/languages/';
775
- load_plugin_textdomain( 'string-locator', false, $i18n_dir );
776
- }
777
-
778
- /**
779
- * Load up JavaScript and CSS for our plugin on the appropriate admin pages.
780
- *
781
- * @return void
782
- */
783
- function admin_enqueue_scripts( $hook ) {
784
- // Break out early if we are not on a String Locator page
785
- if ( 'tools_page_string-locator' != $hook && 'toplevel_page_string-locator' != $hook ) {
786
- return;
787
- }
788
-
789
- /**
790
- * String Locator Styles
791
- */
792
- wp_enqueue_style( 'string-locator', plugin_dir_url( __FILE__ ) . '/resources/css/string-locator.css', array(), $this->version );
793
-
794
- if ( ! isset( $_GET['edit-file'] ) ) {
795
- /**
796
- * String Locator Scripts
797
- */
798
- wp_enqueue_script( 'string-locator-search', plugin_dir_url( __FILE__ ) . '/resources/js/string-locator-search.js', array( 'jquery' ), $this->version );
799
-
800
- wp_localize_script( 'string-locator-search', 'string_locator', array(
801
- 'ajax_url' => admin_url( 'admin-ajax.php' ),
802
- 'search_nonce' => wp_create_nonce( 'string-locator-search' ),
803
- 'search_current_prefix' => __( 'Next file: ', 'string-locator' ),
804
- 'saving_results_string' => __( 'Saving search results&hellip;', 'string-locator' ),
805
- 'search_preparing' => __( 'Preparing search&hellip;', 'string-locator' ),
806
- 'search_started' => __( 'Preparations completed, search started&hellip;', 'string-locator' ),
807
- 'search_error' => __( 'The above error was returned by your server, for more details please consult your servers error logs.', 'string-locator' ),
808
- 'search_no_results' => __( 'Your search was completed, but no results were found..', 'string-locator' ),
809
- 'warning_title' => __( 'Warning', 'string-locator' )
810
- ) );
811
-
812
- }
813
- else {
814
- $code_mirror = wp_enqueue_code_editor( array(
815
- 'file' => $_GET['edit-file']
816
- ) );
817
-
818
- /**
819
- * String Locator Scripts
820
- */
821
- wp_enqueue_script( 'string-locator-editor', $this->plugin_url . '/resources/js/string-locator.js', array( 'jquery', 'code-editor' ), $this->version, true );
822
-
823
- wp_localize_script( 'string-locator-editor', 'string_locator', array(
824
- 'CodeMirror' => $code_mirror,
825
- 'goto_line' => absint( $_GET['string-locator-line'] )
826
- ) );
827
- }
828
- }
829
-
830
- /**
831
- * Add our plugin to the 'Tools' menu.
832
- *
833
- * @return void
834
- */
835
- function populate_menu() {
836
- if ( is_multisite() ) {
837
- return;
838
- }
839
- $page_title = __( 'String Locator', 'string-locator' );
840
- $menu_title = __( 'String Locator', 'string-locator' );
841
- $capability = 'edit_themes';
842
- $parent_slug = 'tools.php';
843
- $menu_slug = 'string-locator';
844
- $function = array( $this, 'options_page' );
845
-
846
- add_submenu_page( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $function );
847
- }
848
-
849
- /**
850
- * Add our plugin to the main menu in the Network Admin.
851
- *
852
- * @return void
853
- */
854
- function populate_network_menu() {
855
- $page_title = __( 'String Locator', 'string-locator' );
856
- $menu_title = __( 'String Locator', 'string-locator' );
857
- $capability = 'edit_themes';
858
- $menu_slug = 'string-locator';
859
- $function = array( $this, 'options_page' );
860
-
861
- add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $function, 'dashicons-edit' );
862
- }
863
-
864
- /**
865
- * Function for including the actual plugin Admin UI page.
866
- *
867
- * @return mixed
868
- */
869
- function options_page() {
870
- /**
871
- * Don't load anything if the user can't edit themes any way
872
- */
873
- if ( ! current_user_can( 'edit_themes' ) ) {
874
- return false;
875
- }
876
-
877
- /**
878
- * Show the edit page if;
879
- * - The edit file path query var is set
880
- * - The edit file path query var isn't empty
881
- * - The edit file path query var does not contains double dots (used to traverse directories)
882
- */
883
- if ( isset( $_GET['string-locator-path'] ) && $this->is_valid_location( $_GET['string-locator-path'] ) ) {
884
- include_once( dirname( __FILE__ ) . '/editor.php' );
885
- } else {
886
- include_once( dirname( __FILE__ ) . '/options.php' );
887
- }
888
- }
889
-
890
- /**
891
- * Check for inconsistencies in brackets and similar.
892
- *
893
- * @param string $start Start delimited.
894
- * @param string $end End delimiter.
895
- * @param string $string The string to scan.
896
- *
897
- * @return array
898
- */
899
- function SmartScan( $start, $end, $string ) {
900
- $opened = array();
901
-
902
- $lines = explode( "\n", $string );
903
- for ( $i = 0; $i < count( $lines ); $i ++ ) {
904
- if ( stristr( $lines[ $i ], $start ) ) {
905
- $opened[] = $i;
906
- }
907
- if ( stristr( $lines[ $i ], $end ) ) {
908
- array_pop( $opened );
909
- }
910
- }
911
-
912
- return $opened;
913
- }
914
-
915
- /**
916
- * Handler for storing the content of the code editor.
917
- *
918
- * Also runs over the Smart-Scan if enabled.
919
- *
920
- * @return void
921
- */
922
- function editor_save() {
923
- if ( isset( $_POST['string-locator-editor-content'] ) && check_admin_referer( 'string-locator-edit_' . $_GET['edit-file'] ) && current_user_can( 'edit_themes' ) ) {
924
-
925
- if ( $this->is_valid_location( $_GET['string-locator-path'] ) ) {
926
- $path = urldecode( $_GET['string-locator-path'] );
927
- $content = stripslashes( $_POST['string-locator-editor-content'] );
928
-
929
- /**
930
- * Send an error notice if the file isn't writable
931
- */
932
- if ( ! is_writeable( $path ) ) {
933
- $this->notice[] = array(
934
- 'type' => 'error',
935
- 'message' => __( 'The file could not be written to, please check file permissions or edit it manually.', 'string-locator' )
936
- );
937
- $this->failed_edit = true;
938
-
939
- return;
940
- }
941
-
942
- /**
943
- * If enabled, run the Smart-Scan on the content before saving it
944
- */
945
- if ( isset( $_POST['string-locator-smart-edit'] ) ) {
946
- $open_brace = substr_count( $content, '{' );
947
- $close_brace = substr_count( $content, '}' );
948
- if ( $open_brace != $close_brace ) {
949
- $this->failed_edit = true;
950
-
951
- $opened = $this->SmartScan( '{', '}', $content );
952
-
953
- foreach ( $opened AS $line ) {
954
- $this->notice[] = array(
955
- 'type' => 'error',
956
- 'message' => sprintf(
957
- esc_html__( 'There is an inconsistency in the opening and closing braces, { and }, of your file on line %s', 'string-locator' ),
958
- '<a href="#" class="string-locator-edit-goto" data-goto-line="' . ( $line + 1 ) . '">' . ( $line + 1 ) . '</a>'
959
- )
960
- );
961
- }
962
- }
963
-
964
- $open_bracket = substr_count( $content, '[' );
965
- $close_bracket = substr_count( $content, ']' );
966
- if ( $open_bracket != $close_bracket ) {
967
- $this->failed_edit = true;
968
-
969
- $opened = $this->SmartScan( '[', ']', $content );
970
-
971
- foreach ( $opened AS $line ) {
972
- $this->notice[] = array(
973
- 'type' => 'error',
974
- 'message' => sprintf(
975
- esc_html__( 'There is an inconsistency in the opening and closing braces, [ and ], of your file on line %s', 'string-locator' ),
976
- '<a href="#" class="string-locator-edit-goto" data-goto-line="' . ( $line + 1 ) . '">' . ( $line + 1 ) . '</a>'
977
- )
978
- );
979
- }
980
- }
981
-
982
- $open_parenthesis = substr_count( $content, '(' );
983
- $close_parenthesis = substr_count( $content, ')' );
984
- if ( $open_parenthesis != $close_parenthesis ) {
985
- $this->failed_edit = true;
986
-
987
- $opened = $this->SmartScan( '(', ')', $content );
988
-
989
- foreach ( $opened AS $line ) {
990
- $this->notice[] = array(
991
- 'type' => 'error',
992
- 'message' => sprintf(
993
- esc_html__( 'There is an inconsistency in the opening and closing braces, ( and ), of your file on line %s', 'string-locator' ),
994
- '<a href="#" class="string-locator-edit-goto" data-goto-line="' . ( $line + 1 ) . '">' . ( $line + 1 ) . '</a>'
995
- )
996
- );
997
- }
998
- }
999
-
1000
- if ( $this->failed_edit ) {
1001
- return;
1002
- }
1003
- }
1004
-
1005
- $original = file_get_contents( $path );
1006
-
1007
- $this->write_file( $path, $content );
1008
-
1009
- /**
1010
- * Check the status of the site after making our edits.
1011
- * If the site fails, revert the changes to return the sites to its original state
1012
- */
1013
- $header = wp_remote_head( site_url() );
1014
- if ( 301 == $header['response']['code'] ) {
1015
- $header = wp_remote_head( $header['headers']['location'] );
1016
- }
1017
-
1018
- $bad_http_check = apply_filters( 'string_locator_bad_http_codes', $this->bad_http_codes );
1019
-
1020
- if ( in_array( $header['response']['code'], $bad_http_check ) ) {
1021
- $this->failed_edit = true;
1022
- $this->write_file( $path, $original );
1023
-
1024
- $this->notice[] = array(
1025
- 'type' => 'error',
1026
- 'message' => esc_html__( 'A 500 server error was detected on your site after updating your file. We have restored the previous version of the file for you.', 'string-locator' )
1027
- );
1028
- } else {
1029
- $this->notice[] = array(
1030
- 'type' => 'updated',
1031
- 'message' => esc_html__( 'The file has been saved', 'string-locator' )
1032
- );
1033
- }
1034
- }
1035
- }
1036
- }
1037
-
1038
- /**
1039
- * When editing a file, this is where we write all the new content.
1040
- * We will break early if the user isn't allowed to edit files.
1041
- *
1042
- * @param string $path The path to the file.
1043
- * @param string $content The content to write to the file.
1044
- *
1045
- * @return void
1046
- */
1047
- private function write_file( $path, $content ) {
1048
- if ( ! current_user_can( 'edit_themes' ) ) {
1049
- return;
1050
- }
1051
-
1052
- // Verify the location is valid before we try using it.
1053
- if ( ! $this->is_valid_location( $path ) ) {
1054
- return;
1055
- }
1056
-
1057
- if ( apply_filters( 'string-locator-filter-closing-php-tags', true ) ) {
1058
- $content = preg_replace( "/\?>$/si", '', trim( $content ), - 1, $replaced_strings );
1059
-
1060
- if ( $replaced_strings >= 1 ) {
1061
- $this->notice[] = array(
1062
- 'type' => 'error',
1063
- 'message' => __( 'We detected a PHP code tag ending, this has been automatically stripped out to help prevent errors in your code.', 'string-locator' )
1064
- );
1065
- }
1066
- }
1067
-
1068
- $file = fopen( $path, "w" );
1069
- $lines = explode( "\n", str_replace( array( "\r\n", "\r" ), "\n", $content ) );
1070
- $total_lines = count( $lines );
1071
-
1072
- for ( $i = 0; $i < $total_lines; $i ++ ) {
1073
- $write_line = $lines[ $i ];
1074
-
1075
- if ( ( $i + 1 ) < $total_lines ) {
1076
- $write_line .= PHP_EOL;
1077
- }
1078
-
1079
- fwrite( $file, $write_line );
1080
- }
1081
-
1082
- fclose( $file );
1083
- }
1084
-
1085
- /**
1086
- * Hook the admin notices and loop over any notices we've registered in the plugin.
1087
- *
1088
- * @return void
1089
- */
1090
- function admin_notice() {
1091
- if ( ! empty( $this->notice ) ) {
1092
- foreach ( $this->notice AS $note ) {
1093
- printf(
1094
- '<div class="%s"><p>%s</p></div>',
1095
- esc_attr( $note['type'] ),
1096
- $note['message']
1097
- );
1098
- }
1099
- }
1100
- }
1101
-
1102
- /**
1103
- * Scan through an individual file to look for occurrences of £string.
1104
- *
1105
- * @param string $filename The path to the file.
1106
- * @param string $string The search string.
1107
- * @param mixed $location The file location object/string.
1108
- * @param string $type File type.
1109
- * @param string $slug The plugin/theme slug of the file.
1110
- * @param boolean $regex Should a regex search be performed.
1111
- *
1112
- * @return mixed
1113
- */
1114
- function scan_file( $filename, $string, $location, $type, $slug, $regex = false ) {
1115
- if ( empty( $string ) || ! is_file( $filename ) ) {
1116
- return false;
1117
- }
1118
- $output = array();
1119
- $linenum = 0;
1120
- $match_count = 0;
1121
-
1122
- if ( ! is_object( $location ) ) {
1123
- $path = $location;
1124
- $location = explode( DIRECTORY_SEPARATOR, $location );
1125
- $file = end( $location );
1126
- } else {
1127
- $path = $location->getPathname();
1128
- $file = $location->getFilename();
1129
- }
1130
-
1131
- /*
1132
- * Check if the filename matches our search pattern
1133
- */
1134
- if ( stristr( $file, $string ) || ( $regex && preg_match( $string, $file ) ) ) {
1135
- $relativepath = str_replace( array( ABSPATH, '\\', '/' ), array(
1136
- '',
1137
- DIRECTORY_SEPARATOR,
1138
- DIRECTORY_SEPARATOR
1139
- ), $path );
1140
- $match_count ++;
1141
-
1142
- $editurl = $this->create_edit_link( $path, $linenum );
1143
-
1144
- $path_string = sprintf(
1145
- '<a href="%s">%s</a>',
1146
- esc_url( $editurl ),
1147
- esc_html( $relativepath )
1148
- );
1149
-
1150
- $output[] = array(
1151
- 'ID' => $match_count,
1152
- 'linenum' => sprintf(
1153
- '[%s]',
1154
- esc_html__( 'Filename matches search', 'string-locator' )
1155
- ),
1156
- 'path' => $path,
1157
- 'filename' => $path_string,
1158
- 'filename_raw' => $relativepath,
1159
- 'editurl' => $editurl,
1160
- 'stringresult' => $file
1161
- );
1162
- }
1163
-
1164
- $readfile = @fopen( $filename, "r" );
1165
- if ( $readfile ) {
1166
- while ( ( $readline = fgets( $readfile ) ) !== false ) {
1167
- $string_preview_is_cut = false;
1168
- $linenum ++;
1169
- /**
1170
- * If our string is found in this line, output the line number and other data
1171
- */
1172
- if ( ( ! $regex && stristr( $readline, $string ) ) || ( $regex && preg_match( $string, $readline ) ) ) {
1173
- /**
1174
- * Prepare the visual path for the end user
1175
- * Removes path leading up to WordPress root and ensures consistent directory separators
1176
- */
1177
- $relativepath = str_replace( array( ABSPATH, '\\', '/' ), array(
1178
- '',
1179
- DIRECTORY_SEPARATOR,
1180
- DIRECTORY_SEPARATOR
1181
- ), $path );
1182
- $match_count ++;
1183
-
1184
- /**
1185
- * Create the URL to take the user to the editor
1186
- */
1187
- $editurl = $this->create_edit_link( $path, $linenum );
1188
-
1189
- $string_preview = $readline;
1190
- if ( strlen( $string_preview ) > ( strlen( $string ) + $this->excerpt_length ) ) {
1191
- $string_location = strpos( $string_preview, $string );
1192
-
1193
- $string_location_start = $string_location - $this->excerpt_length;
1194
- if ( $string_location_start < 0 ) {
1195
- $string_location_start = 0;
1196
- }
1197
-
1198
- $string_location_end = ( strlen( $string ) + ( $this->excerpt_length * 2 ) );
1199
- if ( $string_location_end > strlen( $string_preview ) ) {
1200
- $string_location_end = strlen( $string_preview );
1201
- }
1202
-
1203
- $string_preview = substr( $string_preview, $string_location_start, $string_location_end );
1204
- $string_preview_is_cut = true;
1205
- }
1206
-
1207
- if ( $regex ) {
1208
- $string_preview = preg_replace( preg_replace( '/\/(.+)\//', '/($1)/', $string ), '<strong>$1</strong>', esc_html( $string_preview ) );
1209
- } else {
1210
- $string_preview = str_ireplace( $string, '<strong>' . $string . '</strong>', esc_html( $string_preview ) );
1211
- }
1212
- if ( $string_preview_is_cut ) {
1213
- $string_preview = sprintf(
1214
- '&hellip;%s&hellip;',
1215
- $string_preview
1216
- );
1217
- }
1218
-
1219
- $path_string = sprintf(
1220
- '<a href="%s">%s</a>',
1221
- esc_url( $editurl ),
1222
- esc_html( $relativepath )
1223
- );
1224
-
1225
- $output[] = array(
1226
- 'ID' => $match_count,
1227
- 'linenum' => $linenum,
1228
- 'path' => $path,
1229
- 'filename' => $path_string,
1230
- 'filename_raw' => $relativepath,
1231
- 'editurl' => $editurl,
1232
- 'stringresult' => $string_preview
1233
- );
1234
- }
1235
- }
1236
-
1237
- fclose( $readfile );
1238
- } else {
1239
- /**
1240
- * The file was unreadable, give the user a friendly notification
1241
- */
1242
- $output[] = array(
1243
- 'linenum' => '#',
1244
- 'filename' => esc_html( sprintf( __( 'Could not read file: %s', 'string-locator' ), $filename ) ),
1245
- 'stringresult' => ''
1246
- );
1247
- }
1248
-
1249
- return $output;
1250
- }
1251
-
1252
- function ajax_scan_path( $path ) {
1253
- $files = array();
1254
-
1255
- $paths = new RecursiveIteratorIterator(
1256
- new RecursiveDirectoryIterator( $path ),
1257
- RecursiveIteratorIterator::SELF_FIRST
1258
- );
1259
-
1260
- foreach ( $paths AS $name => $location ) {
1261
- if ( is_dir( $location->getPathname() ) ) {
1262
- continue;
1263
- }
1264
-
1265
- $files[] = $location->getPathname();
1266
- }
1267
-
1268
- return $files;
1269
- }
1270
- }
1271
-
1272
- /**
1273
- * Instantiate the plugin
1274
- */
1275
- $string_locator = new string_locator();
1
+ <?php
2
+ /**
3
+ * Plugin Name: String Locator
4
+ * Plugin URI: http://www.clorith.net/wordpress-string-locator/
5
+ * Description: Scan through theme and plugin files looking for text strings
6
+ * Version: 2.4.0
7
+ * Author: Clorith
8
+ * Author URI: http://www.clorith.net
9
+ * Text Domain: string-locator
10
+ * License: GPL2
11
+ *
12
+ * Copyright 2013 Marius Jensen (email : marius@clorith.net)
13
+ *
14
+ * This program is free software; you can redistribute it and/or modify
15
+ * it under the terms of the GNU General Public License, version 2, as
16
+ * published by the Free Software Foundation.
17
+ *
18
+ * This program is distributed in the hope that it will be useful,
19
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
+ * GNU General Public License for more details.
22
+ *
23
+ * You should have received a copy of the GNU General Public License
24
+ * along with this program; if not, write to the Free Software
25
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26
+ */
27
+
28
+ if ( ! defined( 'ABSPATH' ) ) {
29
+ die();
30
+ }
31
+
32
+ define( 'STRING_LOCATOR_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
33
+ define( 'STRING_LOCATOR_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
34
+
35
+ require_once( __DIR__ . '/includes/class-string-locator.php' );
36
+
37
+ /**
38
+ * Instantiate the plugin
39
+ */
40
+ $string_locator = new string_locator();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
uninstall.php CHANGED
@@ -1,11 +1,11 @@
1
- <?php
2
- //if uninstall not called from WordPress exit
3
- if ( !defined( 'WP_UNINSTALL_PLUGIN' ) ) {
4
- exit();
5
- }
6
-
7
- global $wpdb;
8
- $options = $wpdb->get_results( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE 'string-locator-%'" );
9
- foreach( $options AS $option ) {
10
- delete_option( $option->option_name );
11
- }
1
+ <?php
2
+ //if uninstall not called from WordPress exit
3
+ if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
4
+ exit();
5
+ }
6
+
7
+ global $wpdb;
8
+ $options = $wpdb->get_results( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '%string-locator%'" );
9
+ foreach ( $options as $option ) {
10
+ delete_option( $option->option_name );
11
+ }