PluginProbe
WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards / 5.5.84
WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards v5.5.84
5.5.84 5.5.83 5.5.82 5.5.81 5.5.80 5.5.79 5.5.77 5.5.76 5.5.75 5.5.73 5.5.72 5.5.22 5.5.23 5.5.29 5.5.3 5.5.31 5.5.32 5.5.34 5.5.35 5.5.36 5.5.37 5.5.4 5.5.40 5.5.41 5.5.42 All 160 releases
wp-data-access / WPDataAccess / Global_Search / WPDA_Global_Search.php

WPDA_Global_Search.php in WP Data Access – App Builder for Tables, Forms, Charts, Maps & Dashboards 5.5.84, at WPDataAccess/Global_Search/WPDA_Global_Search.php

1,012 lines 32.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPDataAccess\Global_Search {
4
5 use WPDataAccess\Connection\WPDADB;
6 use WPDataAccess\Data_Dictionary\WPDA_Dictionary_Lists;
7 use WPDataAccess\Data_Dictionary\WPDA_List_Columns_Cache;
8 use WPDataAccess\WPDA;
9
10 class WPDA_Global_Search {
11
12 const NONCE_SEED = 'wpda-global-search-and-replace-';
13
14 protected $databases = array();
15
16 public function __construct() {
17 $dbs = WPDA_Dictionary_Lists::get_db_schemas();
18 foreach ( $dbs as $db ) {
19 $this->databases[] = $db['schema_name'];
20 }
21 }
22
23 public function show() {
24 $this->css();
25 $this->js();
26
27 $esc_attr = 'esc_attr';
28 ?>
29 <div class="wrap">
30 <h1 class="wp-heading-inline">
31 <span style="vertical-align: text-top">Search & Replace</span>
32 </h1>
33
34 <div id="tabs">
35 <ul>
36 <li><a href="#tabs-1">Search</a></li>
37 <li><a href="#tabs-2">Replace</a></li>
38 </ul>
39 <div id="tabs-1">
40 <div>
41 <input type="text" id="searchbox" placeholder="Search for..." onblur="jQuery('#replace_searchbox').val(jQuery(this).val())" />
42 <label>
43 <input type="checkbox" id="searchbox-case" onblur="jQuery('#replace_searchbox-case').prop('checked', jQuery(this).is(':checked'))" />Case-sensitive
44 </label>
45 <button type="button" class="button button-primary" onclick="startSearch()">SEARCH</button>
46 </div>
47 </div>
48 <div id="tabs-2">
49 <div>
50 <label for="replace_searchbox">
51 Search
52 </label>
53 <input type="text" id="replace_searchbox" placeholder="Search for..." onblur="jQuery('#searchbox').val(jQuery(this).val())" />
54 <label>
55 <input type="checkbox" id="replace_searchbox-case" onblur="jQuery('#searchbox-case').prop('checked', jQuery(this).is(':checked'))" />Case-sensitive
56 </label>
57 </div>
58 <div>
59 <label for="replace_replacebox">
60 Replace
61 </label>
62 <input type="text" id="replace_replacebox" placeholder="Replace with..." />
63 <button type="button" class="button button-primary" onclick="startReplace()">SEARCH</button>
64 </div>
65 </div>
66 </div>
67
68 <fieldset class="wpda_fieldset">
69 <legend>
70 Select databases and tables
71 </legend>
72 <div id="selectionFrame">
73 <div id="selectionDatabases">
74 <div class="selectionFrameHeader">
75 Available databases
76 <i class="fas fa-database"></i>
77 </div>
78 <div class="selectionFrameBody">
79 <?php
80 global $wpdb;
81 foreach ( $this->databases as $database ) {
82 $dbname = $database === $wpdb->dbname ? "WordPress database ({$esc_attr( $database )})" : esc_attr( $database );
83
84 // phpcs:ignore PluginCheck.CodeAnalysis.Heredoc.NotAllowed
85 $dbs = <<< EOL
86 <div class="selectionFrameBodyElement"
87 onclick="selectSchema('{$esc_attr( $database )}')"
88 id="{$esc_attr( $database )}"
89 >
90 <input type="checkbox" id="chk_{$esc_attr( $database )}" />
91 {$esc_attr( $dbname )}
92 </div>
93 EOL;
94 echo $dbs; // phpcs:ignore WordPress.Security.EscapeOutput
95 }
96 ?>
97 </div>
98 <div class="selectionFrameFooter">
99 <i class="fas fa-info-circle"></i> Click on a database name to select tables
100 </div>
101 </div>
102 <div id="selectionTables">
103 <div class="selectionFrameHeader">
104 <span>
105 <label>
106 <input type="checkbox"
107 title="Select all/Unselect all"
108 class="wpda_tooltip"
109 id="toggleAllTables"
110 onclick="toggleAllTables()"
111 />
112 Available tables
113 </label>
114 <label style="padding-left:10px;display:none;" id="showWordPressTables">
115 <input type="checkbox"
116 title="Select all/Unselect all"
117 class="wpda_tooltip"
118 id="toggleWordPressTables"
119 onclick="toggleWordPressTables()"
120 />
121 WordPress tables
122 </label>
123 </span>
124 <i class="fas fa-table"></i>
125 </div>
126 <div class="selectionFrameBody">
127 <div class="selectionFrameBodySpinner">
128 <div>
129 <i class="fa fa-spinner fa-spin"></i>
130 <br/><br/>
131 <span class="blink_me">
132 Loading tables...
133 </span>
134 </div>
135 </div>
136 </div>
137 <div class="selectionFrameFooter">
138 <i class="fas fa-info-circle"></i> Select tables to be searched
139 </div>
140 </div>
141 </div>
142 </fieldset>
143
144 <fieldset id="wpda_searchresults_panel" class="wpda_fieldset" style="display:none">
145 <legend>
146 Search results
147 </legend>
148
149 <div id="wpda_searchresults"></div>
150 <div class="selectionFrameFooter">
151 <i class="fas fa-info-circle"></i> <span id="totalMatches">0</span> total rows
152 </div>
153 </fieldset>
154 </div>
155 <?php
156 }
157
158 private function css() {
159 ?>
160 <style>
161 :root {
162 --height: 30vh;
163 }
164
165 fieldset {
166 margin-top: 10px;
167 }
168
169 #searchbox {
170 width: 100%;
171 }
172
173 #selectionFrame {
174 position: relative;
175 height: calc(var(--height) + 115px);
176 overflow: hidden;
177 }
178 #selectionDatabases,
179 #selectionTables {
180 position: absolute;
181 top: 0;
182 width: calc(50% - 5px);
183 }
184 #selectionTables {
185 right: 0;
186 }
187 .selectionFrameHeader {
188 position: relative;
189 padding: 18px 16px;
190 background-color: #acacac;
191 font-weight: bold;
192 border-radius: 5px 5px 0 0;
193 }
194 .selectionFrameHeader i.fas {
195 display: inline-block;
196 position: absolute;
197 top: 22px;
198 right: 20px;
199 }
200 .selectionFrameBody {
201 height: var(--height);
202 overflow-y: scroll;
203 border-left: 1px solid #acacac;
204 border-right: 1px solid #acacac;
205 }
206 .selectionFrameBody > div:nth-child(even) {
207 background-color: #fff;
208 }
209 .selectionFrameBody > div {
210 padding: 18px 16px;
211 }
212 .selectionFrameBody > div:not(.selectionFrameBodySpinner):hover {
213 background-color: rgba(255, 255, 0, 0.5);
214 font-weight: bold;
215 }
216 .selectionFrameBodyElement {
217 cursor: pointer;
218 }
219 .selectionFrameFooter {
220 padding: 18px 16px;
221 background-color: #acacac;
222 border-radius: 0 0 5px 5px;
223 }
224 .selectionFrameFooter i.fas {
225 margin-right: 10px;
226 }
227 .databaseSelected {
228 background-color: rgba(255, 255, 0) !important;
229 font-weight: bold;
230 }
231 .selectionFrameBodySpinner {
232 height: calc(var(--height) - 55px);
233 display: flex;
234 justify-content: center;
235 align-items: center;
236 }
237 .selectionFrameBodySpinner > div {
238 text-align: center;
239 }
240 .selectionFrameBodySpinner i {
241 font-size: 300%;
242 }
243 .blink_me {
244 animation: blinker 1s linear infinite;
245 }
246 @keyframes blinker {
247 50% {
248 opacity: 0;
249 }
250 }
251
252 #wpda_searchstart {
253 text-align: end;
254 margin-top: 2px;
255 }
256
257 #wpda_searchresults .wpdaSchemaOutput {
258 background-color: #acacac;
259 padding: 18px 16px;
260 }
261 #wpda_searchresults .wpdaTableOutput {
262 padding: 18px 16px 18px 36px;
263 border-left: 1px solid #acacac;
264 border-right: 1px solid #acacac;
265 display: flex;
266 justify-content: space-between;
267 align-items: center;
268 }
269 #wpda_searchresults .wpdaTableOutput:nth-child(even) {
270 background-color: #fff;
271 }
272 #wpda_searchresults .wpdaSchemaOutput:first-child {
273 border-radius: 5px 5px 0 0;
274 position: relative;
275 }
276 #wpda_searchresults .wpdaTableOutput span {
277 line-height: 30px;
278 }
279 #wpda_searchresults .wpdaTableOutput button {
280 line-height: 28px;
281 margin-left: 5px;
282 }
283 #wpda_searchresults .wpdaSchemaOutput i.fas,
284 #wpda_searchresults .wpdaTableOutput i.fas {
285 margin-right: 10px;
286 }
287 #wpda_searchresults .wpdaTableOutput:hover {
288 background-color: rgba(255, 255, 0, 0.5);
289 font-weight: bold;
290 }
291 #wpda_searchresults .wpdaTableOutput a {
292 text-decoration: none;
293 }
294 .match {
295 display: inline-block;
296 width: 5px;
297 height: 0;
298 border-right: 15px solid transparent;
299 vertical-align: sub;
300 }
301 .matches {
302 border-top: 8px solid transparent;
303 border-right: 15px solid #555;
304 border-bottom: 8px solid transparent;
305 }
306 .disableLink {
307 color: unset;
308 pointer-events: none;
309 }
310 #wpda_searchresults .wpdaSchemaOutput label {
311 line-height: 30px;
312 vertical-align: initial;
313 position: absolute;
314 right: 12px;
315 top: 13px;
316 }
317 .communication_error {
318 cursor: pointer;
319 }
320 #tabs {
321 padding: 0;
322 background: none;
323 border-width: 0;
324 }
325 #tabs .ui-tabs-nav {
326 padding-left: 0;
327 background: transparent;
328 border-width: 0 0 1px 0;
329 border-color: #ccd0d4;
330 -moz-border-radius: 0;
331 -webkit-border-radius: 0;
332 border-radius: 0;
333 }
334 #tabs .ui-tabs-panel {
335 background: #fff;
336 border-width: 0 1px 1px 1px;
337 border-color: #ccd0d4;
338 }
339 .ui-tabs-anchor {
340 font-weight: bold;
341 }
342 #tabs-1,
343 #tabs-2 {
344 margin: 0;
345 padding: 10px;
346 }
347 #tabs-1 label {
348 text-align: center;
349 }
350 #tabs-1 > div {
351 display: grid;
352 grid-template-columns: auto 120px 110px;
353 grid-column-gap: 5px;
354 align-items: center;
355 }
356 #tabs-2 > div:first-child {
357 display: grid;
358 grid-template-columns: 65px auto 120px;
359 align-items: center;
360 margin-bottom: 5px;
361 }
362 #tabs-2 > div:last-child {
363 display: grid;
364 grid-template-columns: 65px auto 110px;
365 align-items: center;
366 }
367 #tabs-2 > div:last-child input[type=text] {
368 margin-right: 10px;
369 }
370 #tabs-2 label {
371 padding-left: 10px;
372 }
373 .wpda_tooltip_sar_css {
374 max-width: 600px;
375 background: black;
376 color: white;
377 border: 2px solid white;
378 font: bold 10px "Helvetica Neue", Sans-Serif;
379 box-shadow: 0 0 7px black;
380 }
381 </style>
382 <?php
383 }
384
385 private function js() {
386 global $wpdb;
387 $wp_database = $wpdb->dbname;
388 ?>
389 <script>
390 var wpdaDatabases = <?php echo json_encode( $this->databases ); ?>;
391 var wpDatabase = "<?php echo esc_attr( $wp_database ); ?>";
392 var wpdaTables = [];
393 var wpTables = <?php echo json_encode( array_keys( WPDA::get_wp_tables() ) ); ?>;
394 var schemasProcessed = 0;
395 var totalMatches = 0;
396 var replaceAllSelectedTables = false;
397
398 function updateTotalMatches(n) {
399 totalMatches += n;
400 jQuery("#totalMatches").html(totalMatches);
401 }
402
403 function cleanId(id) {
404 return id.replaceAll(".", "\\.").replaceAll(":", "\\:")
405 }
406
407 function showTableResults(schemaName, tableName, rows) {
408 jQuery(cleanId(`#result_${schemaName}_${tableName}`)).html(rows + " rows");
409 jQuery(cleanId(`#result_${schemaName}_${tableName}`)).closest("a").find(".linkSpinner").hide();
410
411 if (parseInt(rows)!==NaN && parseInt(rows)>0) {
412 jQuery(cleanId(`#result_${schemaName}_${tableName}`)).closest("div").find(".match").addClass("matches");
413 updateTotalMatches(parseInt(rows));
414 jQuery(cleanId(`#result_${schemaName}_${tableName}`)).closest("a").removeClass("disableLink");
415 jQuery(cleanId(`#view_${schemaName}_${tableName}`)).closest("span").find("button").show();
416 }
417 }
418
419 function submitForm(schemaName, tableName) {
420 if (jQuery("#matchesInNewTab").is(":checked")) {
421 jQuery(cleanId(`#view_${schemaName}_${tableName}`)).attr("target", "_blank");
422 } else {
423 jQuery(cleanId(`#view_${schemaName}_${tableName}`)).removeAttr("target");
424 }
425
426 jQuery(cleanId(`#view_${schemaName}_${tableName}`)).submit();
427 }
428
429 function showTableOutput(schemaName, tableName, addReplaceButtons, searchString, searchCase, replaceString) {
430 let button = addReplaceButtons ?
431 `<button type="button" class="button button-secondary replace-table-button" style="display:none" onclick="replace('${schemaName}', '${tableName}', '${searchString}', '${searchCase}', '${replaceString}')">Replace</button>` : '<span></span>';
432
433
434 jQuery("#wpda_searchresults").append(`
435 <div class="wpdaTableOutput">
436 <span>
437 <i class="fas fa-table"></i>
438 ${tableName}
439 </span>
440 <span>
441 <form id="view_${schemaName}_${tableName}" action="?page=wpda_table" method="post" style="display:none">
442 <input type="hidden" name="dbs" value="${schemaName}" />
443 <input type="hidden" name="tbl" value="${tableName}" />
444 <input type="hidden" name="s" value="${searchString}" />
445 <input type="hidden" name="c" value="${searchCase}" />
446 </form>
447 <a href="javascript:submitForm('${schemaName}', '${tableName}')" class="disableLink">
448 <span class="linkSpinner">
449 <i class="fa fa-spinner fa-spin"></i>
450 Searching...
451 </span>
452 <span id="result_${schemaName}_${tableName}"></span>
453 </a>
454 ${button}
455 <span class="match"></span>
456 </span>
457 </div>
458 `);
459 }
460
461 function showSchemaOutput(schemaName, i, addReplaceButtons, searchString, replaceString) {
462 let selection = "";
463 let button = addReplaceButtons ?
464 `<button type="button" class="button button-secondary" onclick="replaceAll('${searchString}', '${replaceString}')">Replace All</button>` :
465 '<span style="display:inline-block;width:5px"></span>';
466
467 if (i===0) {
468 // Add selection to open links in new tab|window
469 selection = `<label>
470 <input type="checkbox" id="matchesInNewTab" checked />
471 <span>Open rows in new tab or window</span>
472 ${button}
473 </label>`;
474 }
475
476 jQuery("#wpda_searchresults").append(`
477 <div class="wpdaSchemaOutput">
478 <i class="fas fa-database"></i>
479 ${schemaName}
480 ${selection}
481 </div>
482 `);
483 }
484
485 function replace(schemaName, tableName, searchString, searchCase, replaceString) {
486 if (!replaceAllSelectedTables) {
487 wpda_confirm("Replace?", `Replace all occurences of <strong>${searchString}</strong> with <strong>${replaceString}</strong> in table <strong>${tableName}</strong>?<br/><br/>This action cannot be undone!<br/>Are you sure you want to continue?`).then(function (a) {
488 if (a) {
489 replaceTable(schemaName, tableName, searchString, searchCase, replaceString);
490 }
491 });
492 } else {
493 replaceTable(schemaName, tableName, searchString, searchCase, replaceString);
494 }
495 }
496
497 function replaceAll(searchString, replaceString) {
498 wpda_confirm("Replace?",`Replace all occurences of <strong>${searchString}</strong> with <strong>${replaceString}</strong> in all selected tables?<br/><br/>This action cannot be undone!<br/>Are you sure you want to continue?`).then(function(a) {
499 if (a) {
500 replaceAllSelectedTables = true; // :-\
501 jQuery(".replace-table-button:visible").trigger("click", "test");
502 replaceAllSelectedTables = false; // :-/
503 }
504 });
505 }
506
507 function showError(schemaName, tableName) {
508 jQuery(cleanId(`#result_${schemaName}_${tableName}`)).html("");
509 jQuery(cleanId(`#result_${schemaName}_${tableName}`)).closest("a").find(".linkSpinner").hide();
510
511 jQuery(cleanId(`#view_${schemaName}_${tableName}`)).closest("span").append(`
512 <span class="communication_error wpda_tooltip" title="Please check the console for more information">
513 ERROR
514 <i class="fas fa-exclamation-triangle"></i>
515 </span>
516 `);
517 jQuery(".wpda_tooltip").tooltip();
518 }
519
520 function searchTable(schemaName, tableName, searchString, searchCase) {
521 jQuery.ajax({
522 method: "POST",
523 url: "<?php echo esc_url( admin_url( 'admin-ajax.php?action=wpda_global_search' ) ); ?>",
524 data: {
525 n: "<?php echo esc_attr( wp_create_nonce( self::NONCE_SEED . WPDA::get_current_user_login() ) ); ?>",
526 sn: schemaName,
527 tn: tableName,
528 q: searchString,
529 c: searchCase
530 }
531 }).done(
532 function(msg) {
533 if (msg.status==="OK") {
534 showTableResults(schemaName, tableName, msg.msg);
535 } else {
536 console.error("Communication error on table " + tableName + ":", msg);
537 showError(schemaName, tableName);
538 }
539 }
540 );
541 }
542
543 function replaceTable(schemaName, tableName, searchString, searchCase, replaceString) {
544 jQuery.ajax({
545 method: "POST",
546 url: "<?php echo esc_url( admin_url( 'admin-ajax.php?action=wpda_global_replace' ) ); ?>",
547 data: {
548 n: "<?php echo esc_attr( wp_create_nonce( self::NONCE_SEED . WPDA::get_current_user_login() ) ); ?>",
549 sn: schemaName,
550 tn: tableName,
551 q: searchString,
552 c: searchCase,
553 r: replaceString
554 }
555 }).done(
556 function(msg) {
557 if (msg.status==="OK") {
558 wpda_alert("Update results", "Updated " + msg.msg + " rows in table " + tableName + ".")
559 } else {
560 console.error("Communication error on table " + tableName + ":", msg);
561 showError(schemaName, tableName);
562 }
563 }
564 );
565 }
566
567 function startReplace() {
568 if (jQuery("#replace_searchbox").val().trim()==="") {
569 wpda_alert("Oops!", "Nothing to search. Please enter a search string in the search box above.");
570 return;
571 }
572
573 startSearch(true);
574 }
575
576 function startSearch(addReplaceButtons = false) {
577 if (jQuery("#searchbox").val().trim()==="") {
578 wpda_alert("Oops!", "Nothing to search. Please enter a search string in the search box above.");
579 return;
580 } else {
581 jQuery("#wpda_searchresults").empty();
582 totalMatches = 0;
583 updateTotalMatches(0);
584
585 if (jQuery("#selectionDatabases .selectionFrameBody div input[type=checkbox]:checked").length>0) {
586 jQuery("#wpda_searchresults_panel").show();
587 } else {
588 wpda_alert("No tables selected", "Please select one or more tables and try again.");
589 }
590
591 let searchString = jQuery("#searchbox").val();
592 let searchCase = jQuery("#searchbox-case").is(":checked");
593 let replaceString = jQuery("#replace_replacebox").val();
594 jQuery("#selectionDatabases .selectionFrameBody div input[type=checkbox]:checked").each(function (i, elem) {
595 schemaName = jQuery(elem).closest("div").attr("id");
596 showSchemaOutput(schemaName, i, addReplaceButtons, searchString, replaceString);
597
598 for (tableName in wpdaTables[schemaName]) {
599 if (wpdaTables[schemaName][tableName]) {
600 showTableOutput(schemaName, tableName, addReplaceButtons, searchString, searchCase, replaceString)
601 searchTable(schemaName, tableName, searchString, searchCase);
602 }
603 }
604 });
605 }
606 }
607
608 function toggleAllTables() {
609 let isChecked = jQuery("#toggleAllTables").is(":checked");
610 jQuery("#selectionTables .selectionFrameBody input[type=checkbox]").prop(
611 "checked",
612 isChecked
613 );
614
615 let schemaName = jQuery(".databaseSelected").attr("id");
616
617 for (tableName in wpdaTables[schemaName]) {
618 wpdaTables[schemaName][tableName] = isChecked;
619 }
620
621 jQuery("#chk_" + cleanId(schemaName)).prop(
622 "checked",
623 jQuery("#selectionTables .selectionFrameBody input[type=checkbox]:checked").length>0
624 );
625
626 if (!isChecked) {
627 jQuery("#toggleWordPressTables").prop("checked", false);
628 }
629 }
630
631 function toggleWordPressTables() {
632 if (jQuery("#toggleAllTables").is(":checked")) {
633 return; // nothing to do
634 }
635
636 let isChecked = jQuery("#toggleWordPressTables").is(":checked");
637 let jumpTo = false;
638 jQuery("#selectionTables .selectionFrameBody input[type=checkbox]").each(function() {
639 let tableName = jQuery(this).attr("id");
640 if (wpTables.includes(tableName)) {
641 wpdaTables[wpDatabase][tableName] = isChecked;
642 jQuery("#" + tableName).prop("checked", isChecked);
643 if (isChecked && !jumpTo) {
644 jQuery("#" + tableName).focus();
645 jumpTo = true;
646 }
647 }
648 });
649
650 jQuery("#chk_" + wpDatabase).prop(
651 "checked",
652 jQuery("#selectionTables .selectionFrameBody input[type=checkbox]:checked").length>0
653 );
654 }
655
656 function toggleTable(schemaName, tableName) {
657 if (event.target.type!=="checkbox") {
658 wpdaTables[schemaName][tableName] = !jQuery("#" + tableName).prop("checked");
659 jQuery("#" + tableName).prop("checked", wpdaTables[schemaName][tableName]);
660 } else {
661 wpdaTables[schemaName][tableName] = jQuery("#" + tableName).prop("checked");
662 }
663
664 if (wpdaTables[schemaName][tableName]) {
665 // At least one table selected: enable search for current database
666 jQuery("#chk_" + cleanId(schemaName)).prop("checked", true);
667 } else {
668 if (jQuery("#selectionTables .selectionFrameBody input[type=checkbox]:checked").length===0) {
669 // No tables selected: disable search for current database
670 jQuery("#chk_" + cleanId(schemaName)).prop("checked", false);
671 }
672 }
673 }
674
675 function selectSchema(schemaName) {
676 if (wpDatabase===schemaName) {
677 jQuery("#showWordPressTables").show();
678 } else {
679 jQuery("#showWordPressTables").hide();
680 }
681
682 jQuery("#selectionTables .selectionFrameBody").empty();
683
684 let tables = wpdaTables[schemaName];
685 if (tables===undefined) {
686 alert("No tables found for schema " + schemaName);
687 } else {
688 for (tableName in tables) {
689 let checked = tables[tableName] ? 'checked' : '';
690 let newTable = `
691 <div class="selectionFrameBodyElement"
692 onclick="toggleTable('${schemaName}', '${tableName}')"
693 >
694 <input type="checkbox" id="${tableName}" ${checked} class="selectionTable" />
695 ${tableName}
696 </div>`;
697 jQuery("#selectionTables .selectionFrameBody").append(newTable);
698 }
699
700 jQuery("#selectionDatabases .databaseSelected").removeClass("databaseSelected");
701 jQuery("#" + cleanId(schemaName)).addClass("databaseSelected");
702
703 jQuery("#selectionTables .selectionFrameHeader input[type=checkbox]").prop("checked", false);
704 }
705 }
706
707 function getTables(schemaName) {
708 var url = location.pathname + '?action=wpda_get_tables';
709 var data = {
710 wpdaschema_name: schemaName,
711 wpda_wpnonce: '<?php echo esc_attr( wp_create_nonce( 'wpda-getdata-access-' . WPDA::get_current_user_login() ) ); ?>'
712 };
713 jQuery.post(
714 url,
715 data,
716 function (data) {
717 try {
718 let testIfJson = JSON.parse(data);
719 if (typeof testIfJson == "object") {
720 let tables = [];
721 jQuery.each(testIfJson, function (i, item) {
722 tables[item.table_name] = false;
723 });
724 wpdaTables[schemaName] = tables;
725 } else {
726 console.error("Invalid table list for database " + schemaName, testIfJson);
727 }
728 } catch {
729 console.error("Invalid table list for database " + schemaName, data);
730 } finally {
731 schemasProcessed++;
732 if (schemasProcessed===wpdaDatabases.length) {
733 jQuery(".selectionFrameBodySpinner").remove();
734 }
735 }
736 }
737 );
738 }
739
740 function getDatabases() {
741 for (let i=0; i<wpdaDatabases.length; i++) {
742 getTables(wpdaDatabases[i]);
743 }
744 }
745
746 function enableTooltip() {
747 jQuery(".wpda_tooltip_left").tooltip({
748 tooltipClass: "wpda_tooltip_dashboard",
749 track: true
750 });
751 jQuery(".wpda_tooltip_sar").tooltip({
752 tooltipClass: "wpda_tooltip_sar_css",
753 });
754 }
755
756 jQuery(function() {
757 jQuery("#tabs").tabs();
758
759 getDatabases();
760 enableTooltip();
761 });
762 </script>
763 <?php
764 }
765
766 public static function search() {
767 self::check_request();
768
769 $schema_name = sanitize_text_field( wp_unslash( $_POST['sn'] ) ); // phpcs:ignore
770 $table_name = sanitize_text_field( wp_unslash( $_POST['tn'] ) ); // phpcs:ignore
771 $search_value = sanitize_text_field( wp_unslash( $_POST['q'] ) ); // phpcs:ignore
772
773 $wpdadb = WPDADB::get_db_connection( $schema_name );
774 if ( null !== $wpdadb ) {
775 $wpdadb->suppress_errors( true );
776 }
777
778 // Get column info.
779 $wpda_list_columns = WPDA_List_Columns_Cache::get_list_columns( $schema_name, $table_name );
780 $columns = $wpda_list_columns->get_table_columns();
781 if ( ! is_array( $columns ) ) {
782 // Table not found.
783 WPDA::sent_header( 'application/json' );
784 WPDA::sent_msg( 'ERROR', 'Internal SQL error' );
785 die();
786 }
787
788 // Determine case-sensitive search
789 $search_case = 'true' === $_POST['c']; // phpcs:ignore
790
791 // Perform query
792 $result = self::execute_query( $wpdadb, $schema_name, $table_name, $columns, $search_value, $search_case, true );
793
794 // Process query results
795 if ( '' === $wpdadb->last_error && is_array( $result ) && count( $result ) > 0 ) { // phpcs:ignore -- 8.1 proof
796 WPDA::sent_header( 'application/json' );
797 WPDA::sent_msg( 'OK', $result[0][0] );
798 die();
799 }
800
801 WPDA::sent_header( 'application/json' );
802 WPDA::sent_msg( 'ERROR', $wpdadb->last_error );
803 die();
804 }
805
806 private static function execute_query( $wpdadb, $schema_name, $table_name, $columns, $search_value, $search_case, $just_count = false ) {
807 if ( 'rdb:' === substr( $schema_name, 0, 4) ) {
808 // Remote database
809 $query = true === $just_count ?
810 'select count(*) from `%1s`' : 'select * from `%1s`';
811
812 // Define query.
813 $query = $wpdadb->prepare(
814 $query,
815 array(
816 WPDA::remove_backticks( $table_name ),
817 )
818 );
819 } else {
820 // Local database
821 $query = true === $just_count ?
822 'select count(*) from `%1s`.`%1s`' : 'select * from `%1s`.`%1s`';
823
824 // Define query.
825 $query = $wpdadb->prepare(
826 $query,
827 array(
828 WPDA::remove_backticks( $schema_name ),
829 WPDA::remove_backticks( $table_name ),
830 )
831 );
832 }
833
834 // Construct where clause.
835 $where = WPDA::construct_where_clause(
836 $schema_name,
837 $table_name,
838 $columns,
839 $search_value,
840 $search_case
841 );
842 if ( trim( $where ) !== '' ) {
843 $query .= " where {$where} ";
844 }
845
846 // Perform query.
847 return $wpdadb->get_results( $query, ( true === $just_count ? 'ARRAY_N' : 'ARRAY_A' ) );
848 }
849
850 public static function replace() {
851 self::check_request();
852
853 if ( ! isset( $_POST['r'] ) ) { // phpcs:ignore
854 WPDA::sent_header( 'application/json' );
855 WPDA::sent_msg( 'ERROR', 'Invalid arguments' );
856 die();
857 }
858
859 $schema_name = sanitize_text_field( wp_unslash( $_POST['sn'] ) ); // phpcs:ignore
860 $table_name = sanitize_text_field( wp_unslash( $_POST['tn'] ) ); // phpcs:ignore
861 $search_value = sanitize_text_field( wp_unslash( $_POST['q'] ) ); // phpcs:ignore
862 $replace_value = sanitize_text_field( wp_unslash( $_POST['r'] ) ); // phpcs:ignore
863
864 $wpdadb = WPDADB::get_db_connection( $schema_name );
865 if ( null !== $wpdadb ) {
866 $wpdadb->suppress_errors( true );
867 }
868
869 // Get column info.
870 $wpda_list_columns = WPDA_List_Columns_Cache::get_list_columns( $schema_name, $table_name );
871 $columns = $wpda_list_columns->get_table_columns();
872 if ( ! is_array( $columns ) ) {
873 // Table not found.
874 WPDA::sent_header( 'application/json' );
875 WPDA::sent_msg( 'ERROR', 'Internal SQL error' );
876 die();
877 }
878
879 // Determine case-sensitive search
880 if ( 'true' === $_POST['c'] ) { // phpcs:ignore
881 // Case-sensitive search and replace
882 // Use built-in SQL replace function
883
884 // Define query.
885 $query = $wpdadb->prepare(
886 'update `%1s`.`%1s` set ', // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders
887 array(
888 WPDA::remove_backticks( $schema_name ),
889 WPDA::remove_backticks( $table_name ),
890 )
891 );
892
893 // Add columns to be updated.
894 $set = '';
895 foreach ( $columns as $column ) {
896 if ( 'string' === WPDA::get_type( $column['data_type'] ) ) {
897 if ( '' !== $set ) {
898 $set .= ',';
899 }
900 $set .= $wpdadb->prepare(
901 " `%1s` = replace( `%1s`, '%s', '%s' ) ",
902 array(
903 WPDA::remove_backticks( $column['column_name'] ),
904 WPDA::remove_backticks( $column['column_name'] ),
905 $search_value,
906 $replace_value
907 )
908 );
909 }
910 }
911 if ( '' === $set ) {
912 WPDA::sent_header( 'application/json' );
913 WPDA::sent_msg( 'ERROR', 'No updatable columns found in this table' );
914 die();
915 }
916
917 $query .= $set;
918
919 // Construct where clause.
920 $where = WPDA::construct_where_clause(
921 $schema_name,
922 $table_name,
923 $columns,
924 $search_value
925 );
926 if ( trim( $where ) !== '' ) {
927 $query .= " where {$where} ";
928 }
929
930 // Perform update.
931 $wpdadb->get_results( $query, 'ARRAY_A' );
932 if ( '' === $wpdadb->last_error ) {
933 WPDA::sent_header( 'application/json' );
934 WPDA::sent_msg( 'OK', $wpdadb->rows_affected );
935 die();
936 }
937 } else {
938 // Case-insensitive search and replace
939 // There is no standard SQL function to replace case-insensitive, so we need to implement our own solution
940
941 // Determine updatable columns
942 $update_columns = array();
943 foreach ( $columns as $column ) {
944 if ( 'string' === WPDA::get_type( $column['data_type'] ) ) {
945 $update_columns[] = $column['column_name'];
946 }
947 }
948
949 // Determine primary key for record update
950 $pk = $wpda_list_columns->get_table_primary_key();
951 $rows_affected = 0;
952
953 // Perform query
954 $results = self::execute_query( $wpdadb, $schema_name, $table_name, $columns, $search_value, false );
955 foreach ( $results as $result ) {
956 $update_values = array();
957 $pk_values = array();
958
959 foreach ( $update_columns as $update_column ) {
960 if ( false !== stripos( $result[ $update_column ], $search_value ) ) {
961 $update_values[ $update_column ] = str_ireplace( $search_value, $replace_value, $result[ $update_column ] );
962 foreach ( $pk as $key ) {
963 $pk_values[ $key ] = $result[ $key ];
964 }
965 }
966 }
967
968 // Update row
969 $wpdadb->update(
970 $table_name,
971 $update_values,
972 // phpcs:ignore -- 8.1 proof
973 ( is_array( $pk ) && count( $pk ) > 0 ? $pk_values : $result ) // fall back to all cols if no pk
974 );
975 $rows_affected += $wpdadb->rows_affected;
976 }
977
978 WPDA::sent_header( 'application/json' );
979 WPDA::sent_msg( 'OK', $rows_affected );
980 die();
981 }
982
983 WPDA::sent_header( 'application/json' );
984 WPDA::sent_msg( 'ERROR', $wpdadb->last_error );
985 die();
986 }
987
988 private static function check_request() {
989 $wpnonce = isset( $_POST['n'] ) ? sanitize_text_field( wp_unslash( $_POST['n'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
990 if ( ! wp_verify_nonce( $wpnonce, self::NONCE_SEED . WPDA::get_current_user_login() ) ) {
991 WPDA::sent_header( 'application/json' );
992 WPDA::sent_msg( 'ERROR', 'Not authorized' );
993 die();
994 }
995
996 if ( ! isset( $_POST['sn'], $_POST['tn'], $_POST['q'], $_POST['c'] ) || '' === $_POST['sn'] || '' === $_POST['tn'] ) {
997 WPDA::sent_header( 'application/json' );
998 WPDA::sent_msg( 'ERROR', 'Invalid arguments' );
999 die();
1000 }
1001
1002 if ( '' === $_POST['q'] ) {
1003 WPDA::sent_header( 'application/json' );
1004 WPDA::sent_msg( 'ERROR', 'Nothing to search' );
1005 die();
1006 }
1007 }
1008
1009 }
1010
1011 }
1012