String locator - Version 2.5.0

Version Description

(2022-02-27) = * Fixed a bug where content would have slashes stripped unexpectedly. * Improved table spacing on search results. * Improved loopback checks to also check admin access. * Hardened the search iterator so users can't accidentally perform unexpected directory traversal. * Introduced actions and filters in various places to enable extenders, and future enhancements. * Moved all ajax requests to dedicated REST endpoints. * Refactored file structure.

Download this release

Release Info

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

Code changes from version 2.4.2 to 2.5.0

build/string-locator-search.asset.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php return array('dependencies' => array(), 'version' => 'c872ff59b6ee90fa0f7b500730f6afd8');
build/string-locator-search.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery(document).ready((function(t){let r=!1;const o=wp.template("string-locator-search-result");function s(r,o,s){t(".notices").append('<div class="notice notice-'+s+' is-dismissible"><p><strong>'+r+"</strong><br />"+o+"</p></div>")}function a(o,a){r=!1,t(".string-locator-feedback").hide(),s(o,a,"error")}function e(n,c){if(c>=n||!r)return t("#string-locator-feedback-text").html(string_locator.saving_results_string),r=!1,t("#string-locator-feedback-text").text(""),t.post(string_locator.url.clean,{_wpnonce:string_locator.rest_nonce},(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){a(t.status+" "+o,string_locator.search_error)})),!1;const i={filenum:c,_wpnonce:string_locator.rest_nonce};t.post(string_locator.url.search,i,(function(r){if(!r.success){if(!1===r.data.continue)return a(string_locator.warning_title,r.data.message),!1;s(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){if(t(".no-items",".tools_page_string-locator").is(":visible")&&t(".no-items",".tools_page_string-locator").hide(),Array!==r.constructor)return!1;r.forEach((function(r){if(r)for(let s=0,a=r.length;s<a;s++){const a=r[s];void 0!==a.stringresult&&t("tbody",".tools_page_string-locator").append(o(a))}}))}(r.data.search));const c=r.data.filenum+1;e(n,c)}),"json").fail((function(t,r,o){a(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("");const n={directory:t("#string-locator-search").val(),search:t("#string-locator-string").val(),regex:t("#string-locator-regex").is(":checked"),_wpnonce:string_locator.rest_nonce};t("table.tools_page_string-locator").show(),t.post(string_locator.url.directory_structure,n,(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),e(r.data.total,0)):s(r.data,"alert")}),"json").fail((function(t,r,o){a(t.status+" "+o,string_locator.search_error)}))}))}));
build/string-locator.asset.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php return array('dependencies' => array(), 'version' => '1d5bc3a2fc5a7730308a2b7bc3219295');
build/string-locator.css ADDED
@@ -0,0 +1 @@
 
