Version Description
(2022-07-20) = * Added database search feature. * Added tools for quickly replacing data in the search results. * Added many more filters and actions. * Added hardening of file path checks. * Removed one-time donation notice. * Removed jQuery dependency in favor of vanilla JavaScript code. * Separated search class into a base class for extenders. * Fixed bug with code viewer sizes when resizing your window. * Fixed bug in the list view if special characters were in the search string. * Fixed a bug where RegEx search validation may have a false positive check for invalid patterns. * Fixed missing translator function if Javascript is missing. * Improved capability checks for displaying the search interface when editing is disabled.
Download this release
Release Info
Developer | Clorith |
Plugin | String locator |
Version | 2.6.0 |
Comparing to | |
See all releases |
Code changes from version 2.5.0 to 2.6.0
- build/string-locator-replace.asset.php +1 -0
- build/string-locator-replace.css +1 -0
- build/string-locator-replace.js +1 -0
- build/string-locator-search.asset.php +1 -1
- build/string-locator-search.js +1 -1
- build/string-locator.asset.php +1 -1
- build/string-locator.css +1 -1
- build/string-locator.js +1 -1
- changelog.txt +9 -0
- includes/Base/class-rest.php +34 -0
- includes/Base/class-search.php +142 -0
- includes/Extension/SQL/Tests/class-serialized-data.php +139 -0
- includes/Extension/SQL/class-edit.php +95 -0
- includes/Extension/SQL/class-save.php +145 -0
- includes/Extension/SQL/class-search.php +367 -0
- includes/Extension/SQL/sql.php +16 -0
- includes/Extension/SQL/views/editor/sql.php +251 -0
- includes/Extension/SQL/views/template/search.php +41 -0
- includes/Extension/SearchReplace/REST/class-replace.php +182 -0
- includes/Extension/SearchReplace/Replace/class-file.php +122 -0
- includes/Extension/SearchReplace/Replace/class-sql.php +264 -0
- includes/Extension/SearchReplace/class-replace.php +99 -0
- includes/Extension/SearchReplace/search-replace.php +16 -0
- includes/Extension/SearchReplace/template/error-notice.php +17 -0
- includes/Extension/SearchReplace/views/replace-form.php +55 -0
- includes/REST/class-base.php +0 -17
- includes/REST/class-clean.php +4 -2
- includes/REST/class-directory-structure.php +20 -7
- includes/REST/class-save.php +5 -3
- includes/REST/class-search.php +8 -5
- includes/Tests/class-loopback.php +1 -1
- includes/Tests/class-smart-scan.php +1 -1
- includes/class-directory-iterator.php +2 -2
- includes/class-save.php +4 -4
- includes/class-search.php +6 -156
- includes/class-string-locator.php +107 -88
- readme.txt +20 -14
- string-locator.php +19 -6
- views/assets/instawp-logo.svg +12 -0
- views/{editor.php → editors/default.php} +24 -7
- views/search.php +49 -38
- views/templates/instawp.php +21 -0
- views/templates/search-default.php +41 -0
build/string-locator-replace.asset.php
ADDED
@@ -0,0 +1 @@
|
|
|
1 |
+
<?php return array('dependencies' => array(), 'version' => '1c81e6d76abd42194431');
|
build/string-locator-replace.css
ADDED
@@ -0,0 +1 @@
|
|
|
1 |
+
#string-locator-replace-form{background-color:#fff;border:1px solid #c3c4c7;display:none;padding:10px;width:100%}#string-locator-replace-form.visible{display:block}#string-locator-replace-form h2{margin-top:0}
|
build/string-locator-replace.js
ADDED
@@ -0,0 +1 @@
|
|
|
1 |
+
!function(){"use strict";document.addEventListener("DOMContentLoaded",(function(){const e=document.getElementById("string-locator-replace-new-string"),t=document.getElementById("string-locator-replace-loopback-check"),n=document.getElementById("string-locator-toggle-replace-controls"),r=document.getElementById("string-locator-replace-form"),c=document.getElementById("string-locator-replace-form"),o=document.getElementById("string-locator-search-results-table"),a=document.getElementById("string-locator-search-notices"),l=document.getElementById("string-locator-progress-wrapper"),s=document.getElementById("string-locator-search-progress"),i=document.getElementById("string-locator-feedback-text"),d=document.getElementById("string-locator-string"),g=document.getElementById("string-locator-regex"),p=document.getElementById("string-locator-replace-button-all"),u=document.getElementById("string-locator-replace-button-selected"),m=wp.template("string-locator-replace-error");let y,E,f=!1,h=!1;function b(n){const r=new FormData,c={...y[n].dataset};if(s.value=n,h||y[n].getElementsByClassName("check-column-box")[0].checked){r.append("_wpnonce",stringLocatorReplace.rest_nonce),r.append("replace_nonce",stringLocatorReplace.replace_nonce),r.append("replace_string",e.value),r.append("search_string",d.value),r.append("search_regex",g.checked),r.append("replace_loopback",t.checked);for(const e in c)r.append(e,c[e]);fetch(stringLocatorReplace.url.replace,{method:"POST",body:r}).then((e=>e.json())).then((function(e){null!=e&&e.success?(E=y[n].getElementsByTagName("td")[0],!0!==e.data.replace_string&&(E.innerHTML=e.data.replace_string),n<y.length-1?b(n+1):(l.style.display="none",h=!1)):B(e)})).catch((function(e){B(e)}))}else n<y.length-1?b(n+1):(l.style.display="none",h=!1)}function B(e){a.style.display="block",l.style.display="none",h=!1,a.innerHTML+=m(e)}function L(){if(!f)return p.dispatchEvent(new Event("click")),!1;y=o.getElementsByTagName("tbody")[0].getElementsByTagName("tr"),l.style.display="block",i.innerText=stringLocatorReplace.string.replace_started,a.innerHTML="",s.value=0,s.setAttribute("max",y.length),f=!1,b(0)}c.addEventListener("submit",(function(e){return e.preventDefault(),L(),!1})),p.addEventListener("click",(function(){if(!confirm(stringLocatorReplace.string.confirm_all))return!1;h=!0,f=!0,L()})),u.addEventListener("click",(function(){h=!1,f=!0,L()})),n.addEventListener("click",(function(){r.classList.toggle("visible"),r.classList.contains("visible")?(n.setAttribute("aria-expanded","true"),n.innerText=stringLocatorReplace.string.button_hide):(n.setAttribute("aria-expanded","false"),n.innerText=stringLocatorReplace.string.button_show)}))}))}();
|
build/string-locator-search.asset.php
CHANGED
@@ -1 +1 @@
|
|
1 |
-
<?php return array('dependencies' => array(), 'version' => '
|
1 |
+
<?php return array('dependencies' => array(), 'version' => 'd53f3966489b06eec2a1');
|
build/string-locator-search.js
CHANGED
@@ -1 +1 @@
|
|
1 |
-
|
1 |
+
document.addEventListener("DOMContentLoaded",(function(){let t,e,n=!1,r="";const a=document.getElementById("string-locator-search-notices"),o=document.getElementById("string-locator-progress-wrapper"),c=document.getElementById("string-locator-search-progress"),s=document.getElementById("string-locator-feedback-text"),i=document.getElementById("string-locator-search-form"),l=document.getElementById("string-locator-search"),d=document.getElementById("string-locator-string"),u=document.getElementById("string-locator-regex"),g=document.getElementById("string-locator-search-results-table-wrapper"),_=document.getElementById("string-locator-search-results-table"),h=document.getElementsByTagName("tbody")[0];function m(t,e,n){a.innerHTML+='<div class="notice notice-'+n+' is-dismissible"><p><strong>'+t+"</strong><br />"+e+"</p></div>"}function p(t,e){n=!1,o.style.display="none",m(t,e,"error")}function y(){a.innerHTML="",c.removeAttribute("value"),h.innerHTML=""}function f(a,i){if(e=new FormData,i>=a||!n)return s.innerHTML=string_locator.saving_results_string,n=!1,e=new FormData,s.innerText="",e.append("_wpnonce",string_locator.rest_nonce),fetch(string_locator.url.clean,{method:"POST",body:e}).then((function(){o.style.display="none",h.getElementsByTagName("tr").length<1&&(h.innerHTML='<tr><td colspan="3">'+string_locator.search_no_results+"</td></tr>")})).catch((function(t){p(t,string_locator.search_error)})),!1;e.append("filenum",i),e.append("_wpnonce",string_locator.rest_nonce),fetch(string_locator.url.search,{method:"POST",body:e}).then((t=>t.json())).then((function(e){if(!e.success){if(!1===e.data.continue)return p(string_locator.warning_title,e.data.message),!1;m(string_locator.warning_title,e.data.message,"warning")}void 0!==e.data.search&&(c.value=e.data.filenum,s.innerHTML=string_locator.search_current_prefix+e.data.next_file,r=void 0!==e.data.type?e.data.type:"",function(e){if(Array!==e.constructor)return!1;t=wp.template("string-locator-search-result"+(""!==r?"-"+r:"")),e.forEach((function(e){if(e)for(let n=0,r=e.length;n<r;n++){const r=e[n];void 0!==r.stringresult&&(h.innerHTML+=t(r))}}))}(e.data.search));const n=e.data.filenum+1;f(a,n)})).catch((function(t){p(t,string_locator.search_error)}))}i.addEventListener("submit",(function(t){if(t.preventDefault(),"sql"===l.value)return void function(t){t.preventDefault(),e=new FormData,s.innerText=string_locator.search_preparing,o.style.display="block",n=!0,y();const r=JSON.stringify({directory:l.value,search:d.value,regex:u.checked});_.style.display="table",g.style.display="block",e.append("data",r),e.append("_wpnonce",string_locator.rest_nonce),fetch(string_locator.url.directory_structure,{method:"POST",body:e}).then((t=>t.json())).then((function(t){t.success?(c.setAttribute("max",t.data.total),c.value=t.data.current,s.innerText=string_locator.search_started,f(t.data.total,0)):m("",t.data,"alert")})).catch((function(t){p(t,string_locator.search_error)}))}(t);e=new FormData,s.innerText=string_locator.search_preparing,o.style.display="block",n=!0,y();const r=JSON.stringify({directory:l.value,search:d.value,regex:u.checked});_.style.display="table",g.style.display="block",e.append("data",r),e.append("_wpnonce",string_locator.rest_nonce),fetch(string_locator.url.directory_structure,{method:"POST",body:e}).then((t=>t.json())).then((function(t){t.success?(c.setAttribute("max",t.data.total),c.value=t.data.current,s.innerText=string_locator.search_started,f(t.data.total,0)):m("",t.data,"alert")})).catch((function(t){p(t,string_locator.search_error)}))}))}));
|
build/string-locator.asset.php
CHANGED
@@ -1 +1 @@
|
|
1 |
-
<?php return array('dependencies' => array(), 'version' => '
|
1 |
+
<?php return array('dependencies' => array(), 'version' => 'c39f5990e5961dddae02');
|
build/string-locator.css
CHANGED
@@ -1 +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}
|
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}#string-locator-search-results-table-wrapper{display:none}#string-locator-search-results-table-wrapper.restore{display:block}#string-locator-search-results-table-wrapper .tablenav{height:auto;min-height:30px}table.tools_page_string-locator{display:none}table.tools_page_string-locator.restore{display:table}table thead .manage-column.check-column{padding:11px 0 10px 3px}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
CHANGED
@@ -1 +1 @@
|
|
1 |
-
!function(){"use strict";
|
1 |
+
!function(){"use strict";document.addEventListener("DOMContentLoaded",(function(){let t,e;if(!1!==string_locator.CodeMirror&&""!==string_locator.CodeMirror){t=wp.codeEditor.initialize("code-editor",string_locator.CodeMirror);const o=wp.template("string-locator-alert");function n(){const e=Math.max(document.documentElement.clientHeight,window.innerHeight||0)-89;t.codemirror.setSize(null,parseInt(e))}document.addEventListener("click",(function(e){const o=e.target;o.classList.contains("string-locator-edit-goto")&&(e.preventDefault(),t.codemirror.scrollIntoView(parseInt(o.dataset.gotoLine)),t.codemirror.setCursor(parseInt(o.dataset.gotoLine-1),o.dataset.gotoLinepos))})),document.getElementById("string-locator-edit-form").addEventListener("submit",(function(t){const n=document.getElementById("string-locator-notices");return e=new FormData(this),fetch(string_locator.url.save,{method:"POST",body:e}).then((t=>t.json())).then((function(t){void 0===t.notices?n.innerHTML+=o({type:"error",message:t.responseText}):t.notices.forEach((function(t){n.innerHTML+=o(t)}))})),t.preventDefault(),!1})),n(),t.codemirror.scrollIntoView(parseInt(string_locator.goto_line)),t.codemirror.setCursor(parseInt(string_locator.goto_line-1),parseInt(string_locator.goto_linepos)),window.onresize=n}else t=document.getElementById("code-editor"),t.style.width=document.getElementsByClassName("string-locator-edit-wrap")[0].offsetWidth,t.style.height=parseInt(Math.max(document.documentElement.clientHeight,window.innerHeight||0)-89)}))}();
|
changelog.txt
CHANGED
@@ -1,3 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
1 |
+
= 2.5.0 (2022-02-27) =
|
2 |
+
* Fixed a bug where content would have slashes stripped unexpectedly.
|
3 |
+
* Improved table spacing on search results.
|
4 |
+
* Improved loopback checks to also check admin access.
|
5 |
+
* Hardened the search iterator so users can't accidentally perform unexpected directory traversal.
|
6 |
+
* Introduced actions and filters in various places to enable extenders, and future enhancements.
|
7 |
+
* Moved all ajax requests to dedicated REST endpoints.
|
8 |
+
* Refactored file structure.
|
9 |
+
|
10 |
= 2.4.2 =
|
11 |
* Fixed the option to restore previous search.
|
12 |
* Fixed respecting text capitalization in previews when doing a non-regex search.
|
includes/Base/class-rest.php
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Base class for all REST API controllers.
|
4 |
+
*/
|
5 |
+
|
6 |
+
namespace StringLocator\Base;
|
7 |
+
|
8 |
+
use StringLocator\String_Locator;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Base REST class.
|
12 |
+
*/
|
13 |
+
class REST extends \WP_REST_Controller {
|
14 |
+
|
15 |
+
protected $namespace = 'string-locator/v1';
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Class constructor.
|
19 |
+
*/
|
20 |
+
public function __construct() {
|
21 |
+
add_action( 'rest_api_init', array( $this, 'register_rest_route' ) );
|
22 |
+
}
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Generic helper function to check if the current user
|
26 |
+
* meets the minimum access requirements of the plugin.
|
27 |
+
*
|
28 |
+
* @return bool
|
29 |
+
*/
|
30 |
+
public function permission_callback() {
|
31 |
+
return current_user_can( String_Locator::$default_capability );
|
32 |
+
}
|
33 |
+
|
34 |
+
}
|
includes/Base/class-search.php
ADDED
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Base class for handling Search requests.
|
4 |
+
*/
|
5 |
+
|
6 |
+
namespace StringLocator\Base;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Search class.
|
10 |
+
*/
|
11 |
+
class Search {
|
12 |
+
/**
|
13 |
+
* The server-configured max time a script can run.
|
14 |
+
*
|
15 |
+
* @var int
|
16 |
+
*/
|
17 |
+
protected $max_execution_time = null;
|
18 |
+
|
19 |
+
/**
|
20 |
+
* The current time when our script started executing.
|
21 |
+
*
|
22 |
+
* @var float
|
23 |
+
*/
|
24 |
+
protected $start_execution_timer = 0;
|
25 |
+
|
26 |
+
/**
|
27 |
+
* The server-configured max amount of memory a script can use.
|
28 |
+
*
|
29 |
+
* @var int
|
30 |
+
*/
|
31 |
+
protected $max_memory_consumption = 0;
|
32 |
+
|
33 |
+
/**
|
34 |
+
* The path to the currently editable file.
|
35 |
+
*
|
36 |
+
* @var string
|
37 |
+
*/
|
38 |
+
protected $path_to_use = '';
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Class constructor.
|
42 |
+
*/
|
43 |
+
public function __construct() {
|
44 |
+
/**
|
45 |
+
* Define class variables requiring expressions
|
46 |
+
*/
|
47 |
+
$this->path_to_use = ( is_multisite() ? 'network/admin.php' : 'tools.php' );
|
48 |
+
|
49 |
+
$this->max_execution_time = absint( ini_get( 'max_execution_time' ) );
|
50 |
+
$this->start_execution_timer = microtime( true );
|
51 |
+
|
52 |
+
if ( $this->max_execution_time > 30 ) {
|
53 |
+
$this->max_execution_time = 30;
|
54 |
+
}
|
55 |
+
|
56 |
+
$this->set_memory_limit();
|
57 |
+
|
58 |
+
add_action( 'string_locator_search_templates', array( $this, 'add_search_response_template' ) );
|
59 |
+
}
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Load an underscores template file to be used in the search response.
|
63 |
+
*
|
64 |
+
* @return void
|
65 |
+
*/
|
66 |
+
public function add_search_response_template() {
|
67 |
+
require_once STRING_LOCATOR_PLUGIN_DIR . '/views/templates/search-default.php';
|
68 |
+
}
|
69 |
+
|
70 |
+
/**
|
71 |
+
* Sets up the memory limit variables.
|
72 |
+
*
|
73 |
+
* @return void
|
74 |
+
* @since 2.0.0
|
75 |
+
*
|
76 |
+
*/
|
77 |
+
function set_memory_limit() {
|
78 |
+
$memory_limit = ini_get( 'memory_limit' );
|
79 |
+
|
80 |
+
$this->max_memory_consumption = absint( $memory_limit );
|
81 |
+
|
82 |
+
if ( strstr( $memory_limit, 'k' ) ) {
|
83 |
+
$this->max_memory_consumption = ( str_replace( 'k', '', $memory_limit ) * 1000 );
|
84 |
+
}
|
85 |
+
if ( strstr( $memory_limit, 'M' ) ) {
|
86 |
+
$this->max_memory_consumption = ( str_replace( 'M', '', $memory_limit ) * 1000000 );
|
87 |
+
}
|
88 |
+
if ( strstr( $memory_limit, 'G' ) ) {
|
89 |
+
$this->max_memory_consumption = ( str_replace( 'G', '', $memory_limit ) * 1000000000 );
|
90 |
+
}
|
91 |
+
}
|
92 |
+
|
93 |
+
/**
|
94 |
+
* Check if the script is about to exceed the max execution time.
|
95 |
+
*
|
96 |
+
* @return bool
|
97 |
+
* @since 1.9.0
|
98 |
+
*
|
99 |
+
*/
|
100 |
+
function nearing_execution_limit() {
|
101 |
+
// Max execution time is 0 or -1 (infinite) in server config
|
102 |
+
if ( 0 === $this->max_execution_time || - 1 === $this->max_execution_time ) {
|
103 |
+
return false;
|
104 |
+
}
|
105 |
+
|
106 |
+
$back_compat_filter = apply_filters( 'string-locator-extra-search-delay', 2 ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
|
107 |
+
|
108 |
+
$built_in_delay = apply_filters( 'string_locator_extra_search_delay', $back_compat_filter );
|
109 |
+
$execution_time = ( microtime( true ) - $this->start_execution_timer + $built_in_delay );
|
110 |
+
|
111 |
+
if ( $execution_time >= $this->max_execution_time ) {
|
112 |
+
return $execution_time;
|
113 |
+
}
|
114 |
+
|
115 |
+
return false;
|
116 |
+
}
|
117 |
+
|
118 |
+
/**
|
119 |
+
* Check if the script is about to exceed the server memory limit.
|
120 |
+
*
|
121 |
+
* @return bool
|
122 |
+
* @since 2.0.0
|
123 |
+
*
|
124 |
+
*/
|
125 |
+
function nearing_memory_limit() {
|
126 |
+
// Check if the memory limit is set t o0 or -1 (infinite) in server config
|
127 |
+
if ( 0 === $this->max_memory_consumption || - 1 === $this->max_memory_consumption ) {
|
128 |
+
return false;
|
129 |
+
}
|
130 |
+
|
131 |
+
// We give our selves a 256k memory buffer, as we need to close off the script properly as well
|
132 |
+
$back_compat_filter = apply_filters( 'string-locator-extra-memory-buffer', 256000 ); //phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
|
133 |
+
$built_in_buffer = apply_filters( 'string_locator_extra_memory_buffer', $back_compat_filter );
|
134 |
+
$memory_use = ( memory_get_usage( true ) + $built_in_buffer );
|
135 |
+
|
136 |
+
if ( $memory_use >= $this->max_memory_consumption ) {
|
137 |
+
return $memory_use;
|
138 |
+
}
|
139 |
+
|
140 |
+
return false;
|
141 |
+
}
|
142 |
+
}
|
includes/Extension/SQL/Tests/class-serialized-data.php
ADDED
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Class for testing serialized data on SQL saves.
|
4 |
+
*/
|
5 |
+
|
6 |
+
namespace StringLocator\Extensions\SQL\Tests;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Serialized_Data class.
|
10 |
+
*/
|
11 |
+
class Serialized_Data {
|
12 |
+
|
13 |
+
/**
|
14 |
+
* The content that will be scanned.
|
15 |
+
*
|
16 |
+
* @var string
|
17 |
+
*/
|
18 |
+
private $content = '';
|
19 |
+
|
20 |
+
/**
|
21 |
+
* An array holding any errors returned during testing.
|
22 |
+
*
|
23 |
+
* @var array
|
24 |
+
*/
|
25 |
+
public $errors = array();
|
26 |
+
|
27 |
+
/**
|
28 |
+
* SmartScan constructor.
|
29 |
+
*/
|
30 |
+
public function __construct() {
|
31 |
+
add_action( 'string_locator_editor_checks', array( $this, 'print_checks_option' ) );
|
32 |
+
|
33 |
+
add_filter( 'string_locator_pre_save', array( $this, 'maybe_perform_test' ), 10, 2 );
|
34 |
+
add_filter( 'string_locator_pre_save_fail_notice', array( $this, 'return_failure_notices' ) );
|
35 |
+
}
|
36 |
+
|
37 |
+
/**
|
38 |
+
* Combine reported errors with any new notices.
|
39 |
+
*
|
40 |
+
* @param $notices
|
41 |
+
*
|
42 |
+
* @return mixed
|
43 |
+
*/
|
44 |
+
public function return_failure_notices( $notices ) {
|
45 |
+
if ( empty( $this->errors ) ) {
|
46 |
+
return $notices;
|
47 |
+
}
|
48 |
+
|
49 |
+
return array_merge(
|
50 |
+
$notices,
|
51 |
+
$this->errors
|
52 |
+
);
|
53 |
+
}
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Conditionally run the test tool.
|
57 |
+
*
|
58 |
+
* @param bool $can_save A boolean value if the test has passed or failed.
|
59 |
+
* @param string $content The content being modified.
|
60 |
+
*
|
61 |
+
* @return bool|mixed|null
|
62 |
+
*/
|
63 |
+
public function maybe_perform_test( $can_save, $content ) {
|
64 |
+
// If another addon has determined the file can not be saved, bail early.
|
65 |
+
if ( ! $can_save ) {
|
66 |
+
return $can_save;
|
67 |
+
}
|
68 |
+
|
69 |
+
// Do not perform a smart scan if the option for it is disabled.
|
70 |
+
if ( ! isset( $_POST['string-locator-validate-serialized-data'] ) ) {
|
71 |
+
return $can_save;
|
72 |
+
}
|
73 |
+
|
74 |
+
return $this->run( $content );
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Output a checkbox to enable or disable this feature from within the editor interface.
|
79 |
+
*
|
80 |
+
* @return void
|
81 |
+
*/
|
82 |
+
public function print_checks_option() {
|
83 |
+
if ( ! isset( $_GET['file-type'] ) || 'sql' !== $_GET['file-type'] ) {
|
84 |
+
return;
|
85 |
+
}
|
86 |
+
?>
|
87 |
+
|
88 |
+
<div class="row">
|
89 |
+
<label>
|
90 |
+
<input type="checkbox" name="string-locator-validate-serialized-data" checked="checked">
|
91 |
+
<?php esc_html_e( 'If the SQL data is serialized, validate it before saving.', 'string-locator' ); ?>
|
92 |
+
</label>
|
93 |
+
</div>
|
94 |
+
|
95 |
+
<?php
|
96 |
+
}
|
97 |
+
|
98 |
+
/**
|
99 |
+
* A helper function to return any errors.
|
100 |
+
*
|
101 |
+
* @return array
|
102 |
+
*/
|
103 |
+
public function get_errors() {
|
104 |
+
return $this->errors;
|
105 |
+
}
|
106 |
+
|
107 |
+
/**
|
108 |
+
* Main test runner.
|
109 |
+
*
|
110 |
+
* @param string $content The content to scan.
|
111 |
+
*
|
112 |
+
* @return bool
|
113 |
+
*/
|
114 |
+
public function run( $content ) {
|
115 |
+
$this->content = $content;
|
116 |
+
|
117 |
+
// Reset the stored errors for a fresh run.
|
118 |
+
$this->errors = array();
|
119 |
+
|
120 |
+
if ( \is_serialized( $this->content ) ) {
|
121 |
+
$test_data = @unserialize( $this->content );
|
122 |
+
|
123 |
+
if ( 'b:0;' !== $this->content && false === $test_data ) {
|
124 |
+
$this->errors[] = array(
|
125 |
+
'type' => 'error',
|
126 |
+
'message' => __( 'The data is meant to be serialized with PHP, but is no longer valid.', 'string-locator' ),
|
127 |
+
);
|
128 |
+
}
|
129 |
+
}
|
130 |
+
|
131 |
+
if ( ! empty( $this->errors ) ) {
|
132 |
+
return false;
|
133 |
+
}
|
134 |
+
|
135 |
+
return true;
|
136 |
+
}
|
137 |
+
}
|
138 |
+
|
139 |
+
new Serialized_Data();
|
includes/Extension/SQL/class-edit.php
ADDED
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Class to handle the edit page.
|
4 |
+
*/
|
5 |
+
|
6 |
+
namespace StringLocator\Extension\SQL;
|
7 |
+
|
8 |
+
use StringLocator\String_Locator;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Edit class.
|
12 |
+
*/
|
13 |
+
class Edit {
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Class constructor.
|
17 |
+
*/
|
18 |
+
public function __construct() {
|
19 |
+
add_filter( 'string_locator_view', array( $this, 'sql_edit_page' ) );
|
20 |
+
|
21 |
+
add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
|
22 |
+
add_filter( 'string_locator_editor_fields', array( $this, 'editor_form_fields' ) );
|
23 |
+
}
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Generate form fields that need ot be a part of the editor interface for this data type.
|
27 |
+
*
|
28 |
+
* @param array $fields An array of form fields to output in hidden elements.
|
29 |
+
*
|
30 |
+
* @return array
|
31 |
+
*/
|
32 |
+
public function editor_form_fields( $fields ) {
|
33 |
+
if ( isset( $_GET['file-type'] ) && 'sql' === $_GET['file-type'] ) {
|
34 |
+
$fields = array_merge(
|
35 |
+
array(
|
36 |
+
'sql-column' => $_GET['sql-column'],
|
37 |
+
'sql-table' => $_GET['sql-table'],
|
38 |
+
'sql-primary-column' => $_GET['sql-primary-column'],
|
39 |
+
'sql-primary-type' => $_GET['sql-primary-type'],
|
40 |
+
'sql-primary-key' => $_GET['sql-primary-key'],
|
41 |
+
),
|
42 |
+
$fields
|
43 |
+
);
|
44 |
+
}
|
45 |
+
|
46 |
+
return $fields;
|
47 |
+
}
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Append a helper class ot the wp-admin body class.
|
51 |
+
*
|
52 |
+
* @param string $class The classes for the admin body class.
|
53 |
+
*
|
54 |
+
* @return string
|
55 |
+
*/
|
56 |
+
public function admin_body_class( $class ) {
|
57 |
+
if ( isset( $_GET['file-type'] ) && 'sql' === $_GET['file-type'] && current_user_can( String_Locator::$default_capability ) ) {
|
58 |
+
$class .= ' file-edit-screen';
|
59 |
+
}
|
60 |
+
|
61 |
+
return $class;
|
62 |
+
}
|
63 |
+
|
64 |
+
/**
|
65 |
+
* Conditionally filter the editor interface for SQL files.
|
66 |
+
*
|
67 |
+
* @param string $include_path The path to the editor interface.
|
68 |
+
*
|
69 |
+
* @return string
|
70 |
+
*/
|
71 |
+
public function sql_edit_page( $include_path ) {
|
72 |
+
if ( ! isset( $_GET['file-type'] ) || 'sql' !== $_GET['file-type'] || ! current_user_can( String_Locator::$default_capability ) ) {
|
73 |
+
return $include_path;
|
74 |
+
}
|
75 |
+
|
76 |
+
// Validate the table name.
|
77 |
+
if ( ! isset( $_GET['sql-table'] ) || ! validate_sql_fields( $_GET['sql-table'] ) ) {
|
78 |
+
return $include_path;
|
79 |
+
}
|
80 |
+
|
81 |
+
// Validate the primary column
|
82 |
+
if ( ! isset( $_GET['sql-primary-column'] ) || ! validate_sql_fields( $_GET['sql-primary-column'] ) ) {
|
83 |
+
return $include_path;
|
84 |
+
}
|
85 |
+
|
86 |
+
// A primary key needs to be provided, this could be anything so we just make sure it is set and not empty.
|
87 |
+
if ( ! isset( $_GET['sql-primary-key'] ) || empty( $_GET['sql-primary-key'] ) ) {
|
88 |
+
return $include_path;
|
89 |
+
}
|
90 |
+
|
91 |
+
return STRING_LOCATOR_PLUGIN_DIR . '/includes/Extension/SQL/views/editor/sql.php';
|
92 |
+
}
|
93 |
+
}
|
94 |
+
|
95 |
+
new Edit();
|
includes/Extension/SQL/class-save.php
ADDED
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace StringLocator\Extension\SQL;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Save class.
|
7 |
+
*/
|
8 |
+
class Save {
|
9 |
+
|
10 |
+
private $override_save = false;
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Class constructor.
|
14 |
+
*/
|
15 |
+
public function __construct() {
|
16 |
+
add_filter( 'string_locator_save_params', array( $this, 'check_save_parameters' ) );
|
17 |
+
add_filter( 'string_locator_save_handler', array( $this, 'maybe_handle_save' ) );
|
18 |
+
}
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Check the save parameters to determine if the SQL handler should take over the save request.
|
22 |
+
*
|
23 |
+
* @param array $parameters An array of REST API request parameters.
|
24 |
+
*
|
25 |
+
* @return array
|
26 |
+
*/
|
27 |
+
public function check_save_parameters( $parameters ) {
|
28 |
+
if ( isset( $parameters['file-type'] ) && 'sql' === $parameters['file-type'] ) {
|
29 |
+
$this->override_save = true;
|
30 |
+
}
|
31 |
+
|
32 |
+
return $parameters;
|
33 |
+
}
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Override the save handler if the parameters indicate that the SQL handler should take over.
|
37 |
+
*
|
38 |
+
* @param mixed $handler The class handling the save request.
|
39 |
+
*
|
40 |
+
* @return self|mixed
|
41 |
+
*/
|
42 |
+
public function maybe_handle_save( $handler ) {
|
43 |
+
if ( ! $this->override_save ) {
|
44 |
+
return $handler;
|
45 |
+
}
|
46 |
+
|
47 |
+
return $this;
|
48 |
+
}
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Funciton to trigger the save behavior.
|
52 |
+
*
|
53 |
+
* @param array $params An array of save parameters.
|
54 |
+
*
|
55 |
+
* @return array|\array[][]
|
56 |
+
*/
|
57 |
+
public function save( $params ) {
|
58 |
+
global $wpdb;
|
59 |
+
|
60 |
+
$content = $params['string-locator-editor-content'];
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Filter if the save process should be performed or not.
|
64 |
+
*
|
65 |
+
* @attr bool $can_save Can the save be carried out.
|
66 |
+
* @attr string $content The content to save.
|
67 |
+
* @attr string $path Path to the file being edited.
|
68 |
+
*/
|
69 |
+
$can_save = apply_filters( 'string_locator_pre_save', true, $content, 'sql' );
|
70 |
+
|
71 |
+
if ( ! $can_save ) {
|
72 |
+
return array(
|
73 |
+
'notices' => apply_filters( 'string_locator_pre_save_fail_notice', array() ),
|
74 |
+
);
|
75 |
+
}
|
76 |
+
|
77 |
+
if ( 'int' === $params['sql-primary-type'] ) {
|
78 |
+
$original = $wpdb->get_var(
|
79 |
+
$wpdb->prepare(
|
80 |
+
'SELECT ' . $params['sql-column'] . ' FROM ' . $params['sql-table'] . ' WHERE ' . $params['sql-primary-column'] . ' = %d LIMIT 1', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- It is not possible to prepare a table or column name, but these are instead validated in `/includes/Search/class-sql.php` before reaching this point.
|
81 |
+
$params['sql-primary-key']
|
82 |
+
)
|
83 |
+
);
|
84 |
+
} else {
|
85 |
+
$original = $wpdb->get_var(
|
86 |
+
$wpdb->prepare(
|
87 |
+
'SELECT ' . $params['sql-column'] . ' FROM ' . $params['sql-table'] . ' WHERE ' . $params['sql-primary-column'] . ' = %s LIMIT 1', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- It is not possible to prepare a table or column name, but these are instead validated in `/includes/Search/class-sql.php` before reaching this point.
|
88 |
+
$params['sql-primary-key']
|
89 |
+
)
|
90 |
+
);
|
91 |
+
}
|
92 |
+
|
93 |
+
$wpdb->update(
|
94 |
+
$params['sql-table'],
|
95 |
+
array(
|
96 |
+
$params['sql-column'] => $content,
|
97 |
+
),
|
98 |
+
array(
|
99 |
+
$params['sql-primary-column'] => $params['sql-primary-key'],
|
100 |
+
)
|
101 |
+
);
|
102 |
+
|
103 |
+
/**
|
104 |
+
* Filter if the save process completed as it should or if warnings should be returned.
|
105 |
+
*
|
106 |
+
* @attr bool $save_successful Boolean indicating if the save was successful.
|
107 |
+
* @attr string $content The edited content.
|
108 |
+
* @attr string $original The original content.
|
109 |
+
* @attr string $path The path to the file being edited.
|
110 |
+
*/
|
111 |
+
$save_successful = apply_filters( 'string_locator_post_save', true, $content, $original, 'sql' );
|
112 |
+
|
113 |
+
/**
|
114 |
+
* Check the status of the site after making our edits.
|
115 |
+
* If the site fails, revert the changes to return the sites to its original state
|
116 |
+
*/
|
117 |
+
if ( ! $save_successful ) {
|
118 |
+
$wpdb->update(
|
119 |
+
$params['sql-table'],
|
120 |
+
array(
|
121 |
+
$params['sql-column'] => $original,
|
122 |
+
),
|
123 |
+
array(
|
124 |
+
$params['sql-primary-column'] => $params['sql-primary-key'],
|
125 |
+
)
|
126 |
+
);
|
127 |
+
|
128 |
+
return array(
|
129 |
+
'notices' => apply_filters( 'string_locator_post_save_fail_notice', array() ),
|
130 |
+
);
|
131 |
+
}
|
132 |
+
|
133 |
+
return array(
|
134 |
+
'notices' => array(
|
135 |
+
array(
|
136 |
+
'type' => 'success',
|
137 |
+
'message' => __( 'The database entry has been updated.', 'string-locator' ),
|
138 |
+
),
|
139 |
+
),
|
140 |
+
);
|
141 |
+
}
|
142 |
+
|
143 |
+
}
|
144 |
+
|
145 |
+
new Save();
|
includes/Extension/SQL/class-search.php
ADDED
@@ -0,0 +1,367 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace StringLocator\Extension\SQL;
|
4 |
+
|
5 |
+
use StringLocator\Base\Search as SearchBase;
|
6 |
+
use StringLocator\String_Locator;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Search class.
|
10 |
+
*/
|
11 |
+
class Search extends SearchBase {
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Class constructor.
|
15 |
+
*/
|
16 |
+
public function __construct() {
|
17 |
+
add_filter( 'string_locator_search_sources_markup', array( $this, 'add_search_options' ), 11, 2 );
|
18 |
+
|
19 |
+
add_filter( 'string_locator_search_handler', array( $this, 'maybe_perform_sql_search' ), 10, 2 );
|
20 |
+
add_filter( 'string_locator_directory_iterator_short_circuit', array( $this, 'maybe_short_circuit_directory_iterator' ), 10, 2 );
|
21 |
+
|
22 |
+
add_filter( 'string_locator_restore_search_row', array( $this, 'restore_sql_search' ), 10, 2 );
|
23 |
+
|
24 |
+
parent::__construct();
|
25 |
+
}
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Handle markup output when restoring a search result.
|
29 |
+
*
|
30 |
+
* @param string $row The result row.
|
31 |
+
* @param object $item The search result item for this row.
|
32 |
+
*
|
33 |
+
* @return string
|
34 |
+
*/
|
35 |
+
public function restore_sql_search( $row, $item ) {
|
36 |
+
if ( ! isset( $item->primary_key ) ) {
|
37 |
+
return $row;
|
38 |
+
}
|
39 |
+
|
40 |
+
$row = sprintf(
|
41 |
+
'<tr data-type="sql" data-primary-key="%d" data-primary-column="%s" data-primary-type="%s" data-table-name="%s" data-column-name="%s">
|
42 |
+
<th scope="row" class="check-column">
|
43 |
+
<input type="checkbox" name="string-locator-replace-checked[]" class="check-column-box">
|
44 |
+
</th>
|
45 |
+
<td>
|
46 |
+
%s
|
47 |
+
<div class="row-actions">
|
48 |
+
%s
|
49 |
+
</div>
|
50 |
+
</td>
|
51 |
+
<td>
|
52 |
+
%s
|
53 |
+
</td>
|
54 |
+
<td>
|
55 |
+
%d
|
56 |
+
</td>
|
57 |
+
<td>
|
58 |
+
%d
|
59 |
+
</td>
|
60 |
+
</tr>',
|
61 |
+
esc_attr( $item->primary_key ),
|
62 |
+
esc_attr( $item->primary_column ),
|
63 |
+
esc_attr( $item->primary_type ),
|
64 |
+
esc_attr( $item->table ),
|
65 |
+
esc_attr( $item->column ),
|
66 |
+
$item->stringresult,
|
67 |
+
( ! current_user_can( String_Locator::$default_capability ) ? '' : sprintf(
|
68 |
+
'<span class="edit"><a href="%1$s" aria-label="%2$s">%2$s</a></span>',
|
69 |
+
esc_url( $item->editurl ),
|
70 |
+
// translators: The row-action edit link label.
|
71 |
+
esc_html__( 'Edit', 'string-locator' )
|
72 |
+
) ),
|
73 |
+
( ! current_user_can( String_Locator::$default_capability ) ? $item->filename : sprintf(
|
74 |
+
'<a href="%s">%s</a>',
|
75 |
+
esc_url( $item->editurl ),
|
76 |
+
esc_html( $item->filename )
|
77 |
+
) ),
|
78 |
+
esc_html( $item->primary_key ),
|
79 |
+
esc_html( $item->linepos )
|
80 |
+
);
|
81 |
+
|
82 |
+
return $row;
|
83 |
+
}
|
84 |
+
|
85 |
+
/**
|
86 |
+
* Add SQL items as search options.
|
87 |
+
*
|
88 |
+
* @param string $searchers The markup for the existing search options.
|
89 |
+
* @param string $search_location The currently selected search option, when restoring a search.
|
90 |
+
*
|
91 |
+
* @return string
|
92 |
+
*/
|
93 |
+
public function add_search_options( $searchers, $search_location ) {
|
94 |
+
ob_start();
|
95 |
+
?>
|
96 |
+
|
97 |
+
<optgroup label="<?php esc_attr_e( 'Database', 'string-locator' ); ?>">
|
98 |
+
<option value="sql"<?php echo ( 'sql' === $search_location ? ' selected="selected"' : '' ); ?>><?php esc_html_e( 'All database tables', 'string-locator' ); ?></option>
|
99 |
+
</optgroup>
|
100 |
+
|
101 |
+
<?php
|
102 |
+
|
103 |
+
$searchers .= ob_get_clean();
|
104 |
+
|
105 |
+
return $searchers;
|
106 |
+
}
|
107 |
+
|
108 |
+
/**
|
109 |
+
* Output the underscores template used for SQL search results.
|
110 |
+
*
|
111 |
+
* @return void
|
112 |
+
*/
|
113 |
+
public function add_search_response_template() {
|
114 |
+
require_once STRING_LOCATOR_PLUGIN_DIR . '/includes/Extension/SQL/views/template/search.php';
|
115 |
+
}
|
116 |
+
|
117 |
+
/**
|
118 |
+
* Allow the SQL search to ignore the DirectoryIterator request used to build search bases for files.
|
119 |
+
*
|
120 |
+
* @param bool $short_circuit Whether to short-circuit the DirectoryIterator request.
|
121 |
+
* @param \WP_REST_Response $request The WP_REST_Request for this API call.
|
122 |
+
*
|
123 |
+
* @return array
|
124 |
+
*/
|
125 |
+
public function maybe_short_circuit_directory_iterator( $short_circuit, $request ) {
|
126 |
+
$data = json_decode( $request->get_param( 'data' ) );
|
127 |
+
|
128 |
+
if ( 'sql' === $data->directory ) {
|
129 |
+
$store = (object) array(
|
130 |
+
'type' => 'sql',
|
131 |
+
'search' => $data->search,
|
132 |
+
'directory' => 'sql',
|
133 |
+
'chunks' => 1,
|
134 |
+
'regex' => $data->regex,
|
135 |
+
);
|
136 |
+
|
137 |
+
set_transient( 'string-locator-search-overview', $store );
|
138 |
+
update_option( 'string-locator-search-history', array(), false );
|
139 |
+
|
140 |
+
return array(
|
141 |
+
'success' => true,
|
142 |
+
'data' => array(
|
143 |
+
'chunks' => 1,
|
144 |
+
'current' => 0,
|
145 |
+
'regex' => $data->regex,
|
146 |
+
'total' => 1,
|
147 |
+
),
|
148 |
+
);
|
149 |
+
}
|
150 |
+
|
151 |
+
return $short_circuit;
|
152 |
+
}
|
153 |
+
|
154 |
+
/**
|
155 |
+
* Conditionally override the search handler.
|
156 |
+
*
|
157 |
+
* @param mixed $handler The currently active class for handling the search request.
|
158 |
+
* @param \WP_REST_Request $request The request received by the REST API handler.
|
159 |
+
*
|
160 |
+
* @return $this
|
161 |
+
*/
|
162 |
+
public function maybe_perform_sql_search( $handler, $request ) {
|
163 |
+
$search_data = get_transient( 'string-locator-search-overview' );
|
164 |
+
|
165 |
+
if ( empty( $search_data ) || ! isset( $search_data->type ) || 'sql' !== $search_data->type ) {
|
166 |
+
return $handler;
|
167 |
+
}
|
168 |
+
|
169 |
+
return $this;
|
170 |
+
}
|
171 |
+
|
172 |
+
/**
|
173 |
+
* Run the search.
|
174 |
+
*
|
175 |
+
* @param int $filenum An integer representing where in the line you are when doing batch searches.
|
176 |
+
*
|
177 |
+
* @return array
|
178 |
+
*/
|
179 |
+
public function run( $filenum ) {
|
180 |
+
global $wpdb;
|
181 |
+
|
182 |
+
$response = array(
|
183 |
+
'search' => array(),
|
184 |
+
'filenum' => absint( $filenum ),
|
185 |
+
'current' => 0,
|
186 |
+
'total' => 0,
|
187 |
+
'type' => 'sql',
|
188 |
+
);
|
189 |
+
|
190 |
+
$scan_data = get_transient( 'string-locator-search-overview' );
|
191 |
+
|
192 |
+
$is_regex = false;
|
193 |
+
if ( isset( $scan_data->regex ) ) {
|
194 |
+
$is_regex = String_Locator::absbool( $scan_data->regex );
|
195 |
+
}
|
196 |
+
|
197 |
+
if ( $is_regex ) {
|
198 |
+
if ( false === @preg_match( $scan_data->search, '' ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
|
199 |
+
wp_send_json_error(
|
200 |
+
array(
|
201 |
+
'continue' => false,
|
202 |
+
'message' => sprintf(
|
203 |
+
/* translators: %s: The search string used. */
|
204 |
+
__( 'Your search string, <strong>%s</strong>, is not a valid pattern, and the search has been aborted.', 'string-locator' ),
|
205 |
+
esc_html( $scan_data->search )
|
206 |
+
),
|
207 |
+
)
|
208 |
+
);
|
209 |
+
}
|
210 |
+
}
|
211 |
+
|
212 |
+
// Get al ist of all available tables to search through.
|
213 |
+
$tables = $wpdb->get_results( 'SHOW TABLES' );
|
214 |
+
|
215 |
+
$identifier_name = 'Tables_in_' . DB_NAME;
|
216 |
+
|
217 |
+
if ( ! validate_sql_fields( $identifier_name ) ) {
|
218 |
+
wp_send_json_error(
|
219 |
+
array(
|
220 |
+
'continue' => false,
|
221 |
+
'message' => sprintf(
|
222 |
+
/* translators: %s: The search string used. */
|
223 |
+
__( 'The table identifier, combined with your database name, <strong>%s</strong>, is not a valid SQL pattern, and the search has been aborted.', 'string-locator' ),
|
224 |
+
esc_html( $identifier_name )
|
225 |
+
),
|
226 |
+
)
|
227 |
+
);
|
228 |
+
}
|
229 |
+
|
230 |
+
$match_count = 0;
|
231 |
+
|
232 |
+
$search_results = array();
|
233 |
+
|
234 |
+
foreach ( $tables as $table ) {
|
235 |
+
$table_name = $table->{ $identifier_name };
|
236 |
+
|
237 |
+
$columns = $wpdb->get_results( 'DESCRIBE ' . $table_name ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- The table name is validated earlier for SQLi, and needs to be dynamic due to relying on `DB_NAME`.
|
238 |
+
|
239 |
+
$primary_column = null;
|
240 |
+
$primary_type = null;
|
241 |
+
|
242 |
+
// Initial loop only gets primary data.
|
243 |
+
foreach ( $columns as $column ) {
|
244 |
+
if ( 'PRI' === $column->Key ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Object property name is returned by the MySQL database.
|
245 |
+
$primary_column = $column->Field; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Object property name is returned by the MySQL database.
|
246 |
+
$primary_type = ( stristr( $column->Type, 'int' ) ? 'int' : 'str' ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Object property name is returned by the MySQL database.
|
247 |
+
}
|
248 |
+
}
|
249 |
+
|
250 |
+
foreach ( $columns as $column ) {
|
251 |
+
$column_name = $column->Field; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Object property name is returned by the MySQL database.
|
252 |
+
|
253 |
+
if ( $is_regex ) {
|
254 |
+
$matches = $wpdb->get_results(
|
255 |
+
$wpdb->prepare(
|
256 |
+
'SELECT ' . $column_name . ' AS column_name, ' . $primary_column . ' as primary_column FROM ' . $table_name . ' WHERE ' . $column_name . ' REGEXP %s', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- It is not possible to prepare a table or column name, but these are instead validated in `/includes/Search/class-sql.php` before reaching this point.
|
257 |
+
$scan_data->search
|
258 |
+
)
|
259 |
+
);
|
260 |
+
} else {
|
261 |
+
$matches = $wpdb->get_results(
|
262 |
+
$wpdb->prepare(
|
263 |
+
'SELECT ' . $column_name . ' AS column_name, ' . $primary_column . ' as primary_column FROM ' . $table_name . ' WHERE ' . $column_name . ' LIKE %s', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- It is not possible to prepare a table or column name, but these are instead validated in `/includes/Search/class-sql.php` before reaching this point.
|
264 |
+
'%' . $wpdb->esc_like( $scan_data->search ) . '%'
|
265 |
+
)
|
266 |
+
);
|
267 |
+
}
|
268 |
+
|
269 |
+
if ( is_wp_error( $matches ) ) {
|
270 |
+
wp_send_json_error(
|
271 |
+
array(
|
272 |
+
'continue' => false,
|
273 |
+
'message' => sprintf(
|
274 |
+
/* translators: 1: The search string used. 2: The error received */
|
275 |
+
__( 'Your search for <strong>%1$s</strong> led to an SQL error, and the search has been aborted. The error encountered was: %2$s', 'string-locator' ),
|
276 |
+
esc_html( $scan_data->search ),
|
277 |
+
esc_html( $matches->get_error_message() )
|
278 |
+
),
|
279 |
+
)
|
280 |
+
);
|
281 |
+
}
|
282 |
+
|
283 |
+
foreach ( $matches as $match ) {
|
284 |
+
$match_count++;
|
285 |
+
|
286 |
+
$string = $scan_data->search;
|
287 |
+
$string_preview = $match->column_name;
|
288 |
+
|
289 |
+
$string_location = 0;
|
290 |
+
|
291 |
+
$string_preview = String_Locator::create_preview( $string_preview, $string, $is_regex );
|
292 |
+
|
293 |
+
$editurl = $this->create_edit_link( $table_name, $column_name, $primary_column, $primary_type, $match );
|
294 |
+
|
295 |
+
$search_results[] = array(
|
296 |
+
'ID' => $match_count,
|
297 |
+
'table' => $table_name,
|
298 |
+
'column' => $column_name,
|
299 |
+
'primary_key' => $match->primary_column,
|
300 |
+
'primary_type' => $primary_type,
|
301 |
+
'primary_column' => $primary_column,
|
302 |
+
'filename' => sprintf(
|
303 |
+
'`%s`.`%s`',
|
304 |
+
$table_name,
|
305 |
+
$column_name
|
306 |
+
),
|
307 |
+
'filename_raw' => sprintf(
|
308 |
+
'`%s`.`%s`',
|
309 |
+
$table_name,
|
310 |
+
$column_name
|
311 |
+
),
|
312 |
+
'editurl' => ( current_user_can( String_Locator::$default_capability ) ? $editurl : false ),
|
313 |
+
'stringresult' => $string_preview,
|
314 |
+
'linepos' => $string_location,
|
315 |
+
'linenum' => 0,
|
316 |
+
);
|
317 |
+
}
|
318 |
+
}
|
319 |
+
}
|
320 |
+
|
321 |
+
if ( ! empty( $search_results ) ) {
|
322 |
+
$history = get_option( 'string-locator-search-history', array() );
|
323 |
+
$history = array_merge( $history, $search_results );
|
324 |
+
update_option( 'string-locator-search-history', $history, false );
|
325 |
+
}
|
326 |
+
|
327 |
+
$response['search'] = array(
|
328 |
+
$search_results,
|
329 |
+
);
|
330 |
+
|
331 |
+
return $response;
|
332 |
+
}
|
333 |
+
|
334 |
+
/**
|
335 |
+
* Generate a link to the editor interface for a search result.
|
336 |
+
*
|
337 |
+
* @param string $table_name The table name where a match was found.
|
338 |
+
* @param string $column_name The column name where a match was found.
|
339 |
+
* @param string $primary_column The primary column from the table having a match.
|
340 |
+
* @param string $primary_type The type of the primary column.
|
341 |
+
* @param object $match An object containing details of the match.
|
342 |
+
*
|
343 |
+
* @return string
|
344 |
+
*/
|
345 |
+
public function create_edit_link( $table_name, $column_name, $primary_column, $primary_type, $match ) {
|
346 |
+
return add_query_arg(
|
347 |
+
array(
|
348 |
+
'page' => 'string-locator',
|
349 |
+
'edit-file' => true,
|
350 |
+
'file-type' => 'sql',
|
351 |
+
'file-reference' => sprintf(
|
352 |
+
'`%s`.`%s`',
|
353 |
+
$table_name,
|
354 |
+
$column_name
|
355 |
+
),
|
356 |
+
'sql-column' => $column_name,
|
357 |
+
'sql-table' => $table_name,
|
358 |
+
'sql-primary-column' => $primary_column,
|
359 |
+
'sql-primary-type' => $primary_type,
|
360 |
+
'sql-primary-key' => $match->primary_column,
|
361 |
+
),
|
362 |
+
admin_url( $this->path_to_use )
|
363 |
+
);
|
364 |
+
}
|
365 |
+
}
|
366 |
+
|
367 |
+
new Search();
|
includes/Extension/SQL/sql.php
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Plugin Name: String Locator: SQL search module
|
4 |
+
*/
|
5 |
+
|
6 |
+
namespace StringLocator\Extension\SQL;
|
7 |
+
|
8 |
+
function validate_sql_fields( $field ) {
|
9 |
+
return preg_match( '/^[0-9a-zA-Z_]+$/s', $field );
|
10 |
+
}
|
11 |
+
|
12 |
+
require_once __DIR__ . '/class-search.php';
|
13 |
+
require_once __DIR__ . '/class-edit.php';
|
14 |
+
require_once __DIR__ . '/class-save.php';
|
15 |
+
|
16 |
+
require_once __DIR__ . '/Tests/class-serialized-data.php';
|
includes/Extension/SQL/views/editor/sql.php
ADDED
@@ -0,0 +1,251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace StringLocator;
|
4 |
+
|
5 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
6 |
+
die();
|
7 |
+
}
|
8 |
+
|
9 |
+
global $wpdb;
|
10 |
+
|
11 |
+
$editor_content = '';
|
12 |
+
|
13 |
+
$this_url = add_query_arg(
|
14 |
+
array(
|
15 |
+
'page' => 'string-locator',
|
16 |
+
),
|
17 |
+
admin_url( ( is_multisite() ? 'network/admin.php' : 'tools.php' ) )
|
18 |
+
);
|
19 |
+
|
20 |
+
if ( 'int' === $_GET['sql-primary-type'] ) {
|
21 |
+
$row = $wpdb->get_row(
|
22 |
+
$wpdb->prepare(
|
23 |
+
'SELECT * FROM ' . $_GET['sql-table'] . ' WHERE ' . $_GET['sql-primary-column'] . ' = %d LIMIT 1', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- It is not possible to prepare a table or column name, but these are instead validated in `/includes/Search/class-sql.php` before reaching this point.
|
24 |
+
$_GET['sql-primary-key']
|
25 |
+
)
|
26 |
+
);
|
27 |
+
} else {
|
28 |
+
$row = $wpdb->get_row(
|
29 |
+
$wpdb->prepare(
|
30 |
+
'SELECT * FROM ' . $_GET['sql-table'] . ' WHERE ' . $_GET['sql-primary-column'] . ' = %s LIMIT 1', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- It is not possible to prepare a table or column name, but these are instead validated in `/includes/Search/class-sql.php` before reaching this point.
|
31 |
+
$_GET['sql-primary-key']
|
32 |
+
)
|
33 |
+
);
|
34 |
+
}
|
35 |
+
|
36 |
+
$format = 'string';
|
37 |
+
|
38 |
+
if ( is_serialized( $row->{ $_GET['sql-column'] }, true ) ) {
|
39 |
+
$format = 'serialized';
|
40 |
+
}
|
41 |
+
|
42 |
+
$editor_content = $row->{ $_GET['sql-column'] };
|
43 |
+
?>
|
44 |
+
<form id="string-locator-edit-form" class="string-locator-editor-wrapper">
|
45 |
+
<?php wp_nonce_field( 'wp_rest' ); ?>
|
46 |
+
|
47 |
+
<h1 class="screen-reader-text">
|
48 |
+
<?php
|
49 |
+
/* translators: Title on the editor page. */
|
50 |
+
esc_html_e( 'String Locator - SQL Editor', 'string-locator' );
|
51 |
+
?>
|
52 |
+
</h1>
|
53 |
+
|
54 |
+
<?php String_Locator::edit_form_fields( true ); ?>
|
55 |
+
|
56 |
+
<div class="string-locator-header">
|
57 |
+
<div>
|
58 |
+
<span class="title">
|
59 |
+
<?php
|
60 |
+
printf(
|
61 |
+
// translators: %s: The name of the database column being edited.
|
62 |
+
__( 'You are currently editing a database entry from <em>%s</em>', 'string-locator' ),
|
63 |
+
esc_html( $_GET['file-reference'] )
|
64 |
+
);
|
65 |
+
?>
|
66 |
+
</span>
|
67 |
+
</div>
|
68 |
+
|
69 |
+
<div>
|
70 |
+
<a href="<?php echo esc_url( $this_url . '&restore=true' ); ?>" class="button button-default"><?php esc_html_e( 'Return to search results', 'string-locator' ); ?></a>
|
71 |
+
<button type="submit" class="button button-primary"><?php esc_html_e( 'Save changes', 'string-locator' ); ?></button>
|
72 |
+
</div>
|
73 |
+
</div>
|
74 |
+
|
75 |
+
<div class="string-locator-editor">
|
76 |
+
<div id="string-locator-notices">
|
77 |
+
<div class="row notice notice-error inline below-h2 hide-if-js">
|
78 |
+
<p>
|
79 |
+
<?php esc_html_e( 'The editor requires Javascript to be enabled before it can be used.', 'string-locator' ); ?>
|
80 |
+
</p>
|
81 |
+
</div>
|
82 |
+
|
83 |
+
<div class="row notice notice-warning inline below-h2 is-dismissible <?php echo ( 'serialized' === $format ? 'notice-error' : '' ); ?>">
|
84 |
+
<p>
|
85 |
+
<strong><?php esc_html_e( 'Warning:', 'string-locator' ); ?></strong> <?php esc_html_e( 'You are directly editing a database entry.', 'string-locator' ); ?>
|
86 |
+
</p>
|
87 |
+
<p>
|
88 |
+
<?php _e( 'You are about to make modifications directly to the database. Even if you are familiar with such changes, it is strongly recommended you keep a backup, and to perform such changes on a <a href="https://wordpress.org/support/article/running-a-development-copy-of-wordpress/">staging site</a> first.', 'string-locator' ); ?>
|
89 |
+
</p>
|
90 |
+
|
91 |
+
<?php if ( 'serialized' === $format ) : ?>
|
92 |
+
<p>
|
93 |
+
<strong>
|
94 |
+
<?php _e( 'This data is serialized, that means extra care must be taken as string lengths, and special characters must be accurately representative of the various field descriptors also found in the database entry.', 'string-locator' ); ?>
|
95 |
+
</strong>
|
96 |
+
</p>
|
97 |
+
<?php endif; ?>
|
98 |
+
</div>
|
99 |
+
</div>
|
100 |
+
|
101 |
+
<textarea
|
102 |
+
name="string-locator-editor-content"
|
103 |
+
class="string-locator-editor hide-if-no-js"
|
104 |
+
id="code-editor"
|
105 |
+
autofocus="autofocus"
|
106 |
+
><?php echo esc_html( $editor_content ); ?></textarea>
|
107 |
+
</div>
|
108 |
+
|
109 |
+
<div class="string-locator-sidebar">
|
110 |
+
|
111 |
+
<?php do_action( 'string_locator_editor_sidebar_start' ); ?>
|
112 |
+
|
113 |
+
<div class="string-locator-panel">
|
114 |
+
<h2 class="title"><?php esc_html_e( 'Details', 'string-locator' ); ?></h2>
|
115 |
+
<div class="string-locator-panel-body">
|
116 |
+
<div class="row">
|
117 |
+
<?php
|
118 |
+
printf(
|
119 |
+
// translators: 1: Table name being edited.
|
120 |
+
esc_html__( 'Database table: %s', 'string-locator' ),
|
121 |
+
esc_html( $_GET['sql-table'] )
|
122 |
+
);
|
123 |
+
?>
|
124 |
+
</div>
|
125 |
+
|
126 |
+
<div class="row">
|
127 |
+
<?php
|
128 |
+
printf(
|
129 |
+
// translators: 1: Column name being edited.
|
130 |
+
esc_html__( 'Database Column: %s', 'string-locator' ),
|
131 |
+
esc_html( $_GET['sql-column'] )
|
132 |
+
);
|
133 |
+
?>
|
134 |
+
</div>
|
135 |
+
|
136 |
+
<div class="row">
|
137 |
+
<?php
|
138 |
+
printf(
|
139 |
+
// translators: 1: Primary database column name. 2: Primary database column key.
|
140 |
+
esc_html__( 'Primary column and key: %1$s:%2$s', 'string-locator' ),
|
141 |
+
esc_html( $_GET['sql-primary-column'] ),
|
142 |
+
esc_html( $_GET['sql-primary-key'] )
|
143 |
+
);
|
144 |
+
?>
|
145 |
+
</div>
|
146 |
+
</div>
|
147 |
+
</div>
|
148 |
+
|
149 |
+
<?php do_action( 'string_locator_editor_sidebar_before_checks' ); ?>
|
150 |
+
|
151 |
+
<div class="string-locator-panel">
|
152 |
+
<h2 class="title"><?php esc_html_e( 'Save checks', 'string-locator' ); ?></h2>
|
153 |
+
<div class="string-locator-panel-body">
|
154 |
+
<?php do_action( 'string_locator_editor_checks' ); ?>
|
155 |
+
</div>
|
156 |
+
</div>
|
157 |
+
|
158 |
+
<?php do_action( 'string_locator_editor_sidebar_after_checks' ); ?>
|
159 |
+
|
160 |
+
<div class="string-locator-panel">
|
161 |
+
<h2 class="title"><?php esc_html_e( 'Database entry context', 'string-locator' ); ?></h2>
|
162 |
+
<div class="string-locator-panel-body">
|
163 |
+
|
164 |
+
<?php
|
165 |
+
foreach ( $row as $key => $value ) {
|
166 |
+
// Do not output the currently edited column as a context relationship.
|
167 |
+
if ( $_GET['sql-column'] === $key ) {
|
168 |
+
continue;
|
169 |
+
}
|
170 |
+
?>
|
171 |
+
|
172 |
+
<div class="row">
|
173 |
+
<?php echo esc_html( $key ); ?>:
|
174 |
+
<br />
|
175 |
+
<span class="string-locator-italics">
|
176 |
+
<?php echo esc_html( $value ); ?>
|
177 |
+
</span>
|
178 |
+
</div>
|
179 |
+
|
180 |
+
<?php
|
181 |
+
}
|
182 |
+
?>
|
183 |
+
</div>
|
184 |
+
</div>
|
185 |
+
|
186 |
+
<?php
|
187 |
+
$function_info = get_defined_functions();
|
188 |
+
$function_help = '';
|
189 |
+
|
190 |
+
foreach ( $function_info['user'] as $user_func ) {
|
191 |
+
if ( strstr( $editor_content, $user_func . '(' ) ) {
|
192 |
+
$function_object = new \ReflectionFunction( $user_func );
|
193 |
+
$attrs = $function_object->getParameters();
|
194 |
+
|
195 |
+
$attr_strings = array();
|
196 |
+
|
197 |
+
foreach ( $attrs as $attr ) {
|
198 |
+
$arg = '';
|
199 |
+
|
200 |
+
if ( $attr->isPassedByReference() ) {
|
201 |
+
$arg .= '&';
|
202 |
+
}
|
203 |
+
|
204 |
+
if ( $attr->isOptional() ) {
|
205 |
+
$arg = sprintf(
|
206 |
+
'[ %s$%s ]',
|
207 |
+
$arg,
|
208 |
+
$attr->getName()
|
209 |
+
);
|
210 |
+
} else {
|
211 |
+
$arg = sprintf(
|
212 |
+
'%s$%s',
|
213 |
+
$arg,
|
214 |
+
$attr->getName()
|
215 |
+
);
|
216 |
+
}
|
217 |
+
|
218 |
+
$attr_strings[] = $arg;
|
219 |
+
}
|
220 |
+
|
221 |
+
$function_help .= sprintf(
|
222 |
+
'<div class="row"><a href="%s" target="_blank">%s</a></div>',
|
223 |
+
esc_url( sprintf( 'https://developer.wordpress.org/reference/functions/%s/', $user_func ) ),
|
224 |
+
$user_func . '( ' . implode( ', ', $attr_strings ) . ' )'
|
225 |
+
);
|
226 |
+
}
|
227 |
+
}
|
228 |
+
?>
|
229 |
+
|
230 |
+
<?php if ( ! empty( $function_help ) ) : ?>
|
231 |
+
|
232 |
+
<div class="string-locator-panel">
|
233 |
+
<h2 class="title"><?php esc_html_e( 'WordPress functions', 'string-locator' ); ?></h2>
|
234 |
+
<div class="string-locator-panel-body">
|
235 |
+
<?php echo $function_help; ?>
|
236 |
+
</div>
|
237 |
+
<?php endif; ?>
|
238 |
+
|
239 |
+
<?php do_action( 'string_locator_editor_sidebar_end' ); ?>
|
240 |
+
|
241 |
+
</div>
|
242 |
+
|
243 |
+
</div>
|
244 |
+
|
245 |
+
<script id="tmpl-string-locator-alert" type="text/template">
|
246 |
+
<div class="row notice notice-{{ data.type }} inline below-h2 is-dismissible">
|
247 |
+
{{{ data.message }}}
|
248 |
+
|
249 |
+
<button type="button" class="notice-dismiss" onclick="this.closest( '.notice' ).remove()"><span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'string-locator' ); ?></span></button>
|
250 |
+
</div>
|
251 |
+
</script>
|
includes/Extension/SQL/views/template/search.php
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
die();
|
4 |
+
}
|
5 |
+
?>
|
6 |
+
<script id="tmpl-string-locator-search-result-sql" type="text/template">
|
7 |
+
<tr data-type="sql" data-primary-key="{{ data.primary_key }}" data-primary-column="{{ data.primary_column }}" data-primary-type="{{ data.primary_type }}" data-table-name="{{ data.table }}" data-column-name="{{ data.column }}">
|
8 |
+
<th scope="row" class="check-column">
|
9 |
+
<input type="checkbox" name="string-locator-replace-checked[]" class="check-column-box">
|
10 |
+
</th>
|
11 |
+
<td>
|
12 |
+
{{{ data.stringresult }}}
|
13 |
+
|
14 |
+
<div class="row-actions">
|
15 |
+
<# if ( data.editurl ) { #>
|
16 |
+
<span class="edit">
|
17 |
+
<a href="{{ data.editurl }}" aria-label="<?php esc_attr_e( 'Edit', 'string-locator' ); ?>">
|
18 |
+
<?php esc_html_e( 'Edit', 'string-locator' ); ?>
|
19 |
+
</a>
|
20 |
+
</span>
|
21 |
+
<# } #>
|
22 |
+
</div>
|
23 |
+
</td>
|
24 |
+
<td>
|
25 |
+
<# if ( data.editurl ) { #>
|
26 |
+
<a href="{{ data.editurl }}">
|
27 |
+
{{ data.filename }}
|
28 |
+
</a>
|
29 |
+
<# } #>
|
30 |
+
<# if ( ! data.editurl ) { #>
|
31 |
+
{{ data.filename }}
|
32 |
+
<# } #>
|
33 |
+
</td>
|
34 |
+
<td>
|
35 |
+
{{ data.primary_key }}
|
36 |
+
</td>
|
37 |
+
<td>
|
38 |
+
{{ data.linepos }}
|
39 |
+
</td>
|
40 |
+
</tr>
|
41 |
+
</script>
|
includes/Extension/SearchReplace/REST/class-replace.php
ADDED
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* REST API endpoints for the replacement module.
|
4 |
+
*/
|
5 |
+
|
6 |
+
namespace StringLocator\Extension\SearchReplace\REST;
|
7 |
+
|
8 |
+
use StringLocator\Base\REST;
|
9 |
+
use StringLocator\Extension\SearchReplace\Replace\File;
|
10 |
+
use StringLocator\Extension\SearchReplace\Replace\SQL;
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Replace class.
|
14 |
+
*/
|
15 |
+
class Replace extends REST {
|
16 |
+
|
17 |
+
protected $rest_base = 'replace';
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Class constructor.
|
21 |
+
*/
|
22 |
+
public function __construct() {
|
23 |
+
parent::__construct();
|
24 |
+
}
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Register custom REST API endpoints.
|
28 |
+
*
|
29 |
+
* @return void
|
30 |
+
*/
|
31 |
+
public function register_rest_route() {
|
32 |
+
register_rest_route(
|
33 |
+
$this->namespace,
|
34 |
+
$this->rest_base,
|
35 |
+
array(
|
36 |
+
'methods' => 'POST',
|
37 |
+
'callback' => array( $this, 'replace' ),
|
38 |
+
'permission_callback' => array( $this, 'permission_callback' ),
|
39 |
+
)
|
40 |
+
);
|
41 |
+
}
|
42 |
+
|
43 |
+
/**
|
44 |
+
* REST API handler for the replacement endpoint.
|
45 |
+
*
|
46 |
+
* @param \WP_REST_Request $request A dynamic request object depending on the data type being replaced.
|
47 |
+
*
|
48 |
+
* @return \WP_Error|\WP_REST_Response
|
49 |
+
*/
|
50 |
+
public function replace( \WP_REST_Request $request ) {
|
51 |
+
$replace_nonce = $request->get_param( 'replace_nonce' );
|
52 |
+
|
53 |
+
if ( ! $replace_nonce || ! wp_verify_nonce( $replace_nonce, 'string-locator-replace' ) ) {
|
54 |
+
return new \WP_Error( 'invalid_nonce', __( 'Invalid nonce.', 'string-locator' ), array( 'status' => 400 ) );
|
55 |
+
}
|
56 |
+
|
57 |
+
// Ensure the regex flag is a boolean value.
|
58 |
+
$is_regex = $request->get_param( 'search_regex' );
|
59 |
+
if ( ! is_bool( $is_regex ) ) {
|
60 |
+
if ( 'false' === $is_regex ) {
|
61 |
+
$is_regex = false;
|
62 |
+
} else {
|
63 |
+
$is_regex = true;
|
64 |
+
}
|
65 |
+
}
|
66 |
+
|
67 |
+
$check_loopback = $request->get_param( 'replace_loopback' );
|
68 |
+
if ( ! is_bool( $check_loopback ) ) {
|
69 |
+
if ( 'false' === $check_loopback ) {
|
70 |
+
$check_loopback = false;
|
71 |
+
} else {
|
72 |
+
$check_loopback = true;
|
73 |
+
}
|
74 |
+
}
|
75 |
+
|
76 |
+
switch ( $request->get_param( 'type' ) ) {
|
77 |
+
case 'sql':
|
78 |
+
$handler = new SQL(
|
79 |
+
$request->get_param( 'primaryColumn' ),
|
80 |
+
$request->get_param( 'primaryKey' ),
|
81 |
+
$request->get_param( 'primaryType' ),
|
82 |
+
$request->get_param( 'tableName' ),
|
83 |
+
$request->get_param( 'columnName' ),
|
84 |
+
$request->get_param( 'search_string' ),
|
85 |
+
$request->get_param( 'replace_string' ),
|
86 |
+
$is_regex
|
87 |
+
);
|
88 |
+
break;
|
89 |
+
case 'file':
|
90 |
+
default:
|
91 |
+
$handler = new File(
|
92 |
+
$request->get_param( 'filename' ),
|
93 |
+
$request->get_param( 'linenum' ),
|
94 |
+
$request->get_param( 'search_string' ),
|
95 |
+
$request->get_param( 'replace_string' ),
|
96 |
+
$is_regex
|
97 |
+
);
|
98 |
+
}
|
99 |
+
|
100 |
+
if ( ! $handler->validate() ) {
|
101 |
+
return new \WP_Error( 'invalid_request', __( 'Invalid request', 'string-locator' ), array( 'status' => 400 ) );
|
102 |
+
}
|
103 |
+
|
104 |
+
$replace = $handler->replace();
|
105 |
+
|
106 |
+
if ( is_wp_error( $replace ) ) {
|
107 |
+
return $replace;
|
108 |
+
}
|
109 |
+
|
110 |
+
// Basic check to ensure the site is not broken after the modifications.
|
111 |
+
if ( $check_loopback ) {
|
112 |
+
$urls = array(
|
113 |
+
get_site_url( null, '/' ),
|
114 |
+
get_admin_url( null, '/' ),
|
115 |
+
);
|
116 |
+
|
117 |
+
foreach ( $urls as $url ) {
|
118 |
+
$loopback = wp_remote_head( $url );
|
119 |
+
|
120 |
+
if ( is_wp_error( $loopback ) ) {
|
121 |
+
$handler->restore();
|
122 |
+
|
123 |
+
return new \WP_Error(
|
124 |
+
'loopback_failed',
|
125 |
+
sprintf(
|
126 |
+
// translators: 1: The URL being requested.
|
127 |
+
__( 'The address `%s` could not be loaded after the edits were made, and the changes were therefore reverted.', 'string-locator' ),
|
128 |
+
esc_html( $url )
|
129 |
+
),
|
130 |
+
array(
|
131 |
+
'status' => 400,
|
132 |
+
)
|
133 |
+
);
|
134 |
+
}
|
135 |
+
|
136 |
+
$response_code = wp_remote_retrieve_response_code( $loopback );
|
137 |
+
if ( (int) substr( $response_code, 0, 1 ) > 3 ) {
|
138 |
+
$handler->restore();
|
139 |
+
|
140 |
+
return new \WP_Error(
|
141 |
+
'loopback_failed',
|
142 |
+
sprintf(
|
143 |
+
// translators: 1: The URL being requested. 2: The HTTP status code returned.
|
144 |
+
__( 'The address `%1$s` returned an error code (%2$s), and the changes were therefore reverted.', 'string-locator' ),
|
145 |
+
esc_html( $url ),
|
146 |
+
esc_html( $response_code )
|
147 |
+
),
|
148 |
+
array(
|
149 |
+
'status' => 400,
|
150 |
+
)
|
151 |
+
);
|
152 |
+
}
|
153 |
+
}
|
154 |
+
}
|
155 |
+
|
156 |
+
/*
|
157 |
+
* A `true` response means no errors occurred, but also no replacements/updated were made.
|
158 |
+
*/
|
159 |
+
if ( true !== $replace ) {
|
160 |
+
$string_preview = sprintf(
|
161 |
+
'%s<div class="row-actions"><span class="edit"><a href="%s">%s</a></span></div>',
|
162 |
+
$replace,
|
163 |
+
$handler->get_edit_url(),
|
164 |
+
esc_html__( 'Edit', 'string-locator' )
|
165 |
+
);
|
166 |
+
} else {
|
167 |
+
$string_preview = true;
|
168 |
+
}
|
169 |
+
|
170 |
+
return new \WP_REST_Response(
|
171 |
+
array(
|
172 |
+
'success' => true,
|
173 |
+
'data' => array(
|
174 |
+
'replace_string' => $string_preview,
|
175 |
+
),
|
176 |
+
),
|
177 |
+
200
|
178 |
+
);
|
179 |
+
}
|
180 |
+
}
|
181 |
+
|
182 |
+
new Replace();
|
includes/Extension/SearchReplace/Replace/class-file.php
ADDED
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Class for handling replacements in files.
|
4 |
+
*/
|
5 |
+
|
6 |
+
namespace StringLocator\Extension\SearchReplace\Replace;
|
7 |
+
|
8 |
+
use StringLocator\Search;
|
9 |
+
use StringLocator\String_Locator;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* File class.
|
13 |
+
*/
|
14 |
+
class File {
|
15 |
+
|
16 |
+
private $file;
|
17 |
+
private $line;
|
18 |
+
private $regex;
|
19 |
+
private $new_string;
|
20 |
+
private $old_string;
|
21 |
+
private $original_string;
|
22 |
+
|
23 |
+
private $search;
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Class constructor.
|
27 |
+
*
|
28 |
+
* @param string $file The relative path from the WordPress root to the file being edited.
|
29 |
+
* @param int $line The line in the file containing the string to be replaced.
|
30 |
+
* @param string $old_string The string to be replaced.
|
31 |
+
* @param string $new_string The string to be added.
|
32 |
+
* @param bool $regex Is the search string a regex string.
|
33 |
+
*/
|
34 |
+
public function __construct( $file, $line, $old_string, $new_string, $regex = false ) {
|
35 |
+
$this->file = trailingslashit( ABSPATH ) . $file;
|
36 |
+
$this->line = ( absint( $line ) - 1 );
|
37 |
+
$this->regex = $regex;
|
38 |
+
$this->old_string = $old_string;
|
39 |
+
$this->new_string = $new_string;
|
40 |
+
|
41 |
+
$this->search = new Search();
|
42 |
+
}
|
43 |
+
|
44 |
+
/**
|
45 |
+
* Validate that the file is in a location that does not allow directory traversals.
|
46 |
+
*
|
47 |
+
* @return bool
|
48 |
+
*/
|
49 |
+
public function validate() {
|
50 |
+
return String_Locator::is_valid_location( $this->file );
|
51 |
+
}
|
52 |
+
|
53 |
+
/**
|
54 |
+
* Run the replacement function.
|
55 |
+
*
|
56 |
+
* @return bool|string|\WP_Error
|
57 |
+
*/
|
58 |
+
public function replace() {
|
59 |
+
// A value of 0 or lower indicates a filename or similar matched, and these should NOT be replaced.
|
60 |
+
if ( $this->line < 0 ) {
|
61 |
+
return true;
|
62 |
+
}
|
63 |
+
|
64 |
+
$file_contents = file( $this->file );
|
65 |
+
|
66 |
+
if ( ! $file_contents ) {
|
67 |
+
return new \WP_Error( 'file_inaccessible', __( 'The file could not be read.', 'string-locator' ), array( 'status' => 400 ) );
|
68 |
+
}
|
69 |
+
|
70 |
+
$this->original_string = $file_contents[ $this->line ];
|
71 |
+
|
72 |
+
if ( $this->regex ) {
|
73 |
+
$file_contents[ $this->line ] = preg_replace( $this->old_string, $this->new_string, $file_contents[ $this->line ] );
|
74 |
+
} else {
|
75 |
+
$file_contents[ $this->line ] = str_ireplace( $this->old_string, $this->new_string, $file_contents[ $this->line ] );
|
76 |
+
}
|
77 |
+
|
78 |
+
$file = fopen( $this->file, 'w' );
|
79 |
+
|
80 |
+
if ( ! $file ) {
|
81 |
+
return new \WP_Error( 'file_inaccessible', __( 'The file could not be written.', 'string-locator' ), array( 'status' => 400 ) );
|
82 |
+
}
|
83 |
+
|
84 |
+
foreach ( $file_contents as $file_line ) {
|
85 |
+
fwrite( $file, $file_line );
|
86 |
+
}
|
87 |
+
|
88 |
+
fclose( $file );
|
89 |
+
|
90 |
+
return String_Locator::create_preview( $file_contents[ $this->line ], $this->new_string, $this->regex );
|
91 |
+
}
|
92 |
+
|
93 |
+
/**
|
94 |
+
* Restore the last ran modification.
|
95 |
+
*
|
96 |
+
* @return bool
|
97 |
+
*/
|
98 |
+
public function restore() {
|
99 |
+
$file_contents = file( $this->file );
|
100 |
+
|
101 |
+
$file_contents[ $this->line ] = $this->original_string;
|
102 |
+
|
103 |
+
$file = fopen( $this->file, 'w' );
|
104 |
+
|
105 |
+
foreach ( $file_contents as $file_line ) {
|
106 |
+
fwrite( $file, $file_line );
|
107 |
+
}
|
108 |
+
|
109 |
+
fclose( $file );
|
110 |
+
|
111 |
+
return true;
|
112 |
+
}
|
113 |
+
|
114 |
+
/**
|
115 |
+
* Return the URL for the editor interface for this file.
|
116 |
+
*
|
117 |
+
* @return string
|
118 |
+
*/
|
119 |
+
public function get_edit_url() {
|
120 |
+
return $this->search->create_edit_link( $this->file, $this->line, 0 );
|
121 |
+
}
|
122 |
+
}
|
includes/Extension/SearchReplace/Replace/class-sql.php
ADDED
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Handle replacements in SQL strings.
|
4 |
+
*
|
5 |
+
* This file borrows and adapts functions and concepts from the
|
6 |
+
* interconnect.it Search-Replace script (https://github.com/interconnectit/Search-Replace-DB).
|
7 |
+
*/
|
8 |
+
|
9 |
+
namespace StringLocator\Extension\SearchReplace\Replace;
|
10 |
+
|
11 |
+
use StringLocator\Extension\SQL\Search;
|
12 |
+
use StringLocator\String_Locator;
|
13 |
+
use function StringLocator\Extension\SQL\validate_sql_fields;
|
14 |
+
|
15 |
+
/**
|
16 |
+
* SQL class.
|
17 |
+
*/
|
18 |
+
class SQL {
|
19 |
+
|
20 |
+
private $table_name;
|
21 |
+
private $primary_column;
|
22 |
+
private $primary_key;
|
23 |
+
private $primary_type;
|
24 |
+
private $column_name;
|
25 |
+
private $regex;
|
26 |
+
private $new_string;
|
27 |
+
private $old_string;
|
28 |
+
private $original_string;
|
29 |
+
|
30 |
+
private $search;
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Class constructor.
|
34 |
+
*
|
35 |
+
* @param string $primary_column The name of the primary column of the entry being edited.
|
36 |
+
* @param string $primary_key The key identified of the primary column.
|
37 |
+
* @param string $primary_type The type of the primary column (`int` or `string`).
|
38 |
+
* @param string $table_name The name of the table to perform an edit within.
|
39 |
+
* @param string $column_name The column being edited.
|
40 |
+
* @param string $old_string The string to be replaced.
|
41 |
+
* @param string $new_string The string to be added.
|
42 |
+
* @param bool $regex Is the search string a regex string.
|
43 |
+
*/
|
44 |
+
public function __construct( $primary_column, $primary_key, $primary_type, $table_name, $column_name, $old_string, $new_string, $regex = false ) {
|
45 |
+
$this->primary_column = $primary_column;
|
46 |
+
$this->primary_key = $primary_key;
|
47 |
+
$this->primary_type = $primary_type;
|
48 |
+
$this->table_name = $table_name;
|
49 |
+
$this->column_name = $column_name;
|
50 |
+
$this->regex = $regex;
|
51 |
+
$this->old_string = $old_string;
|
52 |
+
$this->new_string = $new_string;
|
53 |
+
|
54 |
+
$this->search = new Search();
|
55 |
+
}
|
56 |
+
|
57 |
+
/**
|
58 |
+
* Validate that the only non-escaped strings are alpha-numeric to avoid SQL injections.
|
59 |
+
*
|
60 |
+
* @return bool
|
61 |
+
*/
|
62 |
+
public function validate() {
|
63 |
+
if ( ! validate_sql_fields( $this->primary_column ) ) {
|
64 |
+
return false;
|
65 |
+
}
|
66 |
+
if ( ! validate_sql_fields( $this->table_name ) ) {
|
67 |
+
return false;
|
68 |
+
}
|
69 |
+
if ( ! validate_sql_fields( $this->column_name ) ) {
|
70 |
+
return false;
|
71 |
+
}
|
72 |
+
|
73 |
+
return true;
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Run the replacement function.
|
78 |
+
*
|
79 |
+
* @return bool|string|\WP_Error
|
80 |
+
*/
|
81 |
+
public function replace() {
|
82 |
+
global $wpdb;
|
83 |
+
|
84 |
+
if ( 'int' === $this->primary_type ) {
|
85 |
+
$this->original_string = $wpdb->get_var(
|
86 |
+
$wpdb->prepare(
|
87 |
+
'SELECT ' . $this->column_name . ' FROM ' . $this->table_name . ' WHERE ' . $this->primary_column . ' = %d LIMIT 1', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- It is not possible to prepare a table or column name, but these are instead validated in `/includes/Search/class-sql.php` before reaching this point.
|
88 |
+
$this->primary_key
|
89 |
+
)
|
90 |
+
);
|
91 |
+
} else {
|
92 |
+
$this->original_string = $wpdb->get_var(
|
93 |
+
$wpdb->prepare(
|
94 |
+
'SELECT ' . $this->column_name . ' FROM ' . $this->table_name . ' WHERE ' . $this->primary_column . ' = %s LIMIT 1', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- It is not possible to prepare a table or column name, but these are instead validated in `/includes/Search/class-sql.php` before reaching this point.
|
95 |
+
$this->primary_key
|
96 |
+
)
|
97 |
+
);
|
98 |
+
}
|
99 |
+
|
100 |
+
$replaced_line = $this->recursive_unserialize_replace( $this->old_string, $this->new_string, $this->original_string );
|
101 |
+
|
102 |
+
$updated = $wpdb->update(
|
103 |
+
$this->table_name,
|
104 |
+
array(
|
105 |
+
$this->column_name => $replaced_line,
|
106 |
+
),
|
107 |
+
array(
|
108 |
+
$this->primary_column => $this->primary_key,
|
109 |
+
)
|
110 |
+
);
|
111 |
+
|
112 |
+
if ( ! $updated ) {
|
113 |
+
/*
|
114 |
+
* Cause an error to be thrown if updates fail due ot the query.
|
115 |
+
*
|
116 |
+
* This also checks that `$wpdb->last_error` is empty before treating
|
117 |
+
* the result as an error, this is because `$wpdb->update` will return
|
118 |
+
* a `false` value if it did not perform an update, for example when
|
119 |
+
* a string is identical. This may be the case where a class name or
|
120 |
+
* object is encountered, which can not be replaced.
|
121 |
+
*/
|
122 |
+
if ( empty( $wpdb->last_error ) ) {
|
123 |
+
return true;
|
124 |
+
}
|
125 |
+
|
126 |
+
return new \WP_Error( 'search_replace_sql_error', __( 'Error updating the database.', 'search-replace' ) );
|
127 |
+
}
|
128 |
+
|
129 |
+
return String_Locator::create_preview( $replaced_line, $this->new_string, $this->regex );
|
130 |
+
}
|
131 |
+
|
132 |
+
/**
|
133 |
+
* Restore the last ran modification.
|
134 |
+
*
|
135 |
+
* @return bool
|
136 |
+
*/
|
137 |
+
public function restore() {
|
138 |
+
global $wpdb;
|
139 |
+
|
140 |
+
$wpdb->update(
|
141 |
+
$this->table_name,
|
142 |
+
array(
|
143 |
+
$this->column_name => $this->original_string,
|
144 |
+
),
|
145 |
+
array(
|
146 |
+
$this->primary_column => $this->primary_key,
|
147 |
+
)
|
148 |
+
);
|
149 |
+
return true;
|
150 |
+
}
|
151 |
+
|
152 |
+
public function get_edit_url() {
|
153 |
+
return $this->search->create_edit_link( $this->table_name, $this->column_name, $this->primary_column, $this->primary_type, (object) array( 'primary_column' => $this->primary_key ) );
|
154 |
+
}
|
155 |
+
|
156 |
+
/**
|
157 |
+
* Take a serialised array and unserialise it replacing elements as needed and
|
158 |
+
* unserialising any subordinate arrays and performing the replacement on those too.
|
159 |
+
*
|
160 |
+
* @param string $from String we're looking to replace.
|
161 |
+
* @param string $to What we want it to be replaced with
|
162 |
+
* @param array $data Used to pass any subordinate arrays back to in.
|
163 |
+
* @param bool $serialised Does the array passed via $data need serialising.
|
164 |
+
*
|
165 |
+
* @return array|string The original array with all elements replaced as needed.
|
166 |
+
*/
|
167 |
+
public function recursive_unserialize_replace( $from = '', $to = '', $data = '', $serialised = false ) {
|
168 |
+
// Some unserialised data cannot be re-serialised eg. SimpleXMLElements.
|
169 |
+
try {
|
170 |
+
$unserialized = @unserialize( $data );
|
171 |
+
if ( is_string( $data ) && false !== $unserialized ) {
|
172 |
+
$data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true );
|
173 |
+
} elseif ( is_array( $data ) ) {
|
174 |
+
$_tmp = array();
|
175 |
+
foreach ( $data as $key => $value ) {
|
176 |
+
$_tmp[ $key ] = $this->recursive_unserialize_replace( $from, $to, $value, false );
|
177 |
+
}
|
178 |
+
|
179 |
+
$data = $_tmp;
|
180 |
+
unset( $_tmp );
|
181 |
+
} elseif ( is_object( $data ) && ! is_a( $data, '__PHP_Incomplete_Class' ) ) {
|
182 |
+
$_tmp = $data;
|
183 |
+
$props = get_object_vars( $data );
|
184 |
+
foreach ( $props as $key => $value ) {
|
185 |
+
$_tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false );
|
186 |
+
}
|
187 |
+
|
188 |
+
$data = $_tmp;
|
189 |
+
unset( $_tmp );
|
190 |
+
} else {
|
191 |
+
if ( is_string( $data ) ) {
|
192 |
+
$data = $this->str_replace( $from, $to, $data );
|
193 |
+
}
|
194 |
+
}
|
195 |
+
|
196 |
+
if ( $serialised ) {
|
197 |
+
return serialize( $data );
|
198 |
+
}
|
199 |
+
} catch ( \Exception $error ) {
|
200 |
+
}
|
201 |
+
|
202 |
+
return $data;
|
203 |
+
}
|
204 |
+
|
205 |
+
/**
|
206 |
+
* Wrapper for regex/non regex search & replace
|
207 |
+
*
|
208 |
+
* @param string $search
|
209 |
+
* @param string $replace
|
210 |
+
* @param string $string
|
211 |
+
* @param int $count
|
212 |
+
*
|
213 |
+
* @return string
|
214 |
+
*/
|
215 |
+
public function str_replace( $search, $replace, $string, &$count = 0 ) {
|
216 |
+
if ( $this->regex ) {
|
217 |
+
return preg_replace( $search, $replace, $string, - 1, $count );
|
218 |
+
} elseif ( function_exists( 'mb_split' ) ) {
|
219 |
+
return $this->mb_str_replace( $search, $replace, $string, $count );
|
220 |
+
} else {
|
221 |
+
return str_ireplace( $search, $replace, $string, $count );
|
222 |
+
}
|
223 |
+
}
|
224 |
+
|
225 |
+
/**
|
226 |
+
* Replace all occurrences of the search string with the replacement string.
|
227 |
+
*
|
228 |
+
* @param mixed $search
|
229 |
+
* @param mixed $replace
|
230 |
+
* @param mixed $subject
|
231 |
+
* @param int $count
|
232 |
+
*
|
233 |
+
* @return mixed
|
234 |
+
* @copyright Copyright 2012 Sean Murphy. All rights reserved.
|
235 |
+
* @license http://creativecommons.org/publicdomain/zero/1.0/
|
236 |
+
* @link http://php.net/manual/function.str-replace.php
|
237 |
+
*
|
238 |
+
* @author Sean Murphy <sean@iamseanmurphy.com>
|
239 |
+
*/
|
240 |
+
public function mb_str_replace( $search, $replace, $subject, &$count = 0 ) {
|
241 |
+
if ( ! is_array( $subject ) ) {
|
242 |
+
// Normalize $search and $replace so they are both arrays of the same length
|
243 |
+
$searches = is_array( $search ) ? array_values( $search ) : array( $search );
|
244 |
+
$replacements = is_array( $replace ) ? array_values( $replace ) : array( $replace );
|
245 |
+
$replacements = array_pad( $replacements, count( $searches ), '' );
|
246 |
+
|
247 |
+
foreach ( $searches as $key => $search ) {
|
248 |
+
$parts = mb_split( preg_quote( $search ), $subject );
|
249 |
+
if ( ! is_array( $parts ) ) {
|
250 |
+
continue;
|
251 |
+
}
|
252 |
+
$count += count( $parts ) - 1;
|
253 |
+
$subject = implode( $replacements[ $key ], $parts );
|
254 |
+
}
|
255 |
+
} else {
|
256 |
+
// Call mb_str_replace for each subject in array, recursively
|
257 |
+
foreach ( $subject as $key => $value ) {
|
258 |
+
$subject[ $key ] = $this->mb_str_replace( $search, $replace, $value, $count );
|
259 |
+
}
|
260 |
+
}
|
261 |
+
|
262 |
+
return $subject;
|
263 |
+
}
|
264 |
+
}
|
includes/Extension/SearchReplace/class-replace.php
ADDED
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Base class for the Replace feature.
|
4 |
+
*/
|
5 |
+
|
6 |
+
namespace StringLocator\Extension\SearchReplace;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Replace class.
|
10 |
+
*/
|
11 |
+
class Replace {
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Class constructor.
|
15 |
+
*/
|
16 |
+
public function __construct() {
|
17 |
+
add_action( 'string_locator_search_results_tablenav_controls', array( $this, 'add_replace_button' ) );
|
18 |
+
add_action( 'string_locator_search_results_tablenav_controls', array( $this, 'output_replace_form' ) );
|
19 |
+
|
20 |
+
add_action( 'admin_enqueue_scripts', array( $this, 'maybe_enqueue_assets' ) );
|
21 |
+
|
22 |
+
add_action( 'string_locator_search_templates', array( $this, 'add_replace_response_template' ) );
|
23 |
+
}
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Add error notice template.
|
27 |
+
*
|
28 |
+
* @return void
|
29 |
+
*/
|
30 |
+
public function add_replace_response_template() {
|
31 |
+
require_once STRING_LOCATOR_PLUGIN_DIR . '/includes/Extension/SearchReplace/template/error-notice.php';
|
32 |
+
}
|
33 |
+
|
34 |
+
/**
|
35 |
+
* Conditionally register assets on the appropriate pages within wp-admin.
|
36 |
+
*
|
37 |
+
* @param string $hook The hook name for the page being loaded.
|
38 |
+
*
|
39 |
+
* @return void
|
40 |
+
*/
|
41 |
+
public function maybe_enqueue_assets( $hook ) {
|
42 |
+
// Break out early if we are not on a String Locator page
|
43 |
+
if ( 'tools_page_string-locator' !== $hook && 'toplevel_page_string-locator' !== $hook ) {
|
44 |
+
return;
|
45 |
+
}
|
46 |
+
|
47 |
+
$replace = STRING_LOCATOR_PLUGIN_DIR . 'build/string-locator-replace.asset.php';
|
48 |
+
|
49 |
+
$replace = file_exists( $replace ) ? require $replace : array( 'version' => false );
|
50 |
+
|
51 |
+
/**
|
52 |
+
* String Locator Styles and Scripts.
|
53 |
+
*/
|
54 |
+
wp_enqueue_style( 'string-locator-replace', trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'build/string-locator-replace.css', array(), $replace['version'] );
|
55 |
+
wp_enqueue_script( 'string-locator-replace', trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'build/string-locator-replace.js', array(), $replace['version'], true );
|
56 |
+
|
57 |
+
wp_localize_script(
|
58 |
+
'string-locator-replace',
|
59 |
+
'stringLocatorReplace',
|
60 |
+
array(
|
61 |
+
'rest_nonce' => wp_create_nonce( 'wp_rest' ),
|
62 |
+
'replace_nonce' => wp_create_nonce( 'string-locator-replace' ),
|
63 |
+
'url' => array(
|
64 |
+
'replace' => get_rest_url( null, 'string-locator/v1/replace' ),
|
65 |
+
),
|
66 |
+
'string' => array(
|
67 |
+
'replace_started' => __( 'Running replacemenets...', 'string-locator' ),
|
68 |
+
'button_show' => __( 'Show replacement controls', 'string-locator' ),
|
69 |
+
'button_hide' => __( 'Hide replacement controls', 'string-locator' ),
|
70 |
+
'confirm_all' => __( 'Are you sure you want to replace all strings?', 'string-locator' ),
|
71 |
+
),
|
72 |
+
)
|
73 |
+
);
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Output a toggle button to display the replacement form.
|
78 |
+
*
|
79 |
+
* @return void
|
80 |
+
*/
|
81 |
+
public function add_replace_button() {
|
82 |
+
printf(
|
83 |
+
'<button type="button" class="button button-link" id="string-locator-toggle-replace-controls" aria-expanded="false" aria-controls="string-locator-replace-form">%s</button>',
|
84 |
+
esc_html__( 'Show replacement controls', 'string-locator' )
|
85 |
+
);
|
86 |
+
}
|
87 |
+
|
88 |
+
/**
|
89 |
+
* Output the replacement form.
|
90 |
+
*
|
91 |
+
* @return void
|
92 |
+
*/
|
93 |
+
public function output_replace_form() {
|
94 |
+
include_once __DIR__ . '/views/replace-form.php';
|
95 |
+
}
|
96 |
+
|
97 |
+
}
|
98 |
+
|
99 |
+
new Replace();
|
includes/Extension/SearchReplace/search-replace.php
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Plugin Name: String Locator: Replacement module
|
4 |
+
*/
|
5 |
+
|
6 |
+
namespace StringLocator\Extension\SearchReplace;
|
7 |
+
|
8 |
+
// Primary extension class.
|
9 |
+
require_once __DIR__ . '/class-replace.php';
|
10 |
+
|
11 |
+
// Replacement handlers.
|
12 |
+
require_once __DIR__ . '/Replace/class-file.php';
|
13 |
+
require_once __DIR__ . '/Replace/class-sql.php';
|
14 |
+
|
15 |
+
// REST Endpoints.
|
16 |
+
require_once __DIR__ . '/REST/class-replace.php';
|
includes/Extension/SearchReplace/template/error-notice.php
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Template for notices returned form the REST API when performing a replacement.
|
4 |
+
*/
|
5 |
+
|
6 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
7 |
+
die();
|
8 |
+
}
|
9 |
+
?>
|
10 |
+
|
11 |
+
<script id="tmpl-string-locator-replace-error" type="text/template">
|
12 |
+
<div class="notice notice-error">
|
13 |
+
<p>
|
14 |
+
{{ data.message }}
|
15 |
+
</p>
|
16 |
+
</div>
|
17 |
+
</script>
|
includes/Extension/SearchReplace/views/replace-form.php
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Form output for the replace feature.
|
4 |
+
*/
|
5 |
+
|
6 |
+
namespace StringLocator;
|
7 |
+
|
8 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
9 |
+
die();
|
10 |
+
}
|
11 |
+
|
12 |
+
$this_url = admin_url( ( is_multisite() ? 'network/admin.php' : 'tools.php' ) . '?page=string-locator' );
|
13 |
+
?>
|
14 |
+
|
15 |
+
<div id="string-locator-replace-form">
|
16 |
+
<h2><?php esc_html_e( 'Replace in results', 'string-locator' ); ?></h2>
|
17 |
+
|
18 |
+
<form action="<?php echo esc_url( $this_url ); ?>" method="post">
|
19 |
+
<p>
|
20 |
+
<label for="string-locator-replace-new-string"><?php esc_html_e( 'New string', 'string-locator' ); ?></label>
|
21 |
+
<input type="text" id="string-locator-replace-new-string" name="string-locator-replace-new-string">
|
22 |
+
</p>
|
23 |
+
|
24 |
+
<p>
|
25 |
+
<label>
|
26 |
+
<input type="checkbox" name="string-locator-replace-loopback-check" id="string-locator-replace-loopback-check" checked="checked">
|
27 |
+
<?php esc_html_e( 'Perform loopback check', 'string-locator' ); ?>
|
28 |
+
</label>
|
29 |
+
|
30 |
+
<br />
|
31 |
+
|
32 |
+
<em>
|
33 |
+
<?php
|
34 |
+
// translators: The link to the WordPress.org article about loopbacks.
|
35 |
+
$url = __( 'https://wordpress.org/support/article/loopbacks/', 'string-locator' );
|
36 |
+
|
37 |
+
printf(
|
38 |
+
'<a href="%s" target="_blank">%s</a>',
|
39 |
+
esc_url( $url ),
|
40 |
+
esc_html__( 'Read more about loopbacks on WordPress.org', 'string-locator' )
|
41 |
+
);
|
42 |
+
?>
|
43 |
+
</em>
|
44 |
+
</p>
|
45 |
+
|
46 |
+
<p>
|
47 |
+
<button type="button" class="button button-primary" id="string-locator-replace-button-all">
|
48 |
+
<?php esc_html_e( 'Replace all strings', 'string-locator' ); ?>
|
49 |
+
</button>
|
50 |
+
<button type="button" class="button button-primary" id="string-locator-replace-button-selected">
|
51 |
+
<?php esc_html_e( 'Replace selected strings', 'string-locator' ); ?>
|
52 |
+
</button>
|
53 |
+
</p>
|
54 |
+
</form>
|
55 |
+
</div>
|
includes/REST/class-base.php
DELETED
@@ -1,17 +0,0 @@
|
|
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
CHANGED
@@ -1,8 +1,10 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
namespace
|
4 |
|
5 |
-
|
|
|
|
|
6 |
|
7 |
protected $rest_base = 'clean';
|
8 |
|
1 |
<?php
|
2 |
|
3 |
+
namespace StringLocator\REST;
|
4 |
|
5 |
+
use StringLocator\Base\REST;
|
6 |
+
|
7 |
+
class Clean extends REST {
|
8 |
|
9 |
protected $rest_base = 'clean';
|
10 |
|
includes/REST/class-directory-structure.php
CHANGED
@@ -1,10 +1,11 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
namespace
|
4 |
|
5 |
-
use
|
|
|
6 |
|
7 |
-
class Directory_Structure extends
|
8 |
|
9 |
protected $rest_base = 'get-directory-structure';
|
10 |
|
@@ -25,8 +26,20 @@ class Directory_Structure extends Base {
|
|
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( $
|
30 |
return new \WP_REST_Response(
|
31 |
array(
|
32 |
'success' => false,
|
@@ -37,9 +50,9 @@ class Directory_Structure extends Base {
|
|
37 |
}
|
38 |
|
39 |
$iterator = new Directory_Iterator(
|
40 |
-
$
|
41 |
-
$
|
42 |
-
$
|
43 |
);
|
44 |
|
45 |
return array(
|
1 |
<?php
|
2 |
|
3 |
+
namespace StringLocator\REST;
|
4 |
|
5 |
+
use StringLocator\Base\REST;
|
6 |
+
use StringLocator\Directory_Iterator;
|
7 |
|
8 |
+
class Directory_Structure extends REST {
|
9 |
|
10 |
protected $rest_base = 'get-directory-structure';
|
11 |
|
26 |
}
|
27 |
|
28 |
public function get_structure( \WP_REST_Request $request ) {
|
29 |
+
$short_circuit = apply_filters( 'string_locator_directory_iterator_short_circuit', array(), $request );
|
30 |
+
|
31 |
+
if ( ! empty( $short_circuit ) ) {
|
32 |
+
return $short_circuit;
|
33 |
+
}
|
34 |
+
|
35 |
+
$data = json_decode( $request->get_param( 'data' ) );
|
36 |
+
|
37 |
+
$directory = $data->directory;
|
38 |
+
$search = $data->search;
|
39 |
+
$regex = $data->regex;
|
40 |
+
|
41 |
// Validate the search path to avoid unintended directory traversal.
|
42 |
+
if ( 0 !== validate_file( $directory ) ) {
|
43 |
return new \WP_REST_Response(
|
44 |
array(
|
45 |
'success' => false,
|
50 |
}
|
51 |
|
52 |
$iterator = new Directory_Iterator(
|
53 |
+
$directory,
|
54 |
+
$search,
|
55 |
+
$regex
|
56 |
);
|
57 |
|
58 |
return array(
|
includes/REST/class-save.php
CHANGED
@@ -1,8 +1,10 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
namespace
|
4 |
|
5 |
-
|
|
|
|
|
6 |
|
7 |
protected $rest_base = 'save';
|
8 |
|
@@ -23,7 +25,7 @@ class Save extends Base {
|
|
23 |
}
|
24 |
|
25 |
public function save( \WP_REST_Request $request ) {
|
26 |
-
$handler = new \
|
27 |
|
28 |
/**
|
29 |
* Filters the REST Request parameter values that will be used for the save call.
|
1 |
<?php
|
2 |
|
3 |
+
namespace StringLocator\REST;
|
4 |
|
5 |
+
use StringLocator\Base\REST;
|
6 |
+
|
7 |
+
class Save extends REST {
|
8 |
|
9 |
protected $rest_base = 'save';
|
10 |
|
25 |
}
|
26 |
|
27 |
public function save( \WP_REST_Request $request ) {
|
28 |
+
$handler = new \StringLocator\Save();
|
29 |
|
30 |
/**
|
31 |
* Filters the REST Request parameter values that will be used for the save call.
|
includes/REST/class-search.php
CHANGED
@@ -1,8 +1,10 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
namespace
|
4 |
|
5 |
-
|
|
|
|
|
6 |
|
7 |
protected $rest_base = 'search';
|
8 |
|
@@ -23,14 +25,15 @@ class Search extends Base {
|
|
23 |
}
|
24 |
|
25 |
public function perform_search( \WP_REST_Request $request ) {
|
26 |
-
$handler = new \
|
27 |
|
28 |
/**
|
29 |
* Filter the search handler used to find strings.
|
30 |
*
|
31 |
-
* @attr object
|
|
|
32 |
*/
|
33 |
-
$handler = apply_filters( 'string_locator_search_handler', $handler );
|
34 |
|
35 |
return array(
|
36 |
'success' => true,
|
1 |
<?php
|
2 |
|
3 |
+
namespace StringLocator\REST;
|
4 |
|
5 |
+
use StringLocator\Base\REST;
|
6 |
+
|
7 |
+
class Search extends REST {
|
8 |
|
9 |
protected $rest_base = 'search';
|
10 |
|
25 |
}
|
26 |
|
27 |
public function perform_search( \WP_REST_Request $request ) {
|
28 |
+
$handler = new \StringLocator\Search();
|
29 |
|
30 |
/**
|
31 |
* Filter the search handler used to find strings.
|
32 |
*
|
33 |
+
* @attr object $handler The handler performing searches.
|
34 |
+
* @attr \WP_REST_Request $request The request received by the REST API handler.
|
35 |
*/
|
36 |
+
$handler = apply_filters( 'string_locator_search_handler', $handler, $request );
|
37 |
|
38 |
return array(
|
39 |
'success' => true,
|
includes/Tests/class-loopback.php
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
namespace
|
4 |
|
5 |
class Loopback {
|
6 |
/**
|
1 |
<?php
|
2 |
|
3 |
+
namespace StringLocator\Tests;
|
4 |
|
5 |
class Loopback {
|
6 |
/**
|
includes/Tests/class-smart-scan.php
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
namespace
|
4 |
|
5 |
class Smart_Scan {
|
6 |
|
1 |
<?php
|
2 |
|
3 |
+
namespace StringLocator\Tests;
|
4 |
|
5 |
class Smart_Scan {
|
6 |
|
includes/class-directory-iterator.php
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
namespace
|
4 |
|
5 |
class Directory_Iterator {
|
6 |
|
@@ -64,7 +64,7 @@ class Directory_Iterator {
|
|
64 |
|
65 |
$store = (object) array(
|
66 |
'scan_path' => $scan_path,
|
67 |
-
'search' =>
|
68 |
'directory' => $this->directory,
|
69 |
'chunks' => count( $file_chunks ),
|
70 |
'regex' => $this->regex,
|
1 |
<?php
|
2 |
|
3 |
+
namespace StringLocator;
|
4 |
|
5 |
class Directory_Iterator {
|
6 |
|
64 |
|
65 |
$store = (object) array(
|
66 |
'scan_path' => $scan_path,
|
67 |
+
'search' => $this->search,
|
68 |
'directory' => $this->directory,
|
69 |
'chunks' => count( $file_chunks ),
|
70 |
'regex' => $this->regex,
|
includes/class-save.php
CHANGED
@@ -1,9 +1,9 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
namespace
|
4 |
|
5 |
-
use
|
6 |
-
use
|
7 |
|
8 |
class Save {
|
9 |
|
@@ -122,7 +122,7 @@ class Save {
|
|
122 |
* @return void
|
123 |
*/
|
124 |
private function write_file( $path, $content ) {
|
125 |
-
if ( ! current_user_can(
|
126 |
return;
|
127 |
}
|
128 |
|
1 |
<?php
|
2 |
|
3 |
+
namespace StringLocator;
|
4 |
|
5 |
+
use StringLocator\Tests\Loopback;
|
6 |
+
use StringLocator\Tests\Smart_Scan;
|
7 |
|
8 |
class Save {
|
9 |
|
122 |
* @return void
|
123 |
*/
|
124 |
private function write_file( $path, $content ) {
|
125 |
+
if ( ! current_user_can( String_Locator::$default_capability ) ) {
|
126 |
return;
|
127 |
}
|
128 |
|
includes/class-search.php
CHANGED
@@ -1,8 +1,8 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
namespace
|
4 |
|
5 |
-
class Search {
|
6 |
|
7 |
/**
|
8 |
* An array of file extensions that will be ignored by the scanner.
|
@@ -25,129 +25,8 @@ class Search {
|
|
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 ) {
|
@@ -357,7 +236,7 @@ class Search {
|
|
357 |
'path' => $path,
|
358 |
'filename' => $path_string,
|
359 |
'filename_raw' => $relativepath,
|
360 |
-
'editurl' => ( current_user_can(
|
361 |
'stringresult' => $file,
|
362 |
);
|
363 |
}
|
@@ -365,7 +244,6 @@ class Search {
|
|
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
|
@@ -401,35 +279,7 @@ class Search {
|
|
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 |
-
'…%s…',
|
430 |
-
$string_preview
|
431 |
-
);
|
432 |
-
}
|
433 |
|
434 |
$path_string = sprintf(
|
435 |
'<a href="%s">%s</a>',
|
@@ -444,7 +294,7 @@ class Search {
|
|
444 |
'path' => $path,
|
445 |
'filename' => $path_string,
|
446 |
'filename_raw' => $relativepath,
|
447 |
-
'editurl' => ( current_user_can(
|
448 |
'stringresult' => $string_preview,
|
449 |
);
|
450 |
}
|
1 |
<?php
|
2 |
|
3 |
+
namespace StringLocator;
|
4 |
|
5 |
+
class Search extends Base\Search {
|
6 |
|
7 |
/**
|
8 |
* An array of file extensions that will be ignored by the scanner.
|
25 |
'wmv',
|
26 |
);
|
27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
public function __construct() {
|
29 |
+
parent::__construct();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
}
|
31 |
|
32 |
public function run( $filenum ) {
|
236 |
'path' => $path,
|
237 |
'filename' => $path_string,
|
238 |
'filename_raw' => $relativepath,
|
239 |
+
'editurl' => ( current_user_can( String_Locator::$default_capability ) ? $editurl : false ),
|
240 |
'stringresult' => $file,
|
241 |
);
|
242 |
}
|
244 |
$readfile = @fopen( $filename, 'r' );
|
245 |
if ( $readfile ) {
|
246 |
while ( ( $readline = fgets( $readfile ) ) !== false ) { // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
|
|
|
247 |
$linenum ++;
|
248 |
/**
|
249 |
* If our string is found in this line, output the line number and other data
|
279 |
*/
|
280 |
$editurl = $this->create_edit_link( $path, $linenum, $str_pos );
|
281 |
|
282 |
+
$string_preview = String_Locator::create_preview( $readline, $string, $regex );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
283 |
|
284 |
$path_string = sprintf(
|
285 |
'<a href="%s">%s</a>',
|
294 |
'path' => $path,
|
295 |
'filename' => $path_string,
|
296 |
'filename_raw' => $relativepath,
|
297 |
+
'editurl' => ( current_user_can( String_Locator::$default_capability ) ? $editurl : false ),
|
298 |
'stringresult' => $string_preview,
|
299 |
);
|
300 |
}
|
includes/class-string-locator.php
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
namespace
|
4 |
|
5 |
/**
|
6 |
* Class String_Locator
|
@@ -13,6 +13,13 @@ class String_Locator {
|
|
13 |
*/
|
14 |
public $string_locator_language = '';
|
15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
/**
|
17 |
* An array containing all notices to display.
|
18 |
*
|
@@ -48,68 +55,17 @@ class String_Locator {
|
|
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( '
|
|
|
54 |
}
|
55 |
|
56 |
-
public function
|
57 |
-
|
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
|
66 |
-
|
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 ) {
|
@@ -148,8 +104,8 @@ class String_Locator {
|
|
148 |
function plugin_row_meta( $meta, $plugin_file ) {
|
149 |
if ( 'string-locator/string-locator.php' === $plugin_file ) {
|
150 |
$meta[] = sprintf(
|
151 |
-
'<a href="https://
|
152 |
-
esc_html__( '
|
153 |
);
|
154 |
}
|
155 |
|
@@ -319,39 +275,54 @@ class String_Locator {
|
|
319 |
$item = (object) $item;
|
320 |
}
|
321 |
|
322 |
-
|
323 |
-
'<tr>
|
|
|
|
|
|
|
324 |
<td>
|
325 |
-
%s
|
326 |
<div class="row-actions">
|
327 |
-
%s
|
328 |
</div>
|
329 |
</td>
|
330 |
<td>
|
331 |
-
%s
|
332 |
</td>
|
333 |
<td>
|
334 |
-
%d
|
335 |
</td>
|
336 |
<td>
|
337 |
-
%d
|
338 |
</td>
|
339 |
</tr>',
|
340 |
$item->stringresult,
|
341 |
-
( ! current_user_can(
|
342 |
'<span class="edit"><a href="%1$s" aria-label="%2$s">%2$s</a></span>',
|
343 |
esc_url( $item->editurl ),
|
344 |
// translators: The row-action edit link label.
|
345 |
esc_html__( 'Edit', 'string-locator' )
|
346 |
) ),
|
347 |
-
( ! current_user_can(
|
348 |
'<a href="%s">%s</a>',
|
349 |
esc_url( $item->editurl ),
|
350 |
esc_html( $item->filename_raw )
|
351 |
) ),
|
|
|
352 |
esc_html( $item->linenum ),
|
|
|
353 |
esc_html( $item->linepos )
|
354 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
355 |
}
|
356 |
|
357 |
/**
|
@@ -376,14 +347,18 @@ class String_Locator {
|
|
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' ) ),
|
386 |
-
esc_html( __( 'Line number', 'string-locator' ) ),
|
387 |
esc_html( __( 'Line position', 'string-locator' ) )
|
388 |
);
|
389 |
|
@@ -393,7 +368,7 @@ class String_Locator {
|
|
393 |
}
|
394 |
|
395 |
$table = sprintf(
|
396 |
-
'<
|
397 |
implode( ' ', $table_class ),
|
398 |
$table_columns,
|
399 |
implode( "\n", $table_rows ),
|
@@ -456,14 +431,14 @@ class String_Locator {
|
|
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(
|
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( '
|
467 |
$search['version'],
|
468 |
true
|
469 |
);
|
@@ -502,7 +477,7 @@ class String_Locator {
|
|
502 |
wp_enqueue_script(
|
503 |
'string-locator-editor',
|
504 |
trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'build/string-locator.js',
|
505 |
-
array( '
|
506 |
$editor['version'],
|
507 |
true
|
508 |
);
|
@@ -512,8 +487,8 @@ class String_Locator {
|
|
512 |
'string_locator',
|
513 |
array(
|
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 |
),
|
@@ -565,7 +540,7 @@ class String_Locator {
|
|
565 |
/**
|
566 |
* Don't load anything if the user can't edit themes any way
|
567 |
*/
|
568 |
-
if ( ! current_user_can( '
|
569 |
return false;
|
570 |
}
|
571 |
|
@@ -578,8 +553,8 @@ class String_Locator {
|
|
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(
|
582 |
-
$include_path = trailingslashit( STRING_LOCATOR_PLUGIN_DIR ) . 'views/
|
583 |
} else {
|
584 |
$include_path = trailingslashit( STRING_LOCATOR_PLUGIN_DIR ) . 'views/search.php';
|
585 |
}
|
@@ -592,7 +567,7 @@ class String_Locator {
|
|
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(
|
596 |
$class .= ' file-edit-screen';
|
597 |
}
|
598 |
|
@@ -624,25 +599,69 @@ class String_Locator {
|
|
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 |
-
|
634 |
}
|
635 |
|
636 |
if ( empty( $path ) ) {
|
637 |
-
|
638 |
}
|
639 |
if ( stristr( $path, '..' ) ) {
|
640 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
641 |
}
|
642 |
-
if (
|
643 |
-
$
|
|
|
|
|
|
|
644 |
}
|
645 |
|
646 |
-
return $
|
647 |
}
|
648 |
}
|
1 |
<?php
|
2 |
|
3 |
+
namespace StringLocator;
|
4 |
|
5 |
/**
|
6 |
* Class String_Locator
|
13 |
*/
|
14 |
public $string_locator_language = '';
|
15 |
|
16 |
+
/**
|
17 |
+
* The default capability to check for when seeing if a user can access the plugin.
|
18 |
+
*
|
19 |
+
* @var string
|
20 |
+
*/
|
21 |
+
public static $default_capability = 'edit_themes';
|
22 |
+
|
23 |
/**
|
24 |
* An array containing all notices to display.
|
25 |
*
|
55 |
add_filter( 'plugin_row_meta', array( $this, 'plugin_row_meta' ), 10, 2 );
|
56 |
|
57 |
add_filter( 'string_locator_search_sources_markup', array( $this, 'add_search_options' ), 10, 2 );
|
|
|
58 |
|
59 |
+
add_action( 'string_locator_search_templates', array( $this, 'add_search_restults_templates' ) );
|
60 |
+
add_action( 'string_locator_editor_sidebar_before_checks', array( $this, 'add_instawp_reference' ) );
|
61 |
}
|
62 |
|
63 |
+
public function add_search_restults_templates() {
|
64 |
+
require_once STRING_LOCATOR_PLUGIN_DIR . '/views/templates/search-default.php';
|
|
|
|
|
|
|
|
|
|
|
65 |
}
|
66 |
|
67 |
+
public function add_instawp_reference() {
|
68 |
+
include_once STRING_LOCATOR_PLUGIN_DIR . '/views/templates/instawp.php';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
}
|
70 |
|
71 |
public function add_search_options( $searchers, $search_location ) {
|
104 |
function plugin_row_meta( $meta, $plugin_file ) {
|
105 |
if ( 'string-locator/string-locator.php' === $plugin_file ) {
|
106 |
$meta[] = sprintf(
|
107 |
+
'<a href="https://instawp.com/?utm_source=stringlocator/" target="_blank">%s</a>',
|
108 |
+
esc_html__( 'Create Disposable WordPress Sites in Seconds', 'string-locator' )
|
109 |
);
|
110 |
}
|
111 |
|
275 |
$item = (object) $item;
|
276 |
}
|
277 |
|
278 |
+
$row = sprintf(
|
279 |
+
'<tr data-type="file" data-linenum="%6$d" data-filename="%4$s">
|
280 |
+
<th scope="row" class="check-column">
|
281 |
+
<input type="checkbox" name="string-locator-replace-checked[]" class="check-column-box">
|
282 |
+
</th>
|
283 |
<td>
|
284 |
+
%1$s
|
285 |
<div class="row-actions">
|
286 |
+
%2$s
|
287 |
</div>
|
288 |
</td>
|
289 |
<td>
|
290 |
+
%3$s
|
291 |
</td>
|
292 |
<td>
|
293 |
+
%5$d
|
294 |
</td>
|
295 |
<td>
|
296 |
+
%7$d
|
297 |
</td>
|
298 |
</tr>',
|
299 |
$item->stringresult,
|
300 |
+
( ! current_user_can( String_Locator::$default_capability ) ? '' : sprintf(
|
301 |
'<span class="edit"><a href="%1$s" aria-label="%2$s">%2$s</a></span>',
|
302 |
esc_url( $item->editurl ),
|
303 |
// translators: The row-action edit link label.
|
304 |
esc_html__( 'Edit', 'string-locator' )
|
305 |
) ),
|
306 |
+
( ! current_user_can( String_Locator::$default_capability ) ? $item->filename_raw : sprintf(
|
307 |
'<a href="%s">%s</a>',
|
308 |
esc_url( $item->editurl ),
|
309 |
esc_html( $item->filename_raw )
|
310 |
) ),
|
311 |
+
esc_attr( $item->filename_raw ),
|
312 |
esc_html( $item->linenum ),
|
313 |
+
esc_attr( $item->linenum ),
|
314 |
esc_html( $item->linepos )
|
315 |
);
|
316 |
+
|
317 |
+
/**
|
318 |
+
* Enable extensions to override the table row when restoring a previous search.
|
319 |
+
*
|
320 |
+
* @attr string $row The HTML markup for the table row.
|
321 |
+
* @attr object $item The search result item data.
|
322 |
+
*/
|
323 |
+
$row = apply_filters( 'string_locator_restore_search_row', $row, $item );
|
324 |
+
|
325 |
+
return $row;
|
326 |
}
|
327 |
|
328 |
/**
|
347 |
|
348 |
$table_columns = sprintf(
|
349 |
'<tr>
|
350 |
+
<th scope="col" class="manage-column column-cb check-column">
|
351 |
+
<label class="screen-reader-text" for="cb-select-all-1">Select All</label>
|
352 |
+
<input id="cb-select-all-1" type="checkbox">
|
353 |
+
</th>
|
354 |
<th scope="col" class="manage-column column-stringresult column-primary string">%s</th>
|
355 |
<th scope="col" class="manage-column column-filename filename">%s</th>
|
356 |
<th scope="col" class="manage-column column-linenum line">%s</th>
|
357 |
<th scope="col" class="manage-column column-linepos position">%s</th>
|
358 |
</tr>',
|
359 |
esc_html( __( 'String', 'string-locator' ) ),
|
360 |
+
esc_html( __( 'File / Table', 'string-locator' ) ),
|
361 |
+
esc_html( __( 'ID / Line number', 'string-locator' ) ),
|
362 |
esc_html( __( 'Line position', 'string-locator' ) )
|
363 |
);
|
364 |
|
368 |
}
|
369 |
|
370 |
$table = sprintf(
|
371 |
+
'<table class="%s" id="string-locator-search-results-table"><thead>%s</thead><tbody>%s</tbody><tfoot>%s</tfoot></table>',
|
372 |
implode( ' ', $table_class ),
|
373 |
$table_columns,
|
374 |
implode( "\n", $table_rows ),
|
431 |
*/
|
432 |
wp_enqueue_style( 'string-locator', trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'build/string-locator.css', array(), $search['version'] );
|
433 |
|
434 |
+
if ( ! isset( $_GET['edit-file'] ) || ! current_user_can( String_Locator::$default_capability ) ) {
|
435 |
/**
|
436 |
* String Locator Scripts
|
437 |
*/
|
438 |
wp_enqueue_script(
|
439 |
'string-locator-search',
|
440 |
trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'build/string-locator-search.js',
|
441 |
+
array( 'wp-util' ),
|
442 |
$search['version'],
|
443 |
true
|
444 |
);
|
477 |
wp_enqueue_script(
|
478 |
'string-locator-editor',
|
479 |
trailingslashit( STRING_LOCATOR_PLUGIN_URL ) . 'build/string-locator.js',
|
480 |
+
array( 'code-editor', 'wp-util' ),
|
481 |
$editor['version'],
|
482 |
true
|
483 |
);
|
487 |
'string_locator',
|
488 |
array(
|
489 |
'CodeMirror' => $code_mirror,
|
490 |
+
'goto_line' => ( isset( $_GET['string-locator-line'] ) ? absint( $_GET['string-locator-line'] ) : 0 ),
|
491 |
+
'goto_linepos' => ( isset( $_GET['string-locator-linepos'] ) ? absint( $_GET['string-locator-linepos'] ) : 0 ),
|
492 |
'url' => array(
|
493 |
'save' => get_rest_url( null, 'string-locator/v1/save' ),
|
494 |
),
|
540 |
/**
|
541 |
* Don't load anything if the user can't edit themes any way
|
542 |
*/
|
543 |
+
if ( ! current_user_can( 'edit_users' ) ) {
|
544 |
return false;
|
545 |
}
|
546 |
|
553 |
* - The edit file path query var does not contains double dots (used to traverse directories)
|
554 |
* - The user is capable of editing files.
|
555 |
*/
|
556 |
+
if ( isset( $_GET['string-locator-path'] ) && self::is_valid_location( $_GET['string-locator-path'] ) && current_user_can( String_Locator::$default_capability ) ) {
|
557 |
+
$include_path = trailingslashit( STRING_LOCATOR_PLUGIN_DIR ) . 'views/editors/default.php';
|
558 |
} else {
|
559 |
$include_path = trailingslashit( STRING_LOCATOR_PLUGIN_DIR ) . 'views/search.php';
|
560 |
}
|
567 |
}
|
568 |
|
569 |
function admin_body_class( $class ) {
|
570 |
+
if ( isset( $_GET['string-locator-path'] ) && self::is_valid_location( $_GET['string-locator-path'] ) && current_user_can( String_Locator::$default_capability ) ) {
|
571 |
$class .= ' file-edit-screen';
|
572 |
}
|
573 |
|
599 |
* @return bool
|
600 |
*/
|
601 |
public static function is_valid_location( $path ) {
|
|
|
602 |
$path = str_replace( array( '/' ), array( DIRECTORY_SEPARATOR ), stripslashes( $path ) );
|
603 |
$abspath = str_replace( array( '/' ), array( DIRECTORY_SEPARATOR ), ABSPATH );
|
604 |
|
605 |
+
/*
|
606 |
+
* Check that the ABSPath is the start of the path.
|
607 |
+
* This helps ensure that no protocol triggers can be used as part of the file path.
|
608 |
+
*/
|
609 |
+
if ( substr( $path, 0, strlen( $abspath ) ) !== $abspath ) {
|
610 |
+
return false;
|
611 |
+
}
|
612 |
+
|
613 |
// Check that it is a valid file we are trying to access as well.
|
614 |
if ( ! file_exists( $path ) ) {
|
615 |
+
return false;
|
616 |
}
|
617 |
|
618 |
if ( empty( $path ) ) {
|
619 |
+
return false;
|
620 |
}
|
621 |
if ( stristr( $path, '..' ) ) {
|
622 |
+
return false;
|
623 |
+
}
|
624 |
+
|
625 |
+
return true;
|
626 |
+
}
|
627 |
+
|
628 |
+
public static function create_preview( $string_preview, $string, $regex = false ) {
|
629 |
+
/**
|
630 |
+
* Define class variables requiring expressions
|
631 |
+
*/
|
632 |
+
$excerpt_length = apply_filters( 'string_locator_excerpt_length', 25 );
|
633 |
+
|
634 |
+
$string_preview_is_cut = false;
|
635 |
+
|
636 |
+
if ( strlen( $string_preview ) > ( strlen( $string ) + $excerpt_length ) ) {
|
637 |
+
$string_location = strpos( $string_preview, $string );
|
638 |
+
|
639 |
+
$string_location_start = $string_location - $excerpt_length;
|
640 |
+
if ( $string_location_start < 0 ) {
|
641 |
+
$string_location_start = 0;
|
642 |
+
}
|
643 |
+
|
644 |
+
$string_location_end = ( strlen( $string ) + ( $excerpt_length * 2 ) );
|
645 |
+
if ( $string_location_end > strlen( $string_preview ) ) {
|
646 |
+
$string_location_end = strlen( $string_preview );
|
647 |
+
}
|
648 |
+
|
649 |
+
$string_preview = substr( $string_preview, $string_location_start, $string_location_end );
|
650 |
+
$string_preview_is_cut = true;
|
651 |
+
}
|
652 |
+
|
653 |
+
if ( $regex ) {
|
654 |
+
$string_preview = preg_replace( preg_replace( '/\/(.+)\//', '/($1)/', $string ), '<strong>$1</strong>', esc_html( $string_preview ) );
|
655 |
+
} else {
|
656 |
+
$string_preview = preg_replace( '/(' . preg_quote( $string ) . ')/i', '<strong>$1</strong>', esc_html( $string_preview ) );
|
657 |
}
|
658 |
+
if ( $string_preview_is_cut ) {
|
659 |
+
$string_preview = sprintf(
|
660 |
+
'…%s…',
|
661 |
+
$string_preview
|
662 |
+
);
|
663 |
}
|
664 |
|
665 |
+
return $string_preview;
|
666 |
}
|
667 |
}
|
readme.txt
CHANGED
@@ -1,12 +1,11 @@
|
|
1 |
=== String locator ===
|
2 |
-
Contributors: Clorith
|
3 |
-
Author URI:
|
4 |
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:
|
9 |
-
Stable tag: 2.
|
10 |
License: GPLv2 or later
|
11 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
12 |
|
@@ -22,6 +21,8 @@ You can then quickly make edits directly in your browser by clicking the link fr
|
|
22 |
By default a consistency check is performed when making edits to files, this will look for inconsistencies with braces, brackets and parenthesis that are often accidentally left in.
|
23 |
This drastically reduces the risk of breaking your site when making edits, but is in no way an absolute guarantee.
|
24 |
|
|
|
|
|
25 |
|
26 |
== Frequently asked questions ==
|
27 |
|
@@ -45,14 +46,19 @@ When writing your search string, make sure to wrap your search in forward slashe
|
|
45 |
|
46 |
== Changelog ==
|
47 |
|
48 |
-
= 2.
|
49 |
-
*
|
50 |
-
*
|
51 |
-
*
|
52 |
-
*
|
53 |
-
*
|
54 |
-
*
|
55 |
-
*
|
|
|
|
|
|
|
|
|
|
|
56 |
|
57 |
= Older entries =
|
58 |
-
See changelog.txt for the version history
|
1 |
=== String locator ===
|
2 |
+
Contributors: InstaWP, Clorith
|
3 |
+
Author URI: https://instawp.com/
|
4 |
Plugin URI: http://wordpress.org/plugins/string-locator/
|
|
|
5 |
Tags: text, search, find, syntax, highlight
|
6 |
Requires at least: 4.9
|
7 |
+
Tested up to: 6.0
|
8 |
+
Stable tag: 2.6.0
|
9 |
License: GPLv2 or later
|
10 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
11 |
|
21 |
By default a consistency check is performed when making edits to files, this will look for inconsistencies with braces, brackets and parenthesis that are often accidentally left in.
|
22 |
This drastically reduces the risk of breaking your site when making edits, but is in no way an absolute guarantee.
|
23 |
|
24 |
+
[Create Disposable WordPress Sites in Seconds](https://instawp.com/?utm_source=stringlocator)
|
25 |
+
|
26 |
|
27 |
== Frequently asked questions ==
|
28 |
|
46 |
|
47 |
== Changelog ==
|
48 |
|
49 |
+
= 2.6.0 (2022-07-20) =
|
50 |
+
* Added database search feature.
|
51 |
+
* Added tools for quickly replacing data in the search results.
|
52 |
+
* Added many more filters and actions.
|
53 |
+
* Added hardening of file path checks.
|
54 |
+
* Removed one-time donation notice.
|
55 |
+
* Removed jQuery dependency in favor of vanilla JavaScript code.
|
56 |
+
* Separated search class into a base class for extenders.
|
57 |
+
* Fixed bug with code viewer sizes when resizing your window.
|
58 |
+
* Fixed bug in the list view if special characters were in the search string.
|
59 |
+
* Fixed a bug where RegEx search validation may have a false positive check for invalid patterns.
|
60 |
+
* Fixed missing translator function if Javascript is missing.
|
61 |
+
* Improved capability checks for displaying the search interface when editing is disabled.
|
62 |
|
63 |
= Older entries =
|
64 |
+
See changelog.txt for the version history.
|
string-locator.php
CHANGED
@@ -1,15 +1,16 @@
|
|
1 |
<?php
|
2 |
/**
|
3 |
* Plugin Name: String Locator
|
4 |
-
* Plugin URI: http://
|
5 |
* Description: Scan through theme and plugin files looking for text strings
|
6 |
-
* Version: 2.
|
7 |
-
* Author:
|
8 |
-
* Author URI:
|
9 |
* Text Domain: string-locator
|
10 |
* License: GPL2
|
11 |
*
|
12 |
* Copyright 2013 Marius Jensen (email : marius@clorith.net)
|
|
|
13 |
*
|
14 |
* This program is free software; you can redistribute it and/or modify
|
15 |
* it under the terms of the GNU General Public License, version 2, as
|
@@ -25,7 +26,7 @@
|
|
25 |
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
26 |
*/
|
27 |
|
28 |
-
namespace
|
29 |
|
30 |
if ( ! defined( 'ABSPATH' ) ) {
|
31 |
die();
|
@@ -33,6 +34,19 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
33 |
|
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
|
@@ -50,7 +64,6 @@ require_once __DIR__ . '/includes/class-directory-iterator.php';
|
|
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';
|
1 |
<?php
|
2 |
/**
|
3 |
* Plugin Name: String Locator
|
4 |
+
* Plugin URI: http://wordpress.org/plugins/string-locator/
|
5 |
* Description: Scan through theme and plugin files looking for text strings
|
6 |
+
* Version: 2.6.0
|
7 |
+
* Author: InstaWP
|
8 |
+
* Author URI: https://instawp.com/
|
9 |
* Text Domain: string-locator
|
10 |
* License: GPL2
|
11 |
*
|
12 |
* Copyright 2013 Marius Jensen (email : marius@clorith.net)
|
13 |
+
* 2022 InstaWP (https://instawp.com)
|
14 |
*
|
15 |
* This program is free software; you can redistribute it and/or modify
|
16 |
* it under the terms of the GNU General Public License, version 2, as
|
26 |
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
27 |
*/
|
28 |
|
29 |
+
namespace StringLocator;
|
30 |
|
31 |
if ( ! defined( 'ABSPATH' ) ) {
|
32 |
die();
|
34 |
|
35 |
define( 'STRING_LOCATOR_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
|
36 |
define( 'STRING_LOCATOR_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
37 |
+
define( 'STRING_LOCATOR_PLUGIN_FILE', __FILE__ );
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Base classes that other classes may extend.
|
41 |
+
*/
|
42 |
+
require_once __DIR__ . '/includes/Base/class-search.php';
|
43 |
+
require_once __DIR__ . '/includes/Base/class-rest.php';
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Search handlers
|
47 |
+
*/
|
48 |
+
require_once __DIR__ . '/includes/Extension/SQL/sql.php';
|
49 |
+
require_once __DIR__ . '/includes/Extension/SearchReplace/search-replace.php';
|
50 |
|
51 |
/**
|
52 |
* Plugin test runners
|
64 |
/**
|
65 |
* Prepare REST endpoints.
|
66 |
*/
|
|
|
67 |
require_once __DIR__ . '/includes/REST/class-save.php';
|
68 |
require_once __DIR__ . '/includes/REST/class-clean.php';
|
69 |
require_once __DIR__ . '/includes/REST/class-search.php';
|
views/assets/instawp-logo.svg
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<svg width="42" height="34" viewBox="0 0 42 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
2 |
+
<path d="M34.738 12.6667H40.5506C40.6488 12.6664 40.7458 12.692 40.8314 12.7409C40.917 12.7899 40.9887 12.8605 41.0381 12.9456C41.0881 13.0307 41.114 13.1274 41.1146 13.226C41.1152 13.3246 41.0899 13.4216 41.0417 13.5073L30.5818 32.0658C30.4227 32.3485 30.1913 32.5838 29.9111 32.7477C29.631 32.9115 29.3128 32.998 28.9881 32.9983H24.5384C24.1678 32.9983 23.8057 32.8855 23.5008 32.6749C23.1953 32.4643 22.9616 32.1659 22.8302 31.8192L21.7408 28.9567L21.8174 29.0083C22.1656 29.2337 22.5561 29.3851 22.9652 29.4532C23.3749 29.5213 23.7937 29.5046 24.1962 29.4041C24.5986 29.3036 24.9758 29.1215 25.3054 28.8691C25.6344 28.6167 25.9085 28.2994 26.1098 27.9367L32.3863 16.6971H27.3787C27.2715 16.6993 27.166 16.6707 27.0744 16.6149C26.9828 16.559 26.9087 16.4781 26.8617 16.3818C26.8147 16.2854 26.7961 16.1776 26.8081 16.071C26.8202 15.9645 26.8629 15.8636 26.9304 15.7803L39.5149 0.129175C39.5787 0.060403 39.6643 0.0159868 39.7571 0.00355936C39.8505 -0.00886809 39.9445 0.0114701 40.024 0.0610772C40.1035 0.110684 40.1638 0.186462 40.1933 0.275389C40.2228 0.364315 40.221 0.460836 40.1873 0.548362L34.738 12.6667Z" fill="url(#paint0_linear_6_19)"/>
|
3 |
+
<path d="M14.3008 31.8751C14.3008 32.0223 14.2719 32.168 14.2153 32.304C14.1592 32.44 14.0767 32.5635 13.9725 32.6677C13.8688 32.7717 13.7447 32.8543 13.6091 32.9106C13.473 32.9669 13.3271 32.9959 13.1801 32.9959H8.40749C8.17612 32.9959 7.95077 32.9245 7.76158 32.7912C7.57238 32.658 7.42959 32.4695 7.35186 32.2517L6.78488 30.64L0.963238 14.1796C0.902382 14.0085 0.883702 13.8253 0.908405 13.6455C0.933712 13.4656 1.0012 13.2944 1.10604 13.1462C1.21088 12.9979 1.34946 12.877 1.51094 12.7935C1.67181 12.71 1.85076 12.6664 2.03212 12.6665H6.39263C6.67823 12.6663 6.9572 12.7543 7.19159 12.9183C7.42597 13.0824 7.60371 13.3145 7.70132 13.5833L13.9218 30.6198L14.2406 31.4895C14.2828 31.6135 14.3032 31.744 14.3008 31.8751Z" fill="#056960"/>
|
4 |
+
<path d="M19.1404 22.1422L15.5541 32.2789C15.4788 32.4905 15.3396 32.6736 15.1564 32.8032C14.9733 32.9328 14.7539 33.0026 14.5298 33.003H8.4075C8.17552 33.0025 7.94957 32.93 7.76038 32.7955C7.57118 32.661 7.42839 32.4711 7.35187 32.252L6.78247 30.6403C6.89454 30.8241 7.82906 32.2587 8.99255 30.6201L14.9733 14.2382C14.9733 14.2382 15.6909 13.8033 16.1301 14.2382L19.1404 22.1422Z" fill="#0D4943"/>
|
5 |
+
<path d="M27.228 25.9437L24.5227 30.7789C24.4118 30.9767 24.2473 31.1389 24.0479 31.2462C23.8478 31.3536 23.6219 31.4017 23.3959 31.3851C23.17 31.3683 22.9531 31.2874 22.7717 31.1519C22.5904 31.0163 22.4512 30.8317 22.3704 30.6198L19.1403 22.1419L16.2397 14.5203C16.177 14.3603 16.0601 14.2276 15.9089 14.1455C15.7583 14.0635 15.5829 14.0374 15.4148 14.072C15.2576 14.1049 15.1057 14.1623 14.9666 14.2424C15.1148 13.8276 15.3775 13.4632 15.7239 13.191C16.1469 12.852 16.6729 12.6671 17.2146 12.6665H21.2142C21.6293 12.6656 22.0342 12.7927 22.3747 13.0306C22.7145 13.2685 22.973 13.6056 23.1146 13.9958L27.2684 25.3699C27.3039 25.4627 27.3184 25.5622 27.3118 25.6612C27.3046 25.7603 27.2762 25.8567 27.228 25.9437Z" fill="#056960"/>
|
6 |
+
<defs>
|
7 |
+
<linearGradient id="paint0_linear_6_19" x1="30.5994" y1="28" x2="39.0994" y2="-3.29879e-07" gradientUnits="userSpaceOnUse">
|
8 |
+
<stop stop-color="#FE8551"/>
|
9 |
+
<stop offset="1" stop-color="#ED618E"/>
|
10 |
+
</linearGradient>
|
11 |
+
</defs>
|
12 |
+
</svg>
|
views/{editor.php → editors/default.php}
RENAMED
@@ -1,6 +1,6 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
namespace
|
4 |
|
5 |
if ( ! defined( 'ABSPATH' ) ) {
|
6 |
die();
|
@@ -38,6 +38,17 @@ if ( 'core' === $_GET['file-type'] ) {
|
|
38 |
'description' => $themedata->get( 'Description' ),
|
39 |
'parent' => $themedata->get( 'parent' ),
|
40 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
41 |
} else {
|
42 |
$plugins = get_plugins();
|
43 |
|
@@ -58,10 +69,12 @@ if ( 'core' === $_GET['file-type'] ) {
|
|
58 |
}
|
59 |
}
|
60 |
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
$
|
|
|
|
|
65 |
}
|
66 |
}
|
67 |
?>
|
@@ -100,7 +113,7 @@ if ( $readfile ) {
|
|
100 |
<div id="string-locator-notices">
|
101 |
<div class="row notice notice-error inline below-h2 hide-if-js">
|
102 |
<p>
|
103 |
-
|
104 |
</p>
|
105 |
</div>
|
106 |
|
@@ -162,6 +175,8 @@ if ( $readfile ) {
|
|
162 |
</div>
|
163 |
</div>
|
164 |
|
|
|
|
|
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">
|
@@ -169,6 +184,8 @@ if ( $readfile ) {
|
|
169 |
</div>
|
170 |
</div>
|
171 |
|
|
|
|
|
172 |
<div class="string-locator-panel">
|
173 |
<h2 class="title"><?php esc_html_e( 'File information', 'string-locator' ); ?></h2>
|
174 |
<div class="string-locator-panel-body">
|
@@ -262,6 +279,6 @@ if ( $readfile ) {
|
|
262 |
<div class="row notice notice-{{ data.type }} inline below-h2 is-dismissible">
|
263 |
{{{ data.message }}}
|
264 |
|
265 |
-
<button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'string-locator' ); ?></span></button>
|
266 |
</div>
|
267 |
</script>
|
1 |
<?php
|
2 |
|
3 |
+
namespace StringLocator;
|
4 |
|
5 |
if ( ! defined( 'ABSPATH' ) ) {
|
6 |
die();
|
38 |
'description' => $themedata->get( 'Description' ),
|
39 |
'parent' => $themedata->get( 'parent' ),
|
40 |
);
|
41 |
+
} elseif ( 'sql' === $_GET['file-type'] ) {
|
42 |
+
$details = array(
|
43 |
+
'name' => 'Name',
|
44 |
+
'version' => 'Version',
|
45 |
+
'author' => array(
|
46 |
+
'uri' => 'author URI',
|
47 |
+
'name' => 'author name',
|
48 |
+
),
|
49 |
+
'description' => 'description',
|
50 |
+
'parent' => 'parent',
|
51 |
+
);
|
52 |
} else {
|
53 |
$plugins = get_plugins();
|
54 |
|
69 |
}
|
70 |
}
|
71 |
|
72 |
+
if ( 'sql' !== $_GET['file-type'] ) {
|
73 |
+
$readfile = fopen( $file, 'r' );
|
74 |
+
if ( $readfile ) {
|
75 |
+
while ( ( $readline = fgets( $readfile ) ) !== false ) { // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
|
76 |
+
$editor_content .= $readline;
|
77 |
+
}
|
78 |
}
|
79 |
}
|
80 |
?>
|
113 |
<div id="string-locator-notices">
|
114 |
<div class="row notice notice-error inline below-h2 hide-if-js">
|
115 |
<p>
|
116 |
+
<?php esc_html_e( 'The editor requires Javascript to be enabled before it can be used.', 'string-locator' ); ?>
|
117 |
</p>
|
118 |
</div>
|
119 |
|
175 |
</div>
|
176 |
</div>
|
177 |
|
178 |
+
<?php do_action( 'string_locator_editor_sidebar_before_checks' ); ?>
|
179 |
+
|
180 |
<div class="string-locator-panel">
|
181 |
<h2 class="title"><?php esc_html_e( 'Save checks', 'string-locator' ); ?></h2>
|
182 |
<div class="string-locator-panel-body">
|
184 |
</div>
|
185 |
</div>
|
186 |
|
187 |
+
<?php do_action( 'string_locator_editor_sidebar_after_checks' ); ?>
|
188 |
+
|
189 |
<div class="string-locator-panel">
|
190 |
<h2 class="title"><?php esc_html_e( 'File information', 'string-locator' ); ?></h2>
|
191 |
<div class="string-locator-panel-body">
|
279 |
<div class="row notice notice-{{ data.type }} inline below-h2 is-dismissible">
|
280 |
{{{ data.message }}}
|
281 |
|
282 |
+
<button type="button" class="notice-dismiss" onclick="this.closest( '.notice' ).remove()"><span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'string-locator' ); ?></span></button>
|
283 |
</div>
|
284 |
</script>
|
views/search.php
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
namespace
|
4 |
|
5 |
if ( ! defined( 'ABSPATH' ) ) {
|
6 |
die();
|
@@ -48,7 +48,7 @@ if ( isset( $_GET['restore'] ) ) {
|
|
48 |
</strong>
|
49 |
</p>
|
50 |
<p>
|
51 |
-
<?php esc_html_e( 'Because this site is configured to not allow direct file editing, the String Locator plugin has limited functionality and
|
52 |
</p>
|
53 |
</div>
|
54 |
<?php endif; ?>
|
@@ -71,17 +71,52 @@ if ( isset( $_GET['restore'] ) ) {
|
|
71 |
<p>
|
72 |
<input type="submit" name="submit" id="submit" class="button button-primary" value="<?php esc_html_e( 'Search', 'string-locator' ); ?>">
|
73 |
<a href="<?php echo esc_url( $this_url . '&restore=true' ); ?>" class="button button-primary"><?php esc_html_e( 'Restore last search', 'string-locator' ); ?></a>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
</p>
|
75 |
</form>
|
76 |
|
77 |
-
<div class="notices"></div>
|
78 |
|
79 |
-
<div class="string-locator-feedback hide">
|
80 |
<progress id="string-locator-search-progress" max="100"></progress>
|
81 |
<span id="string-locator-feedback-text"><?php esc_html_e( 'Preparing search…', 'string-locator' ); ?></span>
|
82 |
</div>
|
83 |
|
84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
<?php
|
86 |
if ( isset( $_GET['restore'] ) ) {
|
87 |
$items = get_option( 'string-locator-search-history', array() );
|
@@ -93,38 +128,14 @@ if ( isset( $_GET['restore'] ) ) {
|
|
93 |
}
|
94 |
?>
|
95 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
96 |
</div>
|
97 |
|
98 |
-
|
99 |
-
<tr>
|
100 |
-
<td>
|
101 |
-
{{{ data.stringresult }}}
|
102 |
-
|
103 |
-
<div class="row-actions">
|
104 |
-
<# if ( data.editurl ) { #>
|
105 |
-
<span class="edit">
|
106 |
-
<a href="{{ data.editurl }}" aria-label="<?php esc_attr_e( 'Edit', 'string-locator' ); ?>">
|
107 |
-
<?php esc_html_e( 'Edit', 'string-locator' ); ?>
|
108 |
-
</a>
|
109 |
-
</span>
|
110 |
-
<# } #>
|
111 |
-
</div>
|
112 |
-
</td>
|
113 |
-
<td>
|
114 |
-
<# if ( data.editurl ) { #>
|
115 |
-
<a href="{{ data.editurl }}">
|
116 |
-
{{ data.filename_raw }}
|
117 |
-
</a>
|
118 |
-
<# } #>
|
119 |
-
<# if ( ! data.editurl ) { #>
|
120 |
-
{{ data.filename_raw }}
|
121 |
-
<# } #>
|
122 |
-
</td>
|
123 |
-
<td>
|
124 |
-
{{ data.linenum }}
|
125 |
-
</td>
|
126 |
-
<td>
|
127 |
-
{{ data.linepos }}
|
128 |
-
</td>
|
129 |
-
</tr>
|
130 |
-
</script>
|
1 |
<?php
|
2 |
|
3 |
+
namespace StringLocator;
|
4 |
|
5 |
if ( ! defined( 'ABSPATH' ) ) {
|
6 |
die();
|
48 |
</strong>
|
49 |
</p>
|
50 |
<p>
|
51 |
+
<?php esc_html_e( 'Because this site is configured to not allow direct file editing, the String Locator plugin has limited functionality and may noy allow you to directly edit files with your string in them.', 'string-locator' ); ?>
|
52 |
</p>
|
53 |
</div>
|
54 |
<?php endif; ?>
|
71 |
<p>
|
72 |
<input type="submit" name="submit" id="submit" class="button button-primary" value="<?php esc_html_e( 'Search', 'string-locator' ); ?>">
|
73 |
<a href="<?php echo esc_url( $this_url . '&restore=true' ); ?>" class="button button-primary"><?php esc_html_e( 'Restore last search', 'string-locator' ); ?></a>
|
74 |
+
|
75 |
+
<?php
|
76 |
+
/**
|
77 |
+
* Provides an area for outputting additional markup or controls
|
78 |
+
* immediately following the button controllers for the String Locator
|
79 |
+
* search form, but within the same paragraph for easier styling where needed.
|
80 |
+
*/
|
81 |
+
do_action( 'string_locator_after_search_buttons' );
|
82 |
+
?>
|
83 |
</p>
|
84 |
</form>
|
85 |
|
86 |
+
<div class="notices" id="string-locator-search-notices"></div>
|
87 |
|
88 |
+
<div class="string-locator-feedback hide" id="string-locator-progress-wrapper">
|
89 |
<progress id="string-locator-search-progress" max="100"></progress>
|
90 |
<span id="string-locator-feedback-text"><?php esc_html_e( 'Preparing search…', 'string-locator' ); ?></span>
|
91 |
</div>
|
92 |
|
93 |
+
<?php
|
94 |
+
/**
|
95 |
+
* Provides an action for outputting additional markup or controls
|
96 |
+
* immediately preceding the table displaying search results.
|
97 |
+
*/
|
98 |
+
do_action( 'string_locator_before_search_results_table' );
|
99 |
+
|
100 |
+
$wrapper_classes = array(
|
101 |
+
'table-wrapper',
|
102 |
+
);
|
103 |
+
|
104 |
+
if ( isset( $_GET['restore'] ) ) {
|
105 |
+
$wrapper_classes[] = 'restore';
|
106 |
+
}
|
107 |
+
?>
|
108 |
+
|
109 |
+
<div id="string-locator-search-results-table-wrapper" class="<?php echo esc_attr( implode( ' ', $wrapper_classes ) ); ?>">
|
110 |
+
<div class="tablenav top">
|
111 |
+
<?php
|
112 |
+
/**
|
113 |
+
* An action to output controls in the tablenav region, which only
|
114 |
+
* become visible when there are search results available.
|
115 |
+
*/
|
116 |
+
do_action( 'string_locator_search_results_tablenav_controls' );
|
117 |
+
?>
|
118 |
+
<br class="clear" />
|
119 |
+
</div>
|
120 |
<?php
|
121 |
if ( isset( $_GET['restore'] ) ) {
|
122 |
$items = get_option( 'string-locator-search-history', array() );
|
128 |
}
|
129 |
?>
|
130 |
</div>
|
131 |
+
|
132 |
+
<?php
|
133 |
+
/**
|
134 |
+
* Provides an action for outputting additional markup or controls
|
135 |
+
* immediately following the table displaying search results.
|
136 |
+
*/
|
137 |
+
do_action( 'string_locator_after_search_results_table' );
|
138 |
+
?>
|
139 |
</div>
|
140 |
|
141 |
+
<?php do_action( 'string_locator_search_templates' ); ?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
views/templates/instawp.php
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
die();
|
4 |
+
}
|
5 |
+
?>
|
6 |
+
|
7 |
+
<div class="string-locator-panel">
|
8 |
+
<h2 class="title">
|
9 |
+
<img src="<?php echo plugins_url( 'views/assets/instawp-logo.svg', STRING_LOCATOR_PLUGIN_FILE ); ?>" alt="<?php esc_attr_e( 'InstaWP logo', 'string-locator' ); ?>">
|
10 |
+
<span>
|
11 |
+
<?php esc_html_e( 'Create a disposable site', 'string-locator' ); ?>
|
12 |
+
</span>
|
13 |
+
</h2>
|
14 |
+
<div class="string-locator-panel-body">
|
15 |
+
<div class="row">
|
16 |
+
<a href="https://instawp.com/?utm_source=stringlocator" target="_blank">
|
17 |
+
<?php esc_html_e( 'Create Disposable WordPress Sites in Seconds.', 'string-locator' ); ?>
|
18 |
+
</a>
|
19 |
+
</div>
|
20 |
+
</div>
|
21 |
+
</div>
|
views/templates/search-default.php
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
die();
|
4 |
+
}
|
5 |
+
?>
|
6 |
+
<script id="tmpl-string-locator-search-result" type="text/template">
|
7 |
+
<tr data-type="file" data-linenum="{{ data.linenum }}" data-filename="{{ data.filename_raw }}">
|
8 |
+
<th scope="row" class="check-column">
|
9 |
+
<input type="checkbox" name="string-locator-replace-checked[]" class="check-column-box">
|
10 |
+
</th>
|
11 |
+
<td>
|
12 |
+
{{{ data.stringresult }}}
|
13 |
+
|
14 |
+
<div class="row-actions">
|
15 |
+
<# if ( data.editurl ) { #>
|
16 |
+
<span class="edit">
|
17 |
+
<a href="{{ data.editurl }}" aria-label="<?php esc_attr_e( 'Edit', 'string-locator' ); ?>">
|
18 |
+
<?php esc_html_e( 'Edit', 'string-locator' ); ?>
|
19 |
+
</a>
|
20 |
+
</span>
|
21 |
+
<# } #>
|
22 |
+
</div>
|
23 |
+
</td>
|
24 |
+
<td>
|
25 |
+
<# if ( data.editurl ) { #>
|
26 |
+
<a href="{{ data.editurl }}">
|
27 |
+
{{ data.filename_raw }}
|
28 |
+
</a>
|
29 |
+
<# } #>
|
30 |
+
<# if ( ! data.editurl ) { #>
|
31 |
+
{{ data.filename_raw }}
|
32 |
+
<# } #>
|
33 |
+
</td>
|
34 |
+
<td>
|
35 |
+
{{ data.linenum }}
|
36 |
+
</td>
|
37 |
+
<td>
|
38 |
+
{{ data.linepos }}
|
39 |
+
</td>
|
40 |
+
</tr>
|
41 |
+
</script>
|