1
+ .wrap>h1{margin-bottom:15px}.string-locator-italics{font-style:italic}.string-locator-feedback{background:#fff;display:inline-block;text-align:center;width:100%}.string-locator-feedback.hide{display:none}.string-locator-feedback progress{height:1.5em;width:100%}.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}table .string{width:60%}table .filename{width:20%}table .line,table .position{width:10%}body.wp-admin.tools_page_string-locator #wpbody-content>.notice{display:none}.string-locator-editor-wrapper{grid-gap:0;-ms-grid-columns:80% 20%;display:-ms-grid;display:grid;grid-template-columns:80% 20%;height:100%;width:100%}.string-locator-editor-wrapper .notice,.string-locator-editor-wrapper .string-locator-header{align-items:stretch;background:#fff;border-bottom:1px solid #e2e4e7;display:flex;flex-direction:row;height:40px;justify-content:space-between;left:0;padding:4px 2px;position:-webkit-sticky;position:sticky;right:0;top:0;z-index:30}@media(min-width:600px){.string-locator-editor-wrapper .notice,.string-locator-editor-wrapper .string-locator-header{padding:8px;position:fixed;top:46px}}@media(min-width:782px){.string-locator-editor-wrapper .notice,.string-locator-editor-wrapper .string-locator-header{left:160px;top:32px}}.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{align-items:center;display:inline-flex}.string-locator-editor-wrapper .notice .button,.string-locator-editor-wrapper .string-locator-header .button{margin:0 3px 0 12px}.string-locator-editor-wrapper .notice{display:block;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;margin:0;top:89px}.string-locator-editor-wrapper .notice.is-dismissible{position:-webkit-sticky;position:sticky}.string-locator-editor-wrapper .string-locator-editor{margin-top:57px}.string-locator-editor-wrapper .string-locator-sidebar{background:#fff;border-left:1px solid #e2e4e7;margin-top:57px}.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{border:none;box-shadow:none;color:#191e23;font-weight:600;margin:0;padding:15px}.string-locator-editor-wrapper .string-locator-sidebar .string-locator-panel .row{padding:5px 15px}.string-locator-editor-wrapper .CodeMirror .CodeMirror-activeline .CodeMirror-activeline-background,.string-locator-editor-wrapper .CodeMirror .CodeMirror-activeline .CodeMirror-gutter-background{background-color:#cfe4ff}
build/string-locator.js ADDED
@@ -0,0 +1 @@
 
1
+ !function(){"use strict";jQuery(document).ready((function(t){let o;if(!1!==string_locator.CodeMirror&&""!==string_locator.CodeMirror){o=wp.codeEditor.initialize("code-editor",string_locator.CodeMirror);const e=wp.template("string-locator-alert");function r(t){const o=Math.max(document.documentElement.clientHeight,window.innerHeight||0)-89;t.setSize(null,parseInt(o))}t(".string-locator-editor").on("click",".string-locator-edit-goto",(function(e){e.preventDefault(),o.codemirror.scrollIntoView(parseInt(t(this).data("goto-line"))),o.codemirror.setCursor(parseInt(t(this).data("goto-line")-1),t(this).data("goto-linepos"))})),t("body").on("submit","#string-locator-edit-form",(function(o){const r=t("#string-locator-notices");return t.post(string_locator.url.save,t(this).serialize()).always((function(o){void 0===o.notices?r.append(e({type:"error",message:o.responseText})):t.each(o.notices,(function(){r.append(e(this))}))})),o.preventDefault(),!1})),r(o.codemirror),o.codemirror.scrollIntoView(parseInt(string_locator.goto_line)),o.codemirror.setCursor(parseInt(string_locator.goto_line-1),parseInt(string_locator.goto_linepos)),window.addEventListener("resize",r(o.codemirror))}else o=t("#code-editor"),o.css("width",t(".string-locator-edit-wrap").width()),o.css("height",parseInt(Math.max(document.documentElement.clientHeight,window.innerHeight||0)-89));t("#string-locator-notices").on("click",".notice-dismiss",(function(o){return t(this).closest(".notice").slideUp(400,"swing",(function(){t(this).remove()})),o.preventDefault(),!1}))}))}();
changelog.txt CHANGED
@@ -1,3 +1,8 @@
 
 
 
 
 
1
  = 2.4.1 =
2
  * Fixed case-sensitive class call, apparently not all PHP versions are equal in how this is treated.
3
 
1
+ = 2.4.2 =
2
+ * Fixed the option to restore previous search.
3
+ * Fixed respecting text capitalization in previews when doing a non-regex search.
4
+ * Changed capability checks, now works on hosts that maintain updates for their users.
5
+
6
  = 2.4.1 =
7
  * Fixed case-sensitive class call, apparently not all PHP versions are equal in how this is treated.
8
 
includes/REST/class-base.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace JITS\StringLocator\REST;
4
+
5
+ class Base extends \WP_REST_Controller {
6
+
7
+ protected $namespace = 'string-locator/v1';
8
+
9
+ public function __construct() {
10
+ add_action( 'rest_api_init', array( $this, 'register_rest_route' ) );
11
+ }
12
+
13
+ public function permission_callback() {
14
+ return current_user_can( 'edit_themes' );
15
+ }
16
+
17
+ }
includes/REST/class-clean.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace JITS\StringLocator\REST;
4
+
5
+ class Clean extends Base {
6
+
7
+ protected $rest_base = 'clean';
8
+
9
+ public function __construct() {
10
+ parent::__construct();
11
+ }
12
+
13
+ public function register_rest_route() {
14
+ register_rest_route(
15
+ $this->namespace,
16
+ $this->rest_base,
17
+ array(
18
+ 'methods' => 'POST',
19
+ 'callback' => array( $this, 'clean' ),
20
+ 'permission_callback' => array( $this, 'permission_callback' ),
21
+ )
22
+ );
23
+ }
24
+
25
+ public function clean() {
26
+ $scan_data = get_transient( 'string-locator-search-overview' );
27
+ for ( $i = 0; $i < $scan_data->chunks; $i ++ ) {
28
+ delete_transient( 'string-locator-search-files-' . $i );
29
+ }
30
+
31
+ wp_send_json_success( true );
32
+ }
33
+
34
+ }
35
+
36
+ new Clean();
includes/REST/class-directory-structure.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace JITS\StringLocator\REST;
4
+
5
+ use JITS\StringLocator\Directory_Iterator;
6
+
7
+ class Directory_Structure extends Base {
8
+
9
+ protected $rest_base = 'get-directory-structure';
10
+
11
+ public function __construct() {
12
+ parent::__construct();
13
+ }
14
+
15
+ public function register_rest_route() {
16
+ register_rest_route(
17
+ $this->namespace,
18
+ $this->rest_base,
19
+ array(
20
+ 'methods' => 'POST',
21
+ 'callback' => array( $this, 'get_structure' ),
22
+ 'permission_callback' => array( $this, 'permission_callback' ),
23
+ )
24
+ );
25
+ }
26
+
27
+ public function get_structure( \WP_REST_Request $request ) {
28
+ // Validate the search path to avoid unintended directory traversal.
29
+ if ( 0 !== validate_file( $request->get_param( 'directory' ) ) ) {
30
+ return new \WP_REST_Response(
31
+ array(
32
+ 'success' => false,
33
+ 'data' => __( 'Invalid search source provided.', 'string-locator' ),
34
+ ),
35
+ 400
36
+ );
37
+ }
38
+
39
+ $iterator = new Directory_Iterator(
40
+ $request->get_param( 'directory' ),
41
+ $request->get_param( 'search' ),
42
+ $request->get_param( 'regex' )
43
+ );
44
+
45
+ return array(
46
+ 'success' => true,
47
+ 'data' => $iterator->get_structure(),
48
+ );
49
+ }
50
+
51
+ }
52
+
53
+ new Directory_Structure();
includes/REST/class-save.php ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace JITS\StringLocator\REST;
4
+
5
+ class Save extends Base {
6
+
7
+ protected $rest_base = 'save';
8
+
9
+ public function __construct() {
10
+ parent::__construct();
11
+ }
12
+
13
+ public function register_rest_route() {
14
+ register_rest_route(
15
+ $this->namespace,
16
+ $this->rest_base,
17
+ array(
18
+ 'methods' => 'POST',
19
+ 'callback' => array( $this, 'save' ),
20
+ 'permission_callback' => array( $this, 'permission_callback' ),
21
+ )
22
+ );
23
+ }
24
+
25
+ public function save( \WP_REST_Request $request ) {
26
+ $handler = new \JITS\StringLocator\Save();
27
+
28
+ /**
29
+ * Filters the REST Request parameter values that will be used for the save call.
30
+ *
31
+ * @param array $params REST Request parameters.
32
+ */
33
+ $params = apply_filters( 'string_locator_save_params', $request->get_params() );
34
+
35
+ /**
36
+ * Filter the save handler used to perform edits.
37
+ *
38
+ * @attr object $handler The handler performing the save.
39
+ */
40
+ $handler = apply_filters( 'string_locator_save_handler', $handler );
41
+
42
+ /**
43
+ * Trigger an action before the save has been performed.
44
+ *
45
+ * @attr array $params The parameters used to perform the save.
46
+ */
47
+ do_action( 'string_locator_pre_save_action', $params );
48
+
49
+ $save_result = $handler->save( $params );
50
+
51
+ /**
52
+ * Trigger an action after the save has been performed.
53
+ *
54
+ * @attr array $save_result The result of the save.
55
+ * @attr array $params The parameters used to perform the save.
56
+ */
57
+ do_action( 'string_locator_post_save_action', $save_result, $params );
58
+
59
+ return $save_result;
60
+ }
61
+
62
+ }
63
+
64
+ new Save();
includes/REST/class-search.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace JITS\StringLocator\REST;
4
+
5
+ class Search extends Base {
6
+
7
+ protected $rest_base = 'search';
8
+
9
+ public function __construct() {
10
+ parent::__construct();
11
+ }
12
+
13
+ public function register_rest_route() {
14
+ register_rest_route(
15
+ $this->namespace,
16
+ $this->rest_base,
17
+ array(
18
+ 'methods' => 'POST',
19
+ 'callback' => array( $this, 'perform_search' ),
20
+ 'permission_callback' => array( $this, 'permission_callback' ),
21
+ )
22
+ );
23
+ }
24
+
25
+ public function perform_search( \WP_REST_Request $request ) {
26
+ $handler = new \JITS\StringLocator\Search();
27
+
28
+ /**
29
+ * Filter the search handler used to find strings.
30
+ *
31
+ * @attr object $handler The handler performing searches.
32
+ */
33
+ $handler = apply_filters( 'string_locator_search_handler', $handler );
34
+
35
+ return array(
36
+ 'success' => true,
37
+ 'data' => $handler->run( $request->get_param( 'filenum' ) ),
38
+ );
39
+ }
40
+
41
+ }
42
+
43
+ new Search();
includes/Tests/class-loopback.php ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace JITS\StringLocator\Tests;
4
+
5
+ class Loopback {
6
+ /**
7
+ * An array of HTTP status codes that will trigger the rollback feature.
8
+ *
9
+ * @var string[]
10
+ */
11
+ private $bad_http_codes = array( '500' );
12
+
13
+ /**
14
+ * An array holding any errors returned during testing.
15
+ *
16
+ * @var array
17
+ */
18
+ public $errors = array();
19
+
20
+ /**
21
+ * Loopback constructor.
22
+ */
23
+ public function __construct() {
24
+ add_action( 'string_locator_editor_checks', array( $this, 'print_checks_option' ) );
25
+
26
+ add_filter( 'string_locator_post_save', array( $this, 'maybe_perform_test' ) );
27
+ add_filter( 'string_locator_post_save_fail_notice', array( $this, 'return_failure_notices' ) );
28
+ }
29
+
30
+ public function return_failure_notices( $notices ) {
31
+ if ( empty( $this->errors ) ) {
32
+ return $notices;
33
+ }
34
+
35
+ return array_merge(
36
+ $notices,
37
+ $this->errors
38
+ );
39
+ }
40
+
41
+ public function maybe_perform_test( $save_successful ) {
42
+ // If another addon has determined the save is a failure, don't perform the test.
43
+ if ( ! $save_successful ) {
44
+ return $save_successful;
45
+ }
46
+
47
+ // Do not run this check if it has been disabled.
48
+ if ( ! isset( $_POST['string-locator-loopback-check'] ) ) {
49
+ return $save_successful;
50
+ }
51
+
52
+ return $this->run();
53
+ }
54
+
55
+ public function print_checks_option() {
56
+ ?>
57
+
58
+ <div class="row">
59
+ <label>
60
+ <input type="checkbox" name="string-locator-loopback-check" checked="checked">
61
+ <?php esc_html_e( 'Enable loopback tests after making a save.', 'string-locator' ); ?>
62
+ </label>
63
+ <br>
64
+ <em>
65
+ <?php esc_html_e( 'This feature is highly recommended, and is what WordPress does when using the plugin- or theme-editor.', 'string-locator' ); ?>
66
+ </em>
67
+ </div>
68
+
69
+ <?php
70
+ }
71
+
72
+ /**
73
+ * A helper function to return any errors.
74
+ *
75
+ * @return array
76
+ */
77
+ public function get_errors() {
78
+ return $this->errors;
79
+ }
80
+
81
+ /**
82
+ * Main test runner.
83
+ *
84
+ * @return bool
85
+ */
86
+ public function run() {
87
+ $this->bad_http_codes = apply_filters( 'string_locator_bad_http_codes', $this->bad_http_codes );
88
+
89
+ // Clear the error log before doing a new request.
90
+ $this->errors = array();
91
+
92
+ $frontend = $this->test_frontend();
93
+
94
+ // If the frontend has a loopback error, return it immediately.
95
+ if ( ! $frontend ) {
96
+ return false;
97
+ }
98
+
99
+ // If frontend tests are fine, we return the result of the backend scan.
100
+ return $this->test_backend();
101
+ }
102
+
103
+ private function test_frontend() {
104
+ $header = wp_remote_head( site_url() );
105
+
106
+ // If we get redirected, follow the redirect.
107
+ if ( ! is_wp_error( $header ) && 301 === (int) $header['response']['code'] ) {
108
+ $header = wp_remote_head( $header['headers']['location'] );
109
+ }
110
+
111
+ if ( is_wp_error( $header ) ) {
112
+ if ( 'http_request_failed' === $header->get_error_code() ) {
113
+ $this->errors[] = array(
114
+ 'type' => 'error',
115
+ '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' ),
116
+ );
117
+
118
+ return false;
119
+ }
120
+
121
+ // Fallback error message here.
122
+ $this->errors[] = array(
123
+ 'type' => 'error',
124
+ 'message' => $header->get_error_message(),
125
+ );
126
+
127
+ return false;
128
+ }
129
+
130
+ if ( in_array( $header['response']['code'], $this->bad_http_codes, true ) ) {
131
+ $this->errors[] = array(
132
+ 'type' => 'error',
133
+ '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' ),
134
+ );
135
+
136
+ return false;
137
+ }
138
+
139
+ return true;
140
+ }
141
+
142
+ private function test_backend() {
143
+ $header = wp_remote_head( admin_url() );
144
+
145
+ // If we get redirected, follow the redirect.
146
+ if ( ! is_wp_error( $header ) && 301 === (int) $header['response']['code'] ) {
147
+ $header = wp_remote_head( $header['headers']['location'] );
148
+ }
149
+
150
+ if ( is_wp_error( $header ) ) {
151
+ if ( 'http_request_failed' === $header->get_error_code() ) {
152
+ $this->errors[] = array(
153
+ 'type' => 'error',
154
+ 'message' => __( 'Your changes were not saved, as a check of your sites backend could not be completed afterwards. This may be due to a <a href="https://wordpress.org/support/article/loopbacks/">loopback</a> error.', 'string-locator' ),
155
+ );
156
+
157
+ return false;
158
+ }
159
+
160
+ // Fallback error message here.
161
+ $this->errors[] = array(
162
+ 'type' => 'error',
163
+ 'message' => $header->get_error_message(),
164
+ );
165
+
166
+ return false;
167
+ }
168
+
169
+ if ( in_array( $header['response']['code'], $this->bad_http_codes, true ) ) {
170
+ $this->errors[] = array(
171
+ 'type' => 'error',
172
+ 'message' => __( 'A 500 server error was detected on your sites backend after updating your file. We have restored the previous version of the file for you.', 'string-locator' ),
173
+ );
174
+
175
+ return false;
176
+ }
177
+
178
+ return true;
179
+ }
180
+ }
181
+
182
+ new Loopback();
includes/Tests/class-smart-scan.php ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace JITS\StringLocator\Tests;
4
+
5
+ class Smart_Scan {
6
+
7
+ /**
8
+ * The content that will be scanned.
9
+ *
10
+ * @var string
11
+ */
12
+ private $content = '';
13
+
14
+ /**
15
+ * An array holding any errors returned during testing.
16
+ *
17
+ * @var array
18
+ */
19
+ public $errors = array();
20
+
21
+ /**
22
+ * SmartScan constructor.
23
+ */
24
+ public function __construct() {
25
+ add_action( 'string_locator_editor_checks', array( $this, 'print_checks_option' ) );
26
+
27
+ add_filter( 'string_locator_pre_save', array( $this, 'maybe_perform_test' ), 10, 2 );
28
+ add_filter( 'string_locator_pre_save_fail_notice', array( $this, 'return_failure_notices' ) );
29
+ }
30
+
31
+ public function return_failure_notices( $notices ) {
32
+ if ( empty( $this->errors ) ) {
33
+ return $notices;
34
+ }
35
+
36
+ return array_merge(
37
+ $notices,
38
+ $this->errors
39
+ );
40
+ }
41
+
42
+ public function maybe_perform_test( $can_save, $content ) {
43
+ // If another addon has determined the file can not be saved, bail early.
44
+ if ( ! $can_save ) {
45
+ return $can_save;
46
+ }
47
+
48
+ // Do not perform a smart scan if the option for it is disabled.
49
+ if ( ! isset( $_POST['string-locator-smart-edit'] ) ) {
50
+ return $can_save;
51
+ }
52
+
53
+ return $this->run( $content );
54
+ }
55
+
56
+ public function print_checks_option() {
57
+ ?>
58
+
59
+ <div class="row">
60
+ <label>
61
+ <input type="checkbox" name="string-locator-smart-edit" checked="checked">
62
+ <?php esc_html_e( 'Enable a smart-scan of your code to help detect bracket mismatches before saving.', 'string-locator' ); ?>
63
+ </label>
64
+ </div>
65
+
66
+ <?php
67
+ }
68
+
69
+ /**
70
+ * A helper function to return any errors.
71
+ *
72
+ * @return array
73
+ */
74
+ public function get_errors() {
75
+ return $this->errors;
76
+ }
77
+
78
+ /**
79
+ * Main test runner.
80
+ *
81
+ * @param string $content The content to scan.
82
+ *
83
+ * @return bool
84
+ */
85
+ public function run( $content ) {
86
+ $this->content = $content;
87
+
88
+ // Reset the stored errors for a fresh run.
89
+ $this->errors = array();
90
+
91
+ $this->check_braces();
92
+ $this->check_brackets();
93
+ $this->check_parenthesis();
94
+
95
+ if ( ! empty( $this->errors ) ) {
96
+ return false;
97
+ }
98
+
99
+ return true;
100
+ }
101
+
102
+ private function check_braces() {
103
+ $open_brace = substr_count( $this->content, '{' );
104
+ $close_brace = substr_count( $this->content, '}' );
105
+
106
+ if ( $open_brace !== $close_brace ) {
107
+ $opened = $this->compare( '{', '}' );
108
+
109
+ foreach ( $opened as $line ) {
110
+ $this->errors[] = array(
111
+ 'type' => 'error',
112
+ 'message' => sprintf(
113
+ // translators: 1: Line number with an error.
114
+ __( 'There is an inconsistency in the opening and closing braces, { and }, of your file on line %s', 'string-locator' ),
115
+ '<a href="#" class="string-locator-edit-goto" data-goto-line="' . ( $line + 1 ) . '">' . ( $line + 1 ) . '</a>'
116
+ ),
117
+ );
118
+ }
119
+
120
+ return false;
121
+ }
122
+
123
+ return true;
124
+ }
125
+
126
+ private function check_brackets() {
127
+ $open_bracket = substr_count( $this->content, '[' );
128
+ $close_bracket = substr_count( $this->content, ']' );
129
+
130
+ if ( $open_bracket !== $close_bracket ) {
131
+ $opened = $this->compare( '[', ']' );
132
+
133
+ foreach ( $opened as $line ) {
134
+ $this->errors[] = array(
135
+ 'type' => 'error',
136
+ 'message' => sprintf(
137
+ // translators: 1: Line number with an error.
138
+ __( 'There is an inconsistency in the opening and closing braces, [ and ], of your file on line %s', 'string-locator' ),
139
+ '<a href="#" class="string-locator-edit-goto" data-goto-line="' . ( $line + 1 ) . '">' . ( $line + 1 ) . '</a>'
140
+ ),
141
+ );
142
+ }
143
+
144
+ return false;
145
+ }
146
+
147
+ return true;
148
+ }
149
+
150
+ private function check_parenthesis() {
151
+ $open_parenthesis = substr_count( $this->content, '(' );
152
+ $close_parenthesis = substr_count( $this->content, ')' );
153
+
154
+ if ( $open_parenthesis !== $close_parenthesis ) {
155
+ $this->failed_edit = true;
156
+
157
+ $opened = $this->compare( '(', ')' );
158
+
159
+ foreach ( $opened as $line ) {
160
+ $this->errors[] = array(
161
+ 'type' => 'error',
162
+ 'message' => sprintf(
163
+ // translators: 1: Line number with an error.
164
+ __( 'There is an inconsistency in the opening and closing braces, ( and ), of your file on line %s', 'string-locator' ),
165
+ '<a href="#" class="string-locator-edit-goto" data-goto-line="' . ( $line + 1 ) . '">' . ( $line + 1 ) . '</a>'
166
+ ),
167
+ );
168
+ }
169
+
170
+ return false;
171
+ }
172
+
173
+ return true;
174
+ }
175
+
176
+ /**
177
+ * Check for inconsistencies in brackets and similar.
178
+ *
179
+ * @param string $start Start delimited.
180
+ * @param string $end End delimiter.
181
+ *
182
+ * @return array
183
+ */
184
+ function compare( $start, $end ) {
185
+ $opened = array();
186
+
187
+ $lines = explode( "\n", $this->content );
188
+ for ( $i = 0; $i < count( $lines ); $i ++ ) {
189
+ if ( stristr( $lines[ $i ], $start ) ) {
190
+ $opened[] = $i;
191
+ }
192
+ if ( stristr( $lines[ $i ], $end ) ) {
193
+ array_pop( $opened );
194
+ }
195
+ }
196
+
197
+ return $opened;
198
+ }
199
+
200
+ }
201
+
202
+ new Smart_Scan();
includes/class-directory-iterator.php ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace JITS\StringLocator;
4
+
5
+ class Directory_Iterator {
6
+
7
+ /**
8
+ * Directory that will be searched.
9
+ *
10
+ * @var
11
+ */
12
+ private $directory;
13
+
14
+ /**
15
+ * The search term being used.
16
+ *
17
+ * @var
18
+ */
19
+ private $search;
20
+
21
+ /**
22
+ * A check if regex searches are enabled or not.
23
+ *
24
+ * @var
25
+ */
26
+ private $regex;
27
+
28
+ /**
29
+ * DirectoryIterator constructor.
30
+ *
31
+ * @param $directory
32
+ * @param $search
33
+ * @param $regex
34
+ *
35
+ * @return array
36
+ */
37
+ public function __construct( $directory, $search, $regex ) {
38
+ $this->directory = $directory;
39
+ $this->search = $search;
40
+ $this->regex = $regex;
41
+ }
42
+
43
+ /**
44
+ * Build the folder structure.
45
+ *
46
+ * @return array
47
+ */
48
+ public function get_structure() {
49
+ $scan_path = $this->prepare_scan_path( $this->directory );
50
+
51
+ if ( is_file( $scan_path->path ) ) {
52
+ $files = array( $scan_path->path );
53
+ } else {
54
+ $files = $this->ajax_scan_path( $scan_path->path );
55
+ }
56
+
57
+ /*
58
+ * Make sure each chunk of file arrays never exceeds 500 files
59
+ * This is to prevent the SQL string from being too large and crashing everything
60
+ */
61
+ $back_compat_filter = apply_filters( 'string-locator-files-per-array', 500 ); //phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
62
+
63
+ $file_chunks = array_chunk( $files, apply_filters( 'string_locator_files_per_array', $back_compat_filter ), true );
64
+
65
+ $store = (object) array(
66
+ 'scan_path' => $scan_path,
67
+ 'search' => wp_unslash( $this->search ),
68
+ 'directory' => $this->directory,
69
+ 'chunks' => count( $file_chunks ),
70
+ 'regex' => $this->regex,
71
+ );
72
+
73
+ $response = array(
74
+ 'total' => count( $files ),
75
+ 'current' => 0,
76
+ 'directory' => $scan_path,
77
+ 'chunks' => count( $file_chunks ),
78
+ 'regex' => $this->regex,
79
+ );
80
+
81
+ set_transient( 'string-locator-search-overview', $store );
82
+ update_option( 'string-locator-search-history', array(), false );
83
+
84
+ foreach ( $file_chunks as $count => $file_chunk ) {
85
+ set_transient( 'string-locator-search-files-' . $count, $file_chunk );
86
+ }
87
+
88
+ return $response;
89
+ }
90
+
91
+ /**
92
+ * Parse the search option to determine what kind of search we are performing and what directory to start in.
93
+ *
94
+ * @param string $option The search-type identifier.
95
+ *
96
+ * @return bool|object
97
+ */
98
+ private function prepare_scan_path( $option ) {
99
+ $data = array(
100
+ 'path' => '',
101
+ 'type' => '',
102
+ 'slug' => '',
103
+ );
104
+
105
+ switch ( true ) {
106
+ case ( 't--' === $option ):
107
+ $data['path'] = WP_CONTENT_DIR . '/themes/';
108
+ $data['type'] = 'theme';
109
+ break;
110
+ case ( strlen( $option ) > 3 && 't-' === substr( $option, 0, 2 ) ):
111
+ $data['path'] = WP_CONTENT_DIR . '/themes/' . substr( $option, 2 );
112
+ $data['type'] = 'theme';
113
+ $data['slug'] = substr( $option, 2 );
114
+ break;
115
+ case ( 'p--' === $option ):
116
+ $data['path'] = WP_CONTENT_DIR . '/plugins/';
117
+ $data['type'] = 'plugin';
118
+ break;
119
+ case ( 'mup--' === $option ):
120
+ $data['path'] = WP_CONTENT_DIR . '/mu-plugins/';
121
+ $data['type'] = 'mu-plugin';
122
+ break;
123
+ case ( strlen( $option ) > 3 && 'p-' === substr( $option, 0, 2 ) ):
124
+ $slug = explode( '/', substr( $option, 2 ) );
125
+
126
+ $data['path'] = WP_CONTENT_DIR . '/plugins/' . $slug[0];
127
+ $data['type'] = 'plugin';
128
+ $data['slug'] = $slug[0];
129
+ break;
130
+ case ( 'core' === $option ):
131
+ $data['path'] = ABSPATH;
132
+ $data['type'] = 'core';
133
+ break;
134
+ case ( 'wp-content' === $option ):
135
+ $data['path'] = WP_CONTENT_DIR;
136
+ $data['type'] = 'core';
137
+ break;
138
+ }
139
+
140
+ if ( empty( $data['path'] ) ) {
141
+ return false;
142
+ }
143
+
144
+ return (object) $data;
145
+ }
146
+
147
+ private function ajax_scan_path( $path ) {
148
+ $files = array();
149
+
150
+ $paths = new \RecursiveIteratorIterator(
151
+ new \RecursiveDirectoryIterator( $path ),
152
+ \RecursiveIteratorIterator::SELF_FIRST
153
+ );
154
+
155
+ foreach ( $paths as $name => $location ) {
156
+ if ( is_dir( $location->getPathname() ) ) {
157
+ continue;
158
+ }
159
+
160
+ $files[] = $location->getPathname();
161
+ }
162
+
163
+ return $files;
164
+ }
165
+
166
+ }
includes/class-save.php ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace JITS\StringLocator;
4
+
5
+ use JITS\StringLocator\Tests\Loopback;
6
+ use JITS\StringLocator\Tests\Smart_Scan;
7
+
8
+ class Save {
9
+
10
+ /**
11
+ * An array of notices to send back to the user.
12
+ *
13
+ * @var array
14
+ */
15
+ public $notice = array();
16
+
17
+ /**
18
+ * Save constructor.
19
+ */
20
+ public function __construct() {}
21
+
22
+ /**
23
+ * Handler for storing the content of the code editor.
24
+ *
25
+ * Also runs over the Smart-Scan if enabled.
26
+ *
27
+ * @return void|array
28
+ */
29
+ public function save( $save_params ) {
30
+ $_POST = $save_params;
31
+
32
+ if ( String_Locator::is_valid_location( $_POST['string-locator-path'] ) ) {
33
+ $path = urldecode( $_POST['string-locator-path'] );
34
+ $content = $_POST['string-locator-editor-content'];
35
+
36
+ /**
37
+ * Send an error notice if the file isn't writable
38
+ */
39
+ if ( ! is_writeable( $path ) ) {
40
+ $this->notice[] = array(
41
+ 'type' => 'error',
42
+ 'message' => __( 'The file could not be written to, please check file permissions or edit it manually.', 'string-locator' ),
43
+ );
44
+
45
+ return array(
46
+ 'notices' => $this->notice,
47
+ );
48
+ }
49
+
50
+ /**
51
+ * Filter if the save process should be performed or not.
52
+ *
53
+ * @attr bool $can_save Can the save be carried out.
54
+ * @attr string $content The content to save.
55
+ * @attr string $path Path to the file being edited.
56
+ */
57
+ $can_save = apply_filters( 'string_locator_pre_save', true, $content, $path );
58
+
59
+ if ( ! $can_save ) {
60
+ return array(
61
+ 'notices' => apply_filters( 'string_locator_pre_save_fail_notice', array() ),
62
+ );
63
+ }
64
+
65
+ $original = file_get_contents( $path );
66
+
67
+ $this->write_file( $path, $content );
68
+
69
+ /**
70
+ * Filter if the save process completed as it should or if warnings should be returned.
71
+ *
72
+ * @attr bool $save_successful Boolean indicating if the save was successful.
73
+ * @attr string $content The edited content.
74
+ * @attr string $original The original content.
75
+ * @attr string $path The path to the file being edited.
76
+ */
77
+ $save_successful = apply_filters( 'string_locator_post_save', true, $content, $original, $path );
78
+
79
+ /**
80
+ * Check the status of the site after making our edits.
81
+ * If the site fails, revert the changes to return the sites to its original state
82
+ */
83
+ if ( ! $save_successful ) {
84
+ $this->write_file( $path, $original );
85
+
86
+ return array(
87
+ 'notices' => apply_filters( 'string_locator_post_save_fail_notice', array() ),
88
+ );
89
+ }
90
+
91
+ return array(
92
+ 'notices' => array(
93
+ array(
94
+ 'type' => 'success',
95
+ 'message' => __( 'The file has been saved', 'string-locator' ),
96
+ ),
97
+ ),
98
+ );
99
+ } else {
100
+ return array(
101
+ 'notices' => array(
102
+ array(
103
+ 'type' => 'error',
104
+ 'message' => sprintf(
105
+ // translators: %s: The file location that was sent.
106
+ __( 'The file location provided, <strong>%s</strong>, is not valid.', 'string-locator' ),
107
+ $_POST['string-locator-path']
108
+ ),
109
+ ),
110
+ ),
111
+ );
112
+ }
113
+ }
114
+
115
+ /**
116
+ * When editing a file, this is where we write all the new content.
117
+ * We will break early if the user isn't allowed to edit files.
118
+ *
119
+ * @param string $path The path to the file.
120
+ * @param string $content The content to write to the file.
121
+ *
122
+ * @return void
123
+ */
124
+ private function write_file( $path, $content ) {
125
+ if ( ! current_user_can( 'edit_themes' ) ) {
126
+ return;
127
+ }
128
+
129
+ // Verify the location is valid before we try using it.
130
+ if ( ! String_Locator::is_valid_location( $path ) ) {
131
+ return;
132
+ }
133
+
134
+ $back_compat_filter = apply_filters( 'string-locator-filter-closing-php-tags', true ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
135
+
136
+ if ( apply_filters( 'string_locator_filter_closing_php_tags', $back_compat_filter ) ) {
137
+ $content = preg_replace( '/\?>$/si', '', trim( $content ), - 1, $replaced_strings );
138
+
139
+ if ( $replaced_strings >= 1 ) {
140
+ $this->notice[] = array(
141
+ 'type' => 'error',
142
+ 'message' => __( 'We detected a PHP code tag ending, this has been automatically stripped out to help prevent errors in your code.', 'string-locator' ),
143
+ );
144
+ }
145
+ }
146
+
147
+ $file = fopen( $path, 'w' );
148
+ $lines = explode( "\n", str_replace( array( "\r\n", "\r" ), "\n", $content ) );
149
+ $total_lines = count( $lines );
150
+
151
+ for ( $i = 0; $i < $total_lines; $i ++ ) {
152
+ $write_line = $lines[ $i ];
153
+
154
+ if ( ( $i + 1 ) < $total_lines ) {
155
+ $write_line .= PHP_EOL;
156
+ }
157
+
158
+ fwrite( $file, $write_line );
159
+ }
160
+
161
+ fclose( $file );
162
+ }
163
+ }
includes/class-search.php ADDED
@@ -0,0 +1,520 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace JITS\StringLocator;
4
+
5
+ class Search {
6
+
7
+ /**
8
+ * An array of file extensions that will be ignored by the scanner.
9
+ *
10
+ * @var string[]
11
+ */
12
+ private $bad_file_types = array(
13
+ 'rar',
14
+ '7z',
15
+ 'zip',
16
+ 'tar',
17
+ 'gz',
18
+ 'jpg',
19
+ 'jpeg',
20
+ 'png',
21
+ 'gif',
22
+ 'mp3',
23
+ 'mp4',
24
+ 'avi',
25
+ 'wmv',
26
+ );
27
+
28
+ /**
29
+ * The path to the currently editable file.
30
+ *
31
+ * @var string
32
+ */
33
+ private $path_to_use = '';
34
+
35
+ /**
36
+ * The length of the excerpt from the line containing a match.
37
+ *
38
+ * @var int
39
+ */
40
+ private $excerpt_length = 25;
41
+
42
+ /**
43
+ * The server-configured max time a script can run.
44
+ *
45
+ * @var int
46
+ */
47
+ private $max_execution_time = null;
48
+
49
+ /**
50
+ * The current time when our script started executing.
51
+ *
52
+ * @var float
53
+ */
54
+ private $start_execution_timer = 0;
55
+
56
+ /**
57
+ * The server-configured max amount of memory a script can use.
58
+ *
59
+ * @var int
60
+ */
61
+ private $max_memory_consumption = 0;
62
+
63
+ public function __construct() {
64
+ /**
65
+ * Define class variables requiring expressions
66
+ */
67
+ $this->path_to_use = ( is_multisite() ? 'network/admin.php' : 'tools.php' );
68
+ $this->excerpt_length = apply_filters( 'string_locator_excerpt_length', 25 );
69
+
70
+ $this->max_execution_time = absint( ini_get( 'max_execution_time' ) );
71
+ $this->start_execution_timer = microtime( true );
72
+
73
+ if ( $this->max_execution_time > 30 ) {
74
+ $this->max_execution_time = 30;
75
+ }
76
+
77
+ $this->set_memory_limit();
78
+ }
79
+
80
+ /**
81
+ * Sets up the memory limit variables.
82
+ *
83
+ * @return void
84
+ * @since 2.0.0
85
+ *
86
+ */
87
+ function set_memory_limit() {
88
+ $memory_limit = ini_get( 'memory_limit' );
89
+
90
+ $this->max_memory_consumption = absint( $memory_limit );
91
+
92
+ if ( strstr( $memory_limit, 'k' ) ) {
93
+ $this->max_memory_consumption = ( str_replace( 'k', '', $memory_limit ) * 1000 );
94
+ }
95
+ if ( strstr( $memory_limit, 'M' ) ) {
96
+ $this->max_memory_consumption = ( str_replace( 'M', '', $memory_limit ) * 1000000 );
97
+ }
98
+ if ( strstr( $memory_limit, 'G' ) ) {
99
+ $this->max_memory_consumption = ( str_replace( 'G', '', $memory_limit ) * 1000000000 );
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Check if the script is about to exceed the max execution time.
105
+ *
106
+ * @return bool
107
+ * @since 1.9.0
108
+ *
109
+ */
110
+ function nearing_execution_limit() {
111
+ // Max execution time is 0 or -1 (infinite) in server config
112
+ if ( 0 === $this->max_execution_time || - 1 === $this->max_execution_time ) {
113
+ return false;
114
+ }
115
+
116
+ $back_compat_filter = apply_filters( 'string-locator-extra-search-delay', 2 ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
117
+
118
+ $built_in_delay = apply_filters( 'string_locator_extra_search_delay', $back_compat_filter );
119
+ $execution_time = ( microtime( true ) - $this->start_execution_timer + $built_in_delay );
120
+
121
+ if ( $execution_time >= $this->max_execution_time ) {
122
+ return $execution_time;
123
+ }
124
+
125
+ return false;
126
+ }
127
+
128
+ /**
129
+ * Check if the script is about to exceed the server memory limit.
130
+ *
131
+ * @return bool
132
+ * @since 2.0.0
133
+ *
134
+ */
135
+ function nearing_memory_limit() {
136
+ // Check if the memory limit is set t o0 or -1 (infinite) in server config
137
+ if ( 0 === $this->max_memory_consumption || - 1 === $this->max_memory_consumption ) {
138
+ return false;
139
+ }
140
+
141
+ // We give our selves a 256k memory buffer, as we need to close off the script properly as well
142
+ $back_compat_filter = apply_filters( 'string-locator-extra-memory-buffer', 256000 ); //phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
143
+ $built_in_buffer = apply_filters( 'string_locator_extra_memory_buffer', $back_compat_filter );
144
+ $memory_use = ( memory_get_usage( true ) + $built_in_buffer );
145
+
146
+ if ( $memory_use >= $this->max_memory_consumption ) {
147
+ return $memory_use;
148
+ }
149
+
150
+ return false;
151
+ }
152
+
153
+ public function run( $filenum ) {
154
+ $_POST['filenum'] = $filenum;
155
+
156
+ $files_per_chunk = apply_filters( 'string_locator_files_per_array', 500 );
157
+ $response = array(
158
+ 'search' => array(),
159
+ 'filenum' => absint( $_POST['filenum'] ),
160
+ );
161
+
162
+ $filenum = absint( $_POST['filenum'] );
163
+
164
+ $chunk = ( ceil( $filenum / $files_per_chunk ) - 1 );
165
+ if ( $chunk < 0 ) {
166
+ $chunk = 0;
167
+ }
168
+
169
+ $scan_data = get_transient( 'string-locator-search-overview' );
170
+ $file_data = get_transient( 'string-locator-search-files-' . $chunk );
171
+
172
+ if ( ! isset( $file_data[ $filenum ] ) ) {
173
+ wp_send_json_error(
174
+ array(
175
+ 'continue' => false,
176
+ 'message' => sprintf(
177
+ /* translators: %d: The numbered reference to a file being searched. */
178
+ esc_html__( 'The file-number, %d, that was sent could not be found.', 'string-locator' ),
179
+ $filenum
180
+ ),
181
+ )
182
+ );
183
+ }
184
+
185
+ if ( $this->nearing_execution_limit() ) {
186
+ wp_send_json_error(
187
+ array(
188
+ 'continue' => false,
189
+ 'message' => sprintf(
190
+ /* 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. */
191
+ 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' ),
192
+ $this->max_execution_time,
193
+ $this->nearing_execution_limit()
194
+ ),
195
+ )
196
+ );
197
+ }
198
+ if ( $this->nearing_memory_limit() ) {
199
+ wp_send_json_error(
200
+ array(
201
+ 'continue' => false,
202
+ 'message' => sprintf(
203
+ /* translators: %1$d: Current amount of used system memory resources. %2$d: The maximum available system memory. */
204
+ 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' ),
205
+ $this->nearing_memory_limit(),
206
+ $this->max_memory_consumption
207
+ ),
208
+ )
209
+ );
210
+ }
211
+
212
+ $is_regex = false;
213
+ if ( isset( $scan_data->regex ) ) {
214
+ $is_regex = String_Locator::absbool( $scan_data->regex );
215
+ }
216
+
217
+ if ( $is_regex ) {
218
+ if ( false === @preg_match( $scan_data->search, '' ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
219
+ wp_send_json_error(
220
+ array(
221
+ 'continue' => false,
222
+ 'message' => sprintf(
223
+ /* translators: %s: The search string used. */
224
+ __( 'Your search string, <strong>%s</strong>, is not a valid pattern, and the search has been aborted.', 'string-locator' ),
225
+ esc_html( $scan_data->search )
226
+ ),
227
+ )
228
+ );
229
+ }
230
+ }
231
+
232
+ while ( ! $this->nearing_execution_limit() && ! $this->nearing_memory_limit() && isset( $file_data[ $filenum ] ) ) {
233
+ $filenum = absint( $_POST['filenum'] );
234
+ $search_results = null;
235
+ $next_file = $filenum + 1;
236
+
237
+ $next_chunk = ( ceil( ( $next_file ) / $files_per_chunk ) - 1 );
238
+ $chunk = ( ceil( $filenum / $files_per_chunk ) - 1 );
239
+ if ( $chunk < 0 ) {
240
+ $chunk = 0;
241
+ }
242
+ if ( $next_chunk < 0 ) {
243
+ $next_chunk = 0;
244
+ }
245
+
246
+ if ( ! isset( $file_data[ $filenum ] ) ) {
247
+ $chunk ++;
248
+ $file_data = get_transient( 'string-locator-search-files-' . $chunk );
249
+ continue;
250
+ }
251
+
252
+ $file_name = explode( '/', $file_data[ $filenum ] );
253
+ $file_name = end( $file_name );
254
+
255
+ /*
256
+ * Check the file type, if it's an unsupported type, we skip it
257
+ */
258
+ $file_type = explode( '.', $file_name );
259
+ $file_type = strtolower( end( $file_type ) );
260
+
261
+ /*
262
+ * Scan the file and look for our string, but only if it's an approved file extension
263
+ */
264
+ $bad_file_types = apply_filters( 'string_locator_bad_file_types', $this->bad_file_types );
265
+ if ( ! in_array( $file_type, $bad_file_types, true ) ) {
266
+ $search_results = $this->scan_file( $file_data[ $filenum ], $scan_data->search, $file_data[ $filenum ], $scan_data->scan_path->type, '', $is_regex );
267
+ }
268
+
269
+ $response['last_file'] = $file_data[ $filenum ];
270
+ $response['filenum'] = $filenum;
271
+ $response['filename'] = $file_name;
272
+ if ( $search_results ) {
273
+ $response['search'][] = $search_results;
274
+ }
275
+
276
+ if ( $next_chunk !== $chunk ) {
277
+ $file_data = get_transient( 'string-locator-search-files-' . $next_chunk );
278
+ }
279
+
280
+ $response['next_file'] = ( isset( $file_data[ $next_file ] ) ? $file_data[ $next_file ] : '' );
281
+
282
+ if ( ! empty( $search_results ) ) {
283
+ $history = get_option( 'string-locator-search-history', array() );
284
+ $history = array_merge( $history, $search_results );
285
+ update_option( 'string-locator-search-history', $history, false );
286
+ }
287
+
288
+ $_POST['filenum'] ++;
289
+ }
290
+
291
+ return $response;
292
+ }
293
+
294
+ /**
295
+ * Scan through an individual file to look for occurrences of £string.
296
+ *
297
+ * @param string $filename The path to the file.
298
+ * @param string $string The search string.
299
+ * @param mixed $location The file location object/string.
300
+ * @param string $type File type.
301
+ * @param string $slug The plugin/theme slug of the file.
302
+ * @param boolean $regex Should a regex search be performed.
303
+ *
304
+ * @return array
305
+ */
306
+ function scan_file( $filename, $string, $location, $type, $slug, $regex = false ) {
307
+ if ( empty( $string ) || ! is_file( $filename ) ) {
308
+ return array();
309
+ }
310
+ $output = array();
311
+ $linenum = 0;
312
+ $match_count = 0;
313
+
314
+ if ( ! is_object( $location ) ) {
315
+ $path = $location;
316
+ $location = explode( DIRECTORY_SEPARATOR, $location );
317
+ $file = end( $location );
318
+ } else {
319
+ $path = $location->getPathname();
320
+ $file = $location->getFilename();
321
+ }
322
+
323
+ /*
324
+ * Check if the filename matches our search pattern
325
+ */
326
+ if ( stristr( $file, $string ) || ( $regex && preg_match( $string, $file ) ) ) {
327
+ $relativepath = str_replace(
328
+ array(
329
+ ABSPATH,
330
+ '\\',
331
+ '/',
332
+ ),
333
+ array(
334
+ '',
335
+ DIRECTORY_SEPARATOR,
336
+ DIRECTORY_SEPARATOR,
337
+ ),
338
+ $path
339
+ );
340
+ $match_count ++;
341
+
342
+ $editurl = $this->create_edit_link( $path, $linenum );
343
+
344
+ $path_string = sprintf(
345
+ '<a href="%s">%s</a>',
346
+ esc_url( $editurl ),
347
+ esc_html( $relativepath )
348
+ );
349
+
350
+ $output[] = array(
351
+ 'ID' => $match_count,
352
+ 'linenum' => sprintf(
353
+ '[%s]',
354
+ esc_html__( 'Filename matches search', 'string-locator' )
355
+ ),
356
+ 'linepos' => '',
357
+ 'path' => $path,
358
+ 'filename' => $path_string,
359
+ 'filename_raw' => $relativepath,
360
+ 'editurl' => ( current_user_can( 'edit_themes' ) ? $editurl : false ),
361
+ 'stringresult' => $file,
362
+ );
363
+ }
364
+
365
+ $readfile = @fopen( $filename, 'r' );
366
+ if ( $readfile ) {
367
+ while ( ( $readline = fgets( $readfile ) ) !== false ) { // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
368
+ $string_preview_is_cut = false;
369
+ $linenum ++;
370
+ /**
371
+ * If our string is found in this line, output the line number and other data
372
+ */
373
+ if ( ( ! $regex && stristr( $readline, $string ) ) || ( $regex && preg_match( $string, $readline, $match, PREG_OFFSET_CAPTURE ) ) ) {
374
+ /**
375
+ * Prepare the visual path for the end user
376
+ * Removes path leading up to WordPress root and ensures consistent directory separators
377
+ */
378
+ $relativepath = str_replace(
379
+ array(
380
+ ABSPATH,
381
+ '\\',
382
+ '/',
383
+ ),
384
+ array(
385
+ '',
386
+ DIRECTORY_SEPARATOR,
387
+ DIRECTORY_SEPARATOR,
388
+ ),
389
+ $path
390
+ );
391
+ $match_count ++;
392
+
393
+ if ( $regex ) {
394
+ $str_pos = $match[0][1];
395
+ } else {
396
+ $str_pos = stripos( $readline, $string );
397
+ }
398
+
399
+ /**
400
+ * Create the URL to take the user to the editor
401
+ */
402
+ $editurl = $this->create_edit_link( $path, $linenum, $str_pos );
403
+
404
+ $string_preview = $readline;
405
+ if ( strlen( $string_preview ) > ( strlen( $string ) + $this->excerpt_length ) ) {
406
+ $string_location = strpos( $string_preview, $string );
407
+
408
+ $string_location_start = $string_location - $this->excerpt_length;
409
+ if ( $string_location_start < 0 ) {
410
+ $string_location_start = 0;
411
+ }
412
+
413
+ $string_location_end = ( strlen( $string ) + ( $this->excerpt_length * 2 ) );
414
+ if ( $string_location_end > strlen( $string_preview ) ) {
415
+ $string_location_end = strlen( $string_preview );
416
+ }
417
+
418
+ $string_preview = substr( $string_preview, $string_location_start, $string_location_end );
419
+ $string_preview_is_cut = true;
420
+ }
421
+
422
+ if ( $regex ) {
423
+ $string_preview = preg_replace( preg_replace( '/\/(.+)\//', '/($1)/', $string ), '<strong>$1</strong>', esc_html( $string_preview ) );
424
+ } else {
425
+ $string_preview = preg_replace( '/(' . $string . ')/i', '<strong>$1</strong>', esc_html( $string_preview ) );
426
+ }
427
+ if ( $string_preview_is_cut ) {
428
+ $string_preview = sprintf(
429
+ '&hellip;%s&hellip;',
430
+ $string_preview
431
+ );
432
+ }
433
+
434
+ $path_string = sprintf(
435
+ '<a href="%s">%s</a>',
436
+ esc_url( $editurl ),
437
+ esc_html( $relativepath )
438
+ );
439
+
440
+ $output[] = array(
441
+ 'ID' => $match_count,
442
+ 'linenum' => $linenum,
443
+ 'linepos' => $str_pos,
444
+ 'path' => $path,
445
+ 'filename' => $path_string,
446
+ 'filename_raw' => $relativepath,
447
+ 'editurl' => ( current_user_can( 'edit_themes' ) ? $editurl : false ),
448
+ 'stringresult' => $string_preview,
449
+ );
450
+ }
451
+ }
452
+
453
+ fclose( $readfile );
454
+ } else {
455
+ /**
456
+ * The file was unreadable, give the user a friendly notification
457
+ */
458
+ $output[] = array(
459
+ 'linenum' => '#',
460
+ // translators: 1: Filename.
461
+ 'filename' => esc_html( sprintf( __( 'Could not read file: %s', 'string-locator' ), $filename ) ),
462
+ 'stringresult' => '',
463
+ );
464
+ }
465
+
466
+ return $output;
467
+ }
468
+
469
+ /**
470
+ * Create an admin edit link for the supplied path.
471
+ *
472
+ * @param string $path Path to the file we'er adding a link for.
473
+ * @param int $line The line in the file where our search result was found.
474
+ * @param int $linepos The positin in the line where the search result was found.
475
+ *
476
+ * @return string
477
+ */
478
+ function create_edit_link( $path, $line = 0, $linepos = 0 ) {
479
+ $file_type = 'core';
480
+ $file_slug = '';
481
+ $content_path = str_replace( '\\', '/', WP_CONTENT_DIR );
482
+
483
+ $path = str_replace( '\\', '/', $path );
484
+ $paths = explode( '/', $path );
485
+
486
+ $url_args = array(
487
+ 'page=string-locator',
488
+ 'edit-file=' . end( $paths ),
489
+ );
490
+
491
+ switch ( true ) {
492
+ case ( in_array( 'wp-content', $paths, true ) && in_array( 'plugins', $paths, true ) ):
493
+ $file_type = 'plugin';
494
+ $content_path .= '/plugins/';
495
+ break;
496
+ case ( in_array( 'wp-content', $paths, true ) && in_array( 'themes', $paths, true ) ):
497
+ $file_type = 'theme';
498
+ $content_path .= '/themes/';
499
+ break;
500
+ }
501
+
502
+ $rel_path = str_replace( $content_path, '', $path );
503
+ $rel_paths = explode( '/', $rel_path );
504
+
505
+ if ( 'core' !== $file_type ) {
506
+ $file_slug = $rel_paths[0];
507
+ }
508
+
509
+ $url_args[] = 'file-reference=' . $file_slug;
510
+ $url_args[] = 'file-type=' . $file_type;
511
+ $url_args[] = 'string-locator-line=' . absint( $line );
512
+ $url_args[] = 'string-locator-linepos=' . absint( $linepos );
513
+ $url_args[] = 'string-locator-path=' . urlencode( str_replace( '/', DIRECTORY_SEPARATOR, $path ) );
514
+
515
+ $url = admin_url( $this->path_to_use . '?' . implode( '&', $url_args ) );
516
+
517
+ return $url;
518
+ }
519
+
520
+ }
includes/class-string-locator.php CHANGED
@@ -1,35 +1,24 @@
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.2';
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
@@ -47,21 +36,6 @@ class String_Locator {
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' ) );
@@ -71,53 +45,96 @@ class String_Locator {
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
  /**
@@ -131,7 +148,7 @@ class String_Locator {
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
  }
@@ -192,6 +209,8 @@ class String_Locator {
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 ) {
@@ -288,296 +307,6 @@ class String_Locator {
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
- update_option( 'string-locator-search-history', array(), false );
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_option( 'string-locator-search-history', array() );
553
- $history = array_merge( $history, $search_results );
554
- update_option( 'string-locator-search-history', $history, false );
555
- }
556
-
557
- $_POST['filenum'] ++;
558
- }
559
-
560
- wp_send_json_success( $response );
561
- }
562
-
563
- /**
564
- * Clean up our options used to help during the search.
565
- *
566
- * @return void
567
- */
568
- function ajax_clean_search() {
569
- if ( ! check_ajax_referer( 'string-locator-search', 'nonce', false ) ) {
570
- wp_send_json_error( __( 'Authentication failed', 'string-locator' ) );
571
- }
572
-
573
- $scan_data = get_transient( 'string-locator-search-overview' );
574
- for ( $i = 0; $i < $scan_data->chunks; $i ++ ) {
575
- delete_transient( 'string-locator-search-files-' . $i );
576
- }
577
-
578
- wp_send_json_success( true );
579
- }
580
-
581
  /**
582
  * Create a table row for insertion into the search results list.
583
  *
@@ -647,10 +376,10 @@ class String_Locator {
647
 
648
  $table_columns = sprintf(
649
  '<tr>
650
- <th scope="col" class="manage-column column-stringresult column-primary">%s</th>
651
- <th scope="col" class="manage-column column-filename">%s</th>
652
- <th scope="col" class="manage-column column-linenum">%s</th>
653
- <th scope="col" class="manage-column column-linepos">%s</th>
654
  </tr>',
655
  esc_html( __( 'String', 'string-locator' ) ),
656
  esc_html( __( 'File', 'string-locator' ) ),
@@ -675,150 +404,34 @@ class String_Locator {
675
  }
676
 
677
  /**
678
- * Create an admin edit link for the supplied path.
679
- *
680
- * @param string $path Path to the file we'er adding a link for.
681
- * @param int $line The line in the file where our search result was found.
682
- * @param int $linepos The positin in the line where the search result was found.
683
- *
684
- * @return string
685
- */
686
- function create_edit_link( $path, $line = 0, $linepos = 0 ) {
687
- $file_type = 'core';
688
- $file_slug = '';
689
- $content_path = str_replace( '\\', '/', WP_CONTENT_DIR );
690
-
691
- $path = str_replace( '\\', '/', $path );
692
- $paths = explode( '/', $path );
693
-
694
- $url_args = array(
695
- 'page=string-locator',
696
- 'edit-file=' . end( $paths ),
697
- );
698
-
699
- switch ( true ) {
700
- case ( in_array( 'wp-content', $paths, true ) && in_array( 'plugins', $paths, true ) ):
701
- $file_type = 'plugin';
702
- $content_path .= '/plugins/';
703
- break;
704
- case ( in_array( 'wp-content', $paths, true ) && in_array( 'themes', $paths, true ) ):
705
- $file_type = 'theme';
706
- $content_path .= '/themes/';
707
- break;
708
- }
709
-
710
- $rel_path = str_replace( $content_path, '', $path );
711
- $rel_paths = explode( '/', $rel_path );
712
-
713
- if ( 'core' !== $file_type ) {
714
- $file_slug = $rel_paths[0];
715
- }
716
-
717
- $url_args[] = 'file-reference=' . $file_slug;
718
- $url_args[] = 'file-type=' . $file_type;
719
- $url_args[] = 'string-locator-line=' . absint( $line );
720
- $url_args[] = 'string-locator-linepos=' . absint( $linepos );
721
- $url_args[] = 'string-locator-path=' . urlencode( str_replace( '/', DIRECTORY_SEPARATOR, $path ) );
722
-
723
- $url = admin_url( $this->path_to_use . '?' . implode( '&', $url_args ) );
724
-
725
- return $url;
726
- }
727
-
728
- /**
729
- * Parse the search option to determine what kind of search we are performing and what directory to start in.
730
- *
731
- * @param string $option The search-type identifier.
732
  *
733
- * @return bool|object
734
  */
735
- function prepare_scan_path( $option ) {
736
- $data = array(
737
- 'path' => '',
738
- 'type' => '',
739
- 'slug' => '',
740
- );
741
-
742
- switch ( true ) {
743
- case ( 't--' === $option ):
744
- $data['path'] = WP_CONTENT_DIR . '/themes/';
745
- $data['type'] = 'theme';
746
- break;
747
- case ( strlen( $option ) > 3 && 't-' === substr( $option, 0, 2 ) ):
748
- $data['path'] = WP_CONTENT_DIR . '/themes/' . substr( $option, 2 );
749
- $data['type'] = 'theme';
750
- $data['slug'] = substr( $option, 2 );
751
- break;
752
- case ( 'p--' === $option ):
753
- $data['path'] = WP_CONTENT_DIR . '/plugins/';
754
- $data['type'] = 'plugin';
755
- break;
756
- case ( 'mup--' === $option ):
757
- $data['path'] = WP_CONTENT_DIR . '/mu-plugins/';
758
- $data['type'] = 'mu-plugin';
759
- break;
760
- case ( strlen( $option ) > 3 && 'p-' === substr( $option, 0, 2 ) ):
761
- $slug = explode( '/', substr( $option, 2 ) );
762
-
763
- $data['path'] = WP_CONTENT_DIR . '/plugins/' . $slug[0];
764
- $data['type'] = 'plugin';
765
- $data['slug'] = $slug[0];
766
- break;
767
- case ( 'core' === $option ):
768
- $data['path'] = ABSPATH;
769
- $data['type'] = 'core';
770
- break;
771
- case ( 'wp-content' === $option ):
772
- $data['path'] = WP_CONTENT_DIR;
773
- $data['type'] = 'core';
774
- break;
775
- }
776
-
777
- if ( empty( $data['path'] ) ) {
778
- return false;
779
- }
780
-
781
- return (object) $data;
782
  }
783
 
784
  /**
785
- * Check if a file path is valid for editing.
786
  *
787
- * @param string $path Path to file.
788
  *
789
  * @return bool
790
  */
791
- function is_valid_location( $path ) {
792
- $valid = true;
793
- $path = str_replace( array( '/' ), array( DIRECTORY_SEPARATOR ), stripslashes( $path ) );
794
- $abspath = str_replace( array( '/' ), array( DIRECTORY_SEPARATOR ), ABSPATH );
795
-
796
- // Check that it is a valid file we are trying to access as well.
797
- if ( ! file_exists( $path ) ) {
798
- $valid = false;
799
- }
800
-
801
- if ( empty( $path ) ) {
802
- $valid = false;
803
- }
804
- if ( stristr( $path, '..' ) ) {
805
- $valid = false;
806
- }
807
- if ( ! stristr( $path, $abspath ) ) {
808
- $valid = false;
809
  }
810
 
811
- return $valid;
812
- }
813
-
814
- /**
815
- * Set the text domain for translated plugin content.
816
- *
817
- * @return void
818
- */
819
- function load_i18n() {
820
- $i18n_dir = 'string-locator/languages/';
821
- load_plugin_textdomain( 'string-locator', false, $i18n_dir );
822
  }
823
 
824
  /**
@@ -832,30 +445,34 @@ class String_Locator {
832
  return;
833
  }
834
 
835
- if ( ! wp_script_is( 'react', 'registered' ) ) {
836
- wp_register_script( 'react', trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'resources/js/react.js', array() );
837
- }
838
 
839
- if ( ! wp_script_is( 'react-dom', 'registered' ) ) {
840
- wp_register_script( 'react-dom', trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'resources/js/react-dom.js', array() );
841
- }
842
 
843
  /**
844
  * String Locator Styles
845
  */
846
- wp_enqueue_style( 'string-locator', trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'resources/css/string-locator.css', array(), $this->version );
847
 
848
  if ( ! isset( $_GET['edit-file'] ) || ! current_user_can( 'edit_themes' ) ) {
849
  /**
850
  * String Locator Scripts
851
  */
852
- wp_enqueue_script( 'string-locator-search', trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'resources/js/string-locator-search.js', array( 'jquery', 'wp-util' ), $this->version, true );
 
 
 
 
 
 
853
 
854
  wp_localize_script(
855
  'string-locator-search',
856
  'string_locator',
857
  array(
858
- 'ajax_url' => admin_url( 'admin-ajax.php' ),
859
  'search_nonce' => wp_create_nonce( 'string-locator-search' ),
860
  'search_current_prefix' => __( 'Next file: ', 'string-locator' ),
861
  'saving_results_string' => __( 'Saving search results&hellip;', 'string-locator' ),
@@ -864,6 +481,11 @@ class String_Locator {
864
  'search_error' => __( 'The above error was returned by your server, for more details please consult your servers error logs.', 'string-locator' ),
865
  'search_no_results' => __( 'Your search was completed, but no results were found.', 'string-locator' ),
866
  'warning_title' => __( 'Warning', 'string-locator' ),
 
 
 
 
 
867
  )
868
  );
869
 
@@ -877,7 +499,13 @@ class String_Locator {
877
  /**
878
  * String Locator Scripts
879
  */
880
- 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 );
 
 
 
 
 
 
881
 
882
  wp_localize_script(
883
  'string-locator-editor',
@@ -886,7 +514,9 @@ class String_Locator {
886
  'CodeMirror' => $code_mirror,
887
  'goto_line' => absint( $_GET['string-locator-line'] ),
888
  'goto_linepos' => absint( $_GET['string-locator-linepos'] ),
889
- 'save_url' => get_rest_url( null, 'string-locator/v1/save' ),
 
 
890
  )
891
  );
892
  }
@@ -939,6 +569,8 @@ class String_Locator {
939
  return false;
940
  }
941
 
 
 
942
  /**
943
  * Show the edit page if;
944
  * - The edit file path query var is set
@@ -946,273 +578,25 @@ class String_Locator {
946
  * - The edit file path query var does not contains double dots (used to traverse directories)
947
  * - The user is capable of editing files.
948
  */
949
- if ( isset( $_GET['string-locator-path'] ) && $this->is_valid_location( $_GET['string-locator-path'] ) && current_user_can( 'edit_themes' ) ) {
950
- include_once( dirname( __FILE__ ) . '/../editor.php' );
951
  } else {
952
- include_once( dirname( __FILE__ ) . '/../search.php' );
953
- }
954
- }
955
-
956
- function admin_body_class( $class ) {
957
- if ( isset( $_GET['string-locator-path'] ) && $this->is_valid_location( $_GET['string-locator-path'] ) && current_user_can( 'edit_themes' ) ) {
958
- $class .= ' file-edit-screen';
959
  }
960
 
961
- return $class;
962
- }
963
 
964
- /**
965
- * Check for inconsistencies in brackets and similar.
966
- *
967
- * @param string $start Start delimited.
968
- * @param string $end End delimiter.
969
- * @param string $string The string to scan.
970
- *
971
- * @return array
972
- */
973
- function smart_scan( $start, $end, $string ) {
974
- $opened = array();
975
-
976
- $lines = explode( "\n", $string );
977
- for ( $i = 0; $i < count( $lines ); $i ++ ) {
978
- if ( stristr( $lines[ $i ], $start ) ) {
979
- $opened[] = $i;
980
- }
981
- if ( stristr( $lines[ $i ], $end ) ) {
982
- array_pop( $opened );
983
- }
984
  }
985
-
986
- return $opened;
987
  }
988
 
989
- /**
990
- * Handler for storing the content of the code editor.
991
- *
992
- * Also runs over the Smart-Scan if enabled.
993
- *
994
- * @return void|array
995
- */
996
- function editor_save( $request ) {
997
- $_POST = $request->get_params();
998
-
999
- $check_loopback = isset( $_POST['string-locator-loopback-check'] );
1000
- $do_smart_scan = isset( $_POST['string-locator-smart-edit'] );
1001
-
1002
- if ( $this->is_valid_location( $_POST['string-locator-path'] ) ) {
1003
- $path = urldecode( $_POST['string-locator-path'] );
1004
- $content = stripslashes( $_POST['string-locator-editor-content'] );
1005
-
1006
- /**
1007
- * Send an error notice if the file isn't writable
1008
- */
1009
- if ( ! is_writeable( $path ) ) {
1010
- $this->notice[] = array(
1011
- 'type' => 'error',
1012
- 'message' => __( 'The file could not be written to, please check file permissions or edit it manually.', 'string-locator' ),
1013
- );
1014
-
1015
- return array(
1016
- 'notices' => $this->notice,
1017
- );
1018
- }
1019
-
1020
- /**
1021
- * If enabled, run the Smart-Scan on the content before saving it
1022
- */
1023
- if ( $do_smart_scan ) {
1024
- $open_brace = substr_count( $content, '{' );
1025
- $close_brace = substr_count( $content, '}' );
1026
- if ( $open_brace !== $close_brace ) {
1027
- $this->failed_edit = true;
1028
-
1029
- $opened = $this->smart_scan( '{', '}', $content );
1030
-
1031
- foreach ( $opened as $line ) {
1032
- $this->notice[] = array(
1033
- 'type' => 'error',
1034
- 'message' => sprintf(
1035
- // translators: 1: Line number with an error.
1036
- __( 'There is an inconsistency in the opening and closing braces, { and }, of your file on line %s', 'string-locator' ),
1037
- '<a href="#" class="string-locator-edit-goto" data-goto-line="' . ( $line + 1 ) . '">' . ( $line + 1 ) . '</a>'
1038
- ),
1039
- );
1040
- }
1041
- }
1042
-
1043
- $open_bracket = substr_count( $content, '[' );
1044
- $close_bracket = substr_count( $content, ']' );
1045
- if ( $open_bracket !== $close_bracket ) {
1046
- $this->failed_edit = true;
1047
-
1048
- $opened = $this->smart_scan( '[', ']', $content );
1049
-
1050
- foreach ( $opened as $line ) {
1051
- $this->notice[] = array(
1052
- 'type' => 'error',
1053
- 'message' => sprintf(
1054
- // translators: 1: Line number with an error.
1055
- __( 'There is an inconsistency in the opening and closing braces, [ and ], of your file on line %s', 'string-locator' ),
1056
- '<a href="#" class="string-locator-edit-goto" data-goto-line="' . ( $line + 1 ) . '">' . ( $line + 1 ) . '</a>'
1057
- ),
1058
- );
1059
- }
1060
- }
1061
-
1062
- $open_parenthesis = substr_count( $content, '(' );
1063
- $close_parenthesis = substr_count( $content, ')' );
1064
- if ( $open_parenthesis !== $close_parenthesis ) {
1065
- $this->failed_edit = true;
1066
-
1067
- $opened = $this->smart_scan( '(', ')', $content );
1068
-
1069
- foreach ( $opened as $line ) {
1070
- $this->notice[] = array(
1071
- 'type' => 'error',
1072
- 'message' => sprintf(
1073
- // translators: 1: Line number with an error.
1074
- __( 'There is an inconsistency in the opening and closing braces, ( and ), of your file on line %s', 'string-locator' ),
1075
- '<a href="#" class="string-locator-edit-goto" data-goto-line="' . ( $line + 1 ) . '">' . ( $line + 1 ) . '</a>'
1076
- ),
1077
- );
1078
- }
1079
- }
1080
-
1081
- if ( $this->failed_edit ) {
1082
- return array(
1083
- 'notices' => $this->notice,
1084
- );
1085
- }
1086
- }
1087
-
1088
- $original = file_get_contents( $path );
1089
-
1090
- $this->write_file( $path, $content );
1091
-
1092
- /**
1093
- * Check the status of the site after making our edits.
1094
- * If the site fails, revert the changes to return the sites to its original state
1095
- */
1096
- if ( $check_loopback ) {
1097
- $header = wp_remote_head( site_url() );
1098
-
1099
- if ( ! is_wp_error( $header ) && 301 === (int) $header['response']['code'] ) {
1100
- $header = wp_remote_head( $header['headers']['location'] );
1101
- }
1102
-
1103
- $bad_http_check = apply_filters( 'string_locator_bad_http_codes', $this->bad_http_codes );
1104
- }
1105
-
1106
- if ( $check_loopback && is_wp_error( $header ) ) {
1107
- $this->failed_edit = true;
1108
- $this->write_file( $path, $original );
1109
-
1110
- // Likely loopback error, so be useful in our errors.
1111
- if ( 'http_request_failed' === $header->get_error_code() ) {
1112
- return array(
1113
- 'notices' => array(
1114
- array(
1115
- 'type' => 'error',
1116
- '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' ),
1117
- ),
1118
- ),
1119
- );
1120
- }
1121
-
1122
- // Fallback error message here.
1123
- return array(
1124
- 'notices' => array(
1125
- array(
1126
- 'type' => 'error',
1127
- 'message' => $header->get_error_message(),
1128
- ),
1129
- ),
1130
- );
1131
- } elseif ( $check_loopback && in_array( $header['response']['code'], $bad_http_check, true ) ) {
1132
- $this->failed_edit = true;
1133
- $this->write_file( $path, $original );
1134
-
1135
- return array(
1136
- 'notices' => array(
1137
- array(
1138
- 'type' => 'error',
1139
- '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' ),
1140
- ),
1141
- ),
1142
- );
1143
- } else {
1144
- return array(
1145
- 'notices' => array(
1146
- array(
1147
- 'type' => 'success',
1148
- 'message' => __( 'The file has been saved', 'string-locator' ),
1149
- ),
1150
- ),
1151
- );
1152
- }
1153
- } else {
1154
- return array(
1155
- 'notices' => array(
1156
- array(
1157
- 'type' => 'error',
1158
- 'message' => sprintf(
1159
- // translators: %s: The file location that was sent.
1160
- __( 'The file location provided, <strong>%s</strong>, is not valid.', 'string-locator' ),
1161
- $_POST['string-locator-path']
1162
- ),
1163
- ),
1164
- ),
1165
- );
1166
- }
1167
- }
1168
-
1169
- /**
1170
- * When editing a file, this is where we write all the new content.
1171
- * We will break early if the user isn't allowed to edit files.
1172
- *
1173
- * @param string $path The path to the file.
1174
- * @param string $content The content to write to the file.
1175
- *
1176
- * @return void
1177
- */
1178
- private function write_file( $path, $content ) {
1179
- if ( ! current_user_can( 'edit_themes' ) ) {
1180
- return;
1181
- }
1182
-
1183
- // Verify the location is valid before we try using it.
1184
- if ( ! $this->is_valid_location( $path ) ) {
1185
- return;
1186
- }
1187
-
1188
- $back_compat_filter = apply_filters( 'string-locator-filter-closing-php-tags', true ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
1189
-
1190
- if ( apply_filters( 'string_locator_filter_closing_php_tags', $back_compat_filter ) ) {
1191
- $content = preg_replace( '/\?>$/si', '', trim( $content ), - 1, $replaced_strings );
1192
-
1193
- if ( $replaced_strings >= 1 ) {
1194
- $this->notice[] = array(
1195
- 'type' => 'error',
1196
- 'message' => __( 'We detected a PHP code tag ending, this has been automatically stripped out to help prevent errors in your code.', 'string-locator' ),
1197
- );
1198
- }
1199
- }
1200
-
1201
- $file = fopen( $path, 'w' );
1202
- $lines = explode( "\n", str_replace( array( "\r\n", "\r" ), "\n", $content ) );
1203
- $total_lines = count( $lines );
1204
-
1205
- for ( $i = 0; $i < $total_lines; $i ++ ) {
1206
- $write_line = $lines[ $i ];
1207
-
1208
- if ( ( $i + 1 ) < $total_lines ) {
1209
- $write_line .= PHP_EOL;
1210
- }
1211
-
1212
- fwrite( $file, $write_line );
1213
  }
1214
 
1215
- fclose( $file );
1216
  }
1217
 
1218
  /**
@@ -1233,196 +617,32 @@ class String_Locator {
1233
  }
1234
 
1235
  /**
1236
- * Scan through an individual file to look for occurrences of £string.
1237
  *
1238
- * @param string $filename The path to the file.
1239
- * @param string $string The search string.
1240
- * @param mixed $location The file location object/string.
1241
- * @param string $type File type.
1242
- * @param string $slug The plugin/theme slug of the file.
1243
- * @param boolean $regex Should a regex search be performed.
1244
  *
1245
- * @return array
1246
  */
1247
- function scan_file( $filename, $string, $location, $type, $slug, $regex = false ) {
1248
- if ( empty( $string ) || ! is_file( $filename ) ) {
1249
- return array();
1250
- }
1251
- $output = array();
1252
- $linenum = 0;
1253
- $match_count = 0;
1254
-
1255
- if ( ! is_object( $location ) ) {
1256
- $path = $location;
1257
- $location = explode( DIRECTORY_SEPARATOR, $location );
1258
- $file = end( $location );
1259
- } else {
1260
- $path = $location->getPathname();
1261
- $file = $location->getFilename();
1262
- }
1263
-
1264
- /*
1265
- * Check if the filename matches our search pattern
1266
- */
1267
- if ( stristr( $file, $string ) || ( $regex && preg_match( $string, $file ) ) ) {
1268
- $relativepath = str_replace(
1269
- array(
1270
- ABSPATH,
1271
- '\\',
1272
- '/',
1273
- ),
1274
- array(
1275
- '',
1276
- DIRECTORY_SEPARATOR,
1277
- DIRECTORY_SEPARATOR,
1278
- ),
1279
- $path
1280
- );
1281
- $match_count ++;
1282
-
1283
- $editurl = $this->create_edit_link( $path, $linenum );
1284
-
1285
- $path_string = sprintf(
1286
- '<a href="%s">%s</a>',
1287
- esc_url( $editurl ),
1288
- esc_html( $relativepath )
1289
- );
1290
 
1291
- $output[] = array(
1292
- 'ID' => $match_count,
1293
- 'linenum' => sprintf(
1294
- '[%s]',
1295
- esc_html__( 'Filename matches search', 'string-locator' )
1296
- ),
1297
- 'linepos' => '',
1298
- 'path' => $path,
1299
- 'filename' => $path_string,
1300
- 'filename_raw' => $relativepath,
1301
- 'editurl' => ( current_user_can( 'edit_themes' ) ? $editurl : false ),
1302
- 'stringresult' => $file,
1303
- );
1304
  }
1305
 
1306
- $readfile = @fopen( $filename, 'r' );
1307
- if ( $readfile ) {
1308
- while ( ( $readline = fgets( $readfile ) ) !== false ) { // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
1309
- $string_preview_is_cut = false;
1310
- $linenum ++;
1311
- /**
1312
- * If our string is found in this line, output the line number and other data
1313
- */
1314
- if ( ( ! $regex && stristr( $readline, $string ) ) || ( $regex && preg_match( $string, $readline, $match, PREG_OFFSET_CAPTURE ) ) ) {
1315
- /**
1316
- * Prepare the visual path for the end user
1317
- * Removes path leading up to WordPress root and ensures consistent directory separators
1318
- */
1319
- $relativepath = str_replace(
1320
- array(
1321
- ABSPATH,
1322
- '\\',
1323
- '/',
1324
- ),
1325
- array(
1326
- '',
1327
- DIRECTORY_SEPARATOR,
1328
- DIRECTORY_SEPARATOR,
1329
- ),
1330
- $path
1331
- );
1332
- $match_count ++;
1333
-
1334
- if ( $regex ) {
1335
- $str_pos = $match[0][1];
1336
- } else {
1337
- $str_pos = stripos( $readline, $string );
1338
- }
1339
-
1340
- /**
1341
- * Create the URL to take the user to the editor
1342
- */
1343
- $editurl = $this->create_edit_link( $path, $linenum, $str_pos );
1344
-
1345
- $string_preview = $readline;
1346
- if ( strlen( $string_preview ) > ( strlen( $string ) + $this->excerpt_length ) ) {
1347
- $string_location = strpos( $string_preview, $string );
1348
-
1349
- $string_location_start = $string_location - $this->excerpt_length;
1350
- if ( $string_location_start < 0 ) {
1351
- $string_location_start = 0;
1352
- }
1353
-
1354
- $string_location_end = ( strlen( $string ) + ( $this->excerpt_length * 2 ) );
1355
- if ( $string_location_end > strlen( $string_preview ) ) {
1356
- $string_location_end = strlen( $string_preview );
1357
- }
1358
-
1359
- $string_preview = substr( $string_preview, $string_location_start, $string_location_end );
1360
- $string_preview_is_cut = true;
1361
- }
1362
-
1363
- if ( $regex ) {
1364
- $string_preview = preg_replace( preg_replace( '/\/(.+)\//', '/($1)/', $string ), '<strong>$1</strong>', esc_html( $string_preview ) );
1365
- } else {
1366
- $string_preview = preg_replace( '/(' . $string . ')/i', '<strong>$1</strong>', esc_html( $string_preview ) );
1367
- }
1368
- if ( $string_preview_is_cut ) {
1369
- $string_preview = sprintf(
1370
- '&hellip;%s&hellip;',
1371
- $string_preview
1372
- );
1373
- }
1374
-
1375
- $path_string = sprintf(
1376
- '<a href="%s">%s</a>',
1377
- esc_url( $editurl ),
1378
- esc_html( $relativepath )
1379
- );
1380
-
1381
- $output[] = array(
1382
- 'ID' => $match_count,
1383
- 'linenum' => $linenum,
1384
- 'linepos' => $str_pos,
1385
- 'path' => $path,
1386
- 'filename' => $path_string,
1387
- 'filename_raw' => $relativepath,
1388
- 'editurl' => ( current_user_can( 'edit_themes' ) ? $editurl : false ),
1389
- 'stringresult' => $string_preview,
1390
- );
1391
- }
1392
- }
1393
-
1394
- fclose( $readfile );
1395
- } else {
1396
- /**
1397
- * The file was unreadable, give the user a friendly notification
1398
- */
1399
- $output[] = array(
1400
- 'linenum' => '#',
1401
- // translators: 1: Filename.
1402
- 'filename' => esc_html( sprintf( __( 'Could not read file: %s', 'string-locator' ), $filename ) ),
1403
- 'stringresult' => '',
1404
- );
1405
  }
1406
-
1407
- return $output;
1408
- }
1409
-
1410
- function ajax_scan_path( $path ) {
1411
- $files = array();
1412
-
1413
- $paths = new RecursiveIteratorIterator(
1414
- new RecursiveDirectoryIterator( $path ),
1415
- RecursiveIteratorIterator::SELF_FIRST
1416
- );
1417
-
1418
- foreach ( $paths as $name => $location ) {
1419
- if ( is_dir( $location->getPathname() ) ) {
1420
- continue;
1421
- }
1422
-
1423
- $files[] = $location->getPathname();
1424
  }
1425
 
1426
- return $files;
1427
  }
1428
  }
1
  <?php
2
 
3
+ namespace JITS\StringLocator;
4
+
5
  /**
6
  * Class String_Locator
7
  */
8
  class String_Locator {
9
  /**
10
+ * The code language used for the editing page.
11
+ *
12
+ * @var string
 
 
 
 
 
 
 
 
13
  */
14
  public $string_locator_language = '';
15
+
16
+ /**
17
+ * An array containing all notices to display.
18
+ *
19
+ * @var array
20
+ */
21
+ public $notice = array();
 
 
 
 
 
22
 
23
  /**
24
  * Construct the plugin
36
  * @return void
37
  */
38
  public function init() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
40
 
41
  add_action( 'admin_menu', array( $this, 'populate_menu' ) );
45
 
46
  add_action( 'plugins_loaded', array( $this, 'load_i18n' ) );
47
 
 
 
 
 
48
  add_filter( 'plugin_row_meta', array( $this, 'plugin_row_meta' ), 10, 2 );
49
 
50
+ add_filter( 'string_locator_search_sources_markup', array( $this, 'add_search_options' ), 10, 2 );
51
+ add_action( 'wp_ajax_string_locator_notice_dismiss', array( $this, 'dismiss_notice' ) );
52
 
53
+ add_action( 'admin_notices', array( $this, 'show_sponsorship_notice' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  }
55
 
56
+ public function dismiss_notice() {
57
+ // Only users who can use the plugin should be able to dismiss the notice.
58
+ if ( ! current_user_can( 'update_core' ) || ! wp_verify_nonce( $_POST['_nonce'], 'string-locator-notice-dismiss' ) ) {
59
+ return;
60
+ }
61
+
62
+ update_option( 'string-locator-sponsorship-notice-dismissed', array( 'is_dismissed' => true ) );
63
+ }
 
64
 
65
+ public function show_sponsorship_notice() {
66
+ $screen = get_current_screen();
67
 
68
+ // Only show the notice on the plugins search page.
69
+ if ( 'tools_page_string-locator' !== $screen->id || isset( $_GET['edit-file'] ) ) {
70
+ return;
 
 
71
  }
72
+
73
+ // Do not show the notice to users who have dismissed it.
74
+ $option = get_option( 'string-locator-sponsorship-notice-dismissed', array( 'is_dismissed' => false ) );
75
+ if ( true === $option['is_dismissed'] ) {
76
+ return;
77
  }
78
+
79
+ ?>
80
+
81
+ <div class="notice notice-info is-dismissible" id="string-locator-sponsorship-notice">
82
+ <p>
83
+ <?php esc_html_e( 'Thank you for trying out the String Locator plugin!', 'string-locator' ); ?>
84
+ </p>
85
+
86
+ <p>
87
+ <?php esc_html_e( 'If you like the plugin, please consider making a donation to support the plugin development.', 'string-locator' ); ?>
88
+ </p>
89
+
90
+ <p>
91
+ <a href="https://github.com/sponsors/Clorith/" target="_blank" class="button button-primary">
92
+ <?php esc_html_e( 'Donate via GitHub', 'string-locator' ); ?>
93
+ </a>
94
+
95
+ <a href="https://paypal.me/clorith" target="_blank" class="button">
96
+ <?php esc_html_e( 'Donate via PayPal', 'string-locator' ); ?>
97
+ </a>
98
+ </p>
99
+
100
+ <script>
101
+ jQuery( document ).ready( function( $ ) {
102
+ $( '#string-locator-sponsorship-notice' ).on( 'click', '.notice-dismiss', function() {
103
+ $.post( ajaxurl, {
104
+ action: 'string_locator_notice_dismiss',
105
+ _nonce: '<?php echo wp_create_nonce( 'string-locator-notice-dismiss' ); ?>'
106
+ } );
107
+ } );
108
+ } );
109
+ </script>
110
+ </div>
111
+
112
+ <?php
113
+ }
114
+
115
+ public function add_search_options( $searchers, $search_location ) {
116
+ ob_start();
117
+ ?>
118
+ <optgroup label="<?php esc_attr_e( 'Core', 'string-locator' ); ?>">
119
+ <option value="core"><?php esc_html_e( 'The whole WordPress directory', 'string-locator' ); ?></option>
120
+ <option value="wp-content"><?php esc_html_e( 'Everything under wp-content', 'string-locator' ); ?></option>
121
+ </optgroup>
122
+ <optgroup label="<?php esc_attr_e( 'Themes', 'string-locator' ); ?>">
123
+ <?php echo String_Locator::get_themes_options( $search_location ); ?>
124
+ </optgroup>
125
+ <?php if ( String_Locator::has_mu_plugins() ) : ?>
126
+ <optgroup label="<?php esc_attr_e( 'Must Use Plugins', 'string-locator' ); ?>">
127
+ <?php echo String_Locator::get_mu_plugins_options( $search_location ); ?>
128
+ </optgroup>
129
+ <?php endif; ?>
130
+ <optgroup label="<?php esc_attr_e( 'Plugins', 'string-locator' ); ?>">
131
+ <?php echo String_Locator::get_plugins_options( $search_location ); ?>
132
+ </optgroup>
133
+ <?php
134
+
135
+ $searchers .= ob_get_clean();
136
+
137
+ return $searchers;
138
  }
139
 
140
  /**
148
  function plugin_row_meta( $meta, $plugin_file ) {
149
  if ( 'string-locator/string-locator.php' === $plugin_file ) {
150
  $meta[] = sprintf(
151
+ '<a href="https://github.com/sponsors/Clorith/">%s</a>',
152
  esc_html__( 'Donate to this plugin', 'string-locator' )
153
  );
154
  }
209
  'string-locator-path' => ( isset( $_GET['string-locator-path'] ) ? $_GET['string-locator-path'] : '' ),
210
  );
211
 
212
+ $fields = apply_filters( 'string_locator_editor_fields', $fields );
213
+
214
  $field_output = array();
215
 
216
  foreach ( $fields as $label => $value ) {
307
  return false;
308
  }
309
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
  /**
311
  * Create a table row for insertion into the search results list.
312
  *
376
 
377
  $table_columns = sprintf(
378
  '<tr>
379
+ <th scope="col" class="manage-column column-stringresult column-primary string">%s</th>
380
+ <th scope="col" class="manage-column column-filename filename">%s</th>
381
+ <th scope="col" class="manage-column column-linenum line">%s</th>
382
+ <th scope="col" class="manage-column column-linepos position">%s</th>
383
  </tr>',
384
  esc_html( __( 'String', 'string-locator' ) ),
385
  esc_html( __( 'File', 'string-locator' ) ),
404
  }
405
 
406
  /**
407
+ * Set the text domain for translated plugin content.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  *
409
+ * @return void
410
  */
411
+ function load_i18n() {
412
+ $i18n_dir = 'string-locator/languages/';
413
+ load_plugin_textdomain( 'string-locator', false, $i18n_dir );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
414
  }
415
 
416
  /**
417
+ * Convert a value to its absolute boolean interpretation.
418
  *
419
+ * @param $value
420
  *
421
  * @return bool
422
  */
423
+ public static function absbool( $value ) {
424
+ if ( is_bool( $value ) ) {
425
+ $bool = $value;
426
+ } else {
427
+ if ( 'false' === $value ) {
428
+ $bool = false;
429
+ } else {
430
+ $bool = true;
431
+ }
 
 
 
 
 
 
 
 
 
432
  }
433
 
434
+ return $bool;
 
 
 
 
 
 
 
 
 
 
435
  }
436
 
437
  /**
445
  return;
446
  }
447
 
448
+ $search = STRING_LOCATOR_PLUGIN_DIR . 'build/string-locator-search.asset.php';
449
+ $editor = STRING_LOCATOR_PLUGIN_DIR . 'build/string-locator.asset.php';
 
450
 
451
+ $search = file_exists( $search ) ? require $search : array();
452
+ $editor = file_exists( $editor ) ? require $editor : array();
 
453
 
454
  /**
455
  * String Locator Styles
456
  */
457
+ wp_enqueue_style( 'string-locator', trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'build/string-locator.css', array(), $search['version'] );
458
 
459
  if ( ! isset( $_GET['edit-file'] ) || ! current_user_can( 'edit_themes' ) ) {
460
  /**
461
  * String Locator Scripts
462
  */
463
+ wp_enqueue_script(
464
+ 'string-locator-search',
465
+ trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'build/string-locator-search.js',
466
+ array( 'jquery', 'wp-util' ),
467
+ $search['version'],
468
+ true
469
+ );
470
 
471
  wp_localize_script(
472
  'string-locator-search',
473
  'string_locator',
474
  array(
475
+ 'rest_nonce' => wp_create_nonce( 'wp_rest' ),
476
  'search_nonce' => wp_create_nonce( 'string-locator-search' ),
477
  'search_current_prefix' => __( 'Next file: ', 'string-locator' ),
478
  'saving_results_string' => __( 'Saving search results&hellip;', 'string-locator' ),
481
  'search_error' => __( 'The above error was returned by your server, for more details please consult your servers error logs.', 'string-locator' ),
482
  'search_no_results' => __( 'Your search was completed, but no results were found.', 'string-locator' ),
483
  'warning_title' => __( 'Warning', 'string-locator' ),
484
+ 'url' => array(
485
+ 'search' => get_rest_url( null, 'string-locator/v1/search' ),
486
+ 'clean' => get_rest_url( null, 'string-locator/v1/clean' ),
487
+ 'directory_structure' => get_rest_url( null, 'string-locator/v1/get-directory-structure' ),
488
+ ),
489
  )
490
  );
491
 
499
  /**
500
  * String Locator Scripts
501
  */
502
+ wp_enqueue_script(
503
+ 'string-locator-editor',
504
+ trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'build/string-locator.js',
505
+ array( 'jquery', 'code-editor', 'wp-util' ),
506
+ $editor['version'],
507
+ true
508
+ );
509
 
510
  wp_localize_script(
511
  'string-locator-editor',
514
  'CodeMirror' => $code_mirror,
515
  'goto_line' => absint( $_GET['string-locator-line'] ),
516
  'goto_linepos' => absint( $_GET['string-locator-linepos'] ),
517
+ 'url' => array(
518
+ 'save' => get_rest_url( null, 'string-locator/v1/save' ),
519
+ ),
520
  )
521
  );
522
  }
569
  return false;
570
  }
571
 
572
+ $include_path = '';
573
+
574
  /**
575
  * Show the edit page if;
576
  * - The edit file path query var is set
578
  * - The edit file path query var does not contains double dots (used to traverse directories)
579
  * - The user is capable of editing files.
580
  */
581
+ if ( isset( $_GET['string-locator-path'] ) && self::is_valid_location( $_GET['string-locator-path'] ) && current_user_can( 'edit_themes' ) ) {
582
+ $include_path = trailingslashit( STRING_LOCATOR_PLUGIN_DIR ) . 'views/editor.php';
583
  } else {
584
+ $include_path = trailingslashit( STRING_LOCATOR_PLUGIN_DIR ) . 'views/search.php';
 
 
 
 
 
 
585
  }
586
 
587
+ $include_path = apply_filters( 'string_locator_view', $include_path );
 
588
 
589
+ if ( ! empty( $include_path ) ) {
590
+ include_once $include_path;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
591
  }
 
 
592
  }
593
 
594
+ function admin_body_class( $class ) {
595
+ if ( isset( $_GET['string-locator-path'] ) && self::is_valid_location( $_GET['string-locator-path'] ) && current_user_can( 'edit_themes' ) ) {
596
+ $class .= ' file-edit-screen';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
597
  }
598
 
599
+ return $class;
600
  }
601
 
602
  /**
617
  }
618
 
619
  /**
620
+ * Check if a file path is valid for editing.
621
  *
622
+ * @param string $path Path to file.
 
 
 
 
 
623
  *
624
+ * @return bool
625
  */
626
+ public static function is_valid_location( $path ) {
627
+ $valid = true;
628
+ $path = str_replace( array( '/' ), array( DIRECTORY_SEPARATOR ), stripslashes( $path ) );
629
+ $abspath = str_replace( array( '/' ), array( DIRECTORY_SEPARATOR ), ABSPATH );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
630
 
631
+ // Check that it is a valid file we are trying to access as well.
632
+ if ( ! file_exists( $path ) ) {
633
+ $valid = false;
 
 
 
 
 
 
 
 
 
 
634
  }
635
 
636
+ if ( empty( $path ) ) {
637
+ $valid = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
638
  }
639
+ if ( stristr( $path, '..' ) ) {
640
+ $valid = false;
641
+ }
642
+ if ( ! stristr( $path, $abspath ) ) {
643
+ $valid = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
644
  }
645
 
646
+ return $valid;
647
  }
648
  }
readme.txt CHANGED
@@ -5,8 +5,8 @@ Plugin URI: http://wordpress.org/plugins/string-locator/
5
  Donate link: https://www.paypal.me/clorith
6
  Tags: text, search, find, syntax, highlight
7
  Requires at least: 4.9
8
- Tested up to: 5.6
9
- Stable tag: 2.4.2
10
  License: GPLv2 or later
11
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
12
 
@@ -45,10 +45,14 @@ When writing your search string, make sure to wrap your search in forward slashe
45
 
46
  == Changelog ==
47
 
48
- = 2.4.2 =
49
- * Fixed the option to restore previous search.
50
- * Fixed respecting text capitalization in previews when doing a non-regex search.
51
- * Changed capability checks, now works on hosts that maintain updates for their users.
 
 
 
 
52
 
53
  = Older entries =
54
  See changelog.txt for the version history
5
  Donate link: https://www.paypal.me/clorith
6
  Tags: text, search, find, syntax, highlight
7
  Requires at least: 4.9
8
+ Tested up to: 5.9
9
+ Stable tag: 2.5.0
10
  License: GPLv2 or later
11
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
12
 
45
 
46
  == Changelog ==
47
 
48
+ = 2.5.0 (2022-02-27) =
49
+ * Fixed a bug where content would have slashes stripped unexpectedly.
50
+ * Improved table spacing on search results.
51
+ * Improved loopback checks to also check admin access.
52
+ * Hardened the search iterator so users can't accidentally perform unexpected directory traversal.
53
+ * Introduced actions and filters in various places to enable extenders, and future enhancements.
54
+ * Moved all ajax requests to dedicated REST endpoints.
55
+ * Refactored file structure.
56
 
57
  = Older entries =
58
  See changelog.txt for the version history
resources/css/string-locator.css DELETED
@@ -1,2 +0,0 @@
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 DELETED
@@ -1 +0,0 @@
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){let r=!1;const 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("");const 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;const 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(let e=0,n=r.length;e<n;e++){const n=r[e];void 0!==n.stringresult&&t("tbody",".tools_page_string-locator").append(o(n))}}))}(r.data.search));const 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("");const 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 DELETED
@@ -1 +0,0 @@
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){let e;if(!1!==string_locator.CodeMirror&&""!==string_locator.CodeMirror){e=wp.codeEditor.initialize("code-editor",string_locator.CodeMirror);const r=wp.template("string-locator-alert");function o(t){const 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){const o=t("#string-locator-notices");return t.post(string_locator.save_url,t(this).serialize()).always((function(e){void 0===e.notices?o.append(r({type:"error",message:e.responseText})):t.each(e.notices,(function(){o.append(r(this))}))})),e.preventDefault(),!1})),o(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",o(e.codemirror))}else e=t("#code-editor"),e.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){"use strict";o.r(e)}]);
 
string-locator.php CHANGED
@@ -3,7 +3,7 @@
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.2
7
  * Author: Clorith
8
  * Author URI: http://www.clorith.net
9
  * Text Domain: string-locator
@@ -25,6 +25,8 @@
25
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26
  */
27
 
 
 
28
  if ( ! defined( 'ABSPATH' ) ) {
29
  die();
30
  }
@@ -32,9 +34,30 @@ if ( ! defined( 'ABSPATH' ) ) {
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();
 
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.5.0
7
  * Author: Clorith
8
  * Author URI: http://www.clorith.net
9
  * Text Domain: string-locator
25
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26
  */
27
 
28
+ namespace JITS\StringLocator;
29
+
30
  if ( ! defined( 'ABSPATH' ) ) {
31
  die();
32
  }
34
  define( 'STRING_LOCATOR_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
35
  define( 'STRING_LOCATOR_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
36
 
37
+ /**
38
+ * Plugin test runners
39
+ */
40
+ require_once __DIR__ . '/includes/Tests/class-loopback.php';
41
+ require_once __DIR__ . '/includes/Tests/class-smart-scan.php';
42
+
43
+ /**
44
+ * Plugin action classes.
45
+ */
46
+ require_once __DIR__ . '/includes/class-save.php';
47
+ require_once __DIR__ . '/includes/class-search.php';
48
+ require_once __DIR__ . '/includes/class-directory-iterator.php';
49
+
50
+ /**
51
+ * Prepare REST endpoints.
52
+ */
53
+ require_once __DIR__ . '/includes/REST/class-base.php';
54
+ require_once __DIR__ . '/includes/REST/class-save.php';
55
+ require_once __DIR__ . '/includes/REST/class-clean.php';
56
+ require_once __DIR__ . '/includes/REST/class-search.php';
57
+ require_once __DIR__ . '/includes/REST/class-directory-structure.php';
58
 
59
  /**
60
  * Instantiate the plugin
61
  */
62
+ require_once __DIR__ . '/includes/class-string-locator.php';
63
+ new String_Locator();
editor.php → views/editor.php RENAMED
@@ -1,9 +1,11 @@
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().
@@ -56,15 +58,11 @@ if ( 'core' === $_GET['file-type'] ) {
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">
@@ -73,7 +71,7 @@ if ( ! $string_locator->failed_edit ) {
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
 
@@ -100,6 +98,12 @@ if ( ! $string_locator->failed_edit ) {
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>
@@ -107,7 +111,7 @@ if ( ! $string_locator->failed_edit ) {
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 } ?>
@@ -124,17 +128,25 @@ if ( ! $string_locator->failed_edit ) {
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">
@@ -153,23 +165,7 @@ if ( ! $string_locator->failed_edit ) {
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
 
@@ -209,7 +205,7 @@ if ( ! $string_locator->failed_edit ) {
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();
@@ -256,6 +252,8 @@ if ( ! $string_locator->failed_edit ) {
256
  </div>
257
  <?php endif; ?>
258
 
 
 
259
  </div>
260
 
261
  </div>
1
  <?php
2
+
3
+ namespace JITS\StringLocator;
4
+
5
  if ( ! defined( 'ABSPATH' ) ) {
6
  die();
7
  }
8
 
 
9
  $editor_content = '';
10
 
11
  // $file is validated in String_Locator::is_valid_location() before this page can be loaded through String_Locator::options_page().
58
  }
59
  }
60
 
61
+ $readfile = fopen( $file, 'r' );
62
+ if ( $readfile ) {
63
+ while ( ( $readline = fgets( $readfile ) ) !== false ) { // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
64
+ $editor_content .= $readline;
 
 
65
  }
 
 
66
  }
67
  ?>
68
  <form id="string-locator-edit-form" class="string-locator-editor-wrapper">
71
  <h1 class="screen-reader-text">
72
  <?php
73
  /* translators: Title on the editor page. */
74
+ esc_html_e( 'String Locator - Editor', 'string-locator' );
75
  ?>
76
  </h1>
77
 
98
 
99
  <div class="string-locator-editor">
100
  <div id="string-locator-notices">
101
+ <div class="row notice notice-error inline below-h2 hide-if-js">
102
+ <p>
103
+ Something about JS being required!
104
+ </p>
105
+ </div>
106
+
107
  <?php if ( isset( $details['parent'] ) && ! $details['parent'] ) { ?>
108
  <div class="row notice notice-warning inline below-h2 is-dismissible">
109
  <p>
111
  </p>
112
 
113
  <p>
114
+ <?php _e( 'When making changes to a theme, it is recommended you make a <a href="https://developer.wordpress.org/themes/advanced-topics/child-themes/">Child Theme</a>.', 'string-locator' ); ?>
115
  </p>
116
  </div>
117
  <?php } ?>
128
  <?php } ?>
129
  </div>
130
 
131
+ <?php
132
+ $editor = '<textarea
133
  name="string-locator-editor-content"
134
+ class="string-locator-editor hide-if-no-js"
135
  id="code-editor"
136
+ data-editor-goto-line="' . esc_attr( $_GET['string-locator-line'] ) . ':' . esc_attr( $_GET['string-locator-linepos'] ) . '"
137
+ data-editor-language=""
138
  autofocus="autofocus"
139
+ >' . esc_html( $editor_content ) . '</textarea>';
140
+
141
+ echo apply_filters( 'string_locator_editor_markup', $editor );
142
+
143
+ ?>
144
  </div>
145
 
146
  <div class="string-locator-sidebar">
147
+
148
+ <?php do_action( 'string_locator_editor_sidebar_start' ); ?>
149
+
150
  <div class="string-locator-panel">
151
  <h2 class="title"><?php esc_html_e( 'Details', 'string-locator' ); ?></h2>
152
  <div class="string-locator-panel-body">
165
  <div class="string-locator-panel">
166
  <h2 class="title"><?php esc_html_e( 'Save checks', 'string-locator' ); ?></h2>
167
  <div class="string-locator-panel-body">
168
+ <?php do_action( 'string_locator_editor_checks' ); ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  </div>
170
  </div>
171
 
205
 
206
  foreach ( $function_info['user'] as $user_func ) {
207
  if ( strstr( $editor_content, $user_func . '(' ) ) {
208
+ $function_object = new \ReflectionFunction( $user_func );
209
  $attrs = $function_object->getParameters();
210
 
211
  $attr_strings = array();
252
  </div>
253
  <?php endif; ?>
254
 
255
+ <?php do_action( 'string_locator_editor_sidebar_end' ); ?>
256
+
257
  </div>
258
 
259
  </div>
search.php → views/search.php RENAMED
@@ -1,4 +1,7 @@
1
  <?php
 
 
 
2
  if ( ! defined( 'ABSPATH' ) ) {
3
  die();
4
  }
@@ -35,6 +38,8 @@ if ( isset( $_GET['restore'] ) ) {
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>
@@ -51,21 +56,11 @@ if ( isset( $_GET['restore'] ) ) {
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>
1
  <?php
2
+
3
+ namespace JITS\StringLocator;
4
+
5
  if ( ! defined( 'ABSPATH' ) ) {
6
  die();
7
  }
38
  <?php esc_html_e( 'String Locator', 'string-locator' ); ?>
39
  </h1>
40
 
41
+ <?php do_action( 'string_locator_view_search_pre_form' ); ?>
42
+
43
  <?php if ( ! current_user_can( 'edit_themes' ) ) : ?>
44
  <div class="notice notice-warning inline">
45
  <p>
56
  <form action="<?php echo esc_url( $this_url ); ?>" method="post" id="string-locator-search-form">
57
  <label for="string-locator-search"><?php esc_html_e( 'Search through', 'string-locator' ); ?></label>
58
  <select name="string-locator-search" id="string-locator-search">
59
+ <?php
60
+ $searchers = apply_filters( 'string_locator_search_sources_markup', '', $search_location );
61
+
62
+ echo $searchers;
63
+ ?>
 
 
 
 
 
 
 
 
 
 
64
  </select>
65
 
66
  <label for="string-locator-string"><?php esc_html_e( 'Search string', 'string-locator' ); ?></label>