Version Description
Download this release
Release Info
Developer | WebFactory |
Plugin | WP Reset – Fastest WordPress Reset Plugin |
Version | 1.40 |
Comparing to | |
See all releases |
Code changes from version 1.35 to 1.40
- css/wp-reset.css +230 -4
- js/wp-reset.js +170 -4
- libs/diff.php +179 -0
- libs/diff/Renderer/Abstract.php +82 -0
- libs/diff/Renderer/Html/Array.php +225 -0
- libs/diff/Renderer/Html/Inline.php +143 -0
- libs/diff/Renderer/Html/SideBySide.php +163 -0
- libs/diff/Renderer/Text/Context.php +128 -0
- libs/diff/Renderer/Text/Unified.php +87 -0
- libs/diff/SequenceMatcher.php +742 -0
- libs/dumper.php +514 -0
- readme.txt +19 -4
- wp-reset.php +713 -25
css/wp-reset.css
CHANGED
@@ -18,7 +18,9 @@
|
|
18 |
text-align: center;
|
19 |
}
|
20 |
|
21 |
-
.tools_page_wp-reset.wp-core-ui .button,
|
|
|
|
|
22 |
border-radius: 0;
|
23 |
}
|
24 |
|
@@ -99,9 +101,12 @@
|
|
99 |
|
100 |
.tools_page_wp-reset .card {
|
101 |
padding: 1em 2em 1em 2em;
|
|
|
102 |
}
|
103 |
|
104 |
-
.tools_page_wp-reset .card.collapsed p,
|
|
|
|
|
105 |
display: none;
|
106 |
}
|
107 |
|
@@ -109,6 +114,137 @@
|
|
109 |
border-radius: 0;
|
110 |
}
|
111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
@-moz-keyframes spin {
|
113 |
100% {
|
114 |
-moz-transform: rotate(-360deg);
|
@@ -196,7 +332,7 @@
|
|
196 |
font-weight: 600;
|
197 |
line-height: 32px;
|
198 |
color: #AAAAAA;
|
199 |
-
padding: 10px
|
200 |
display: block;
|
201 |
letter-spacing: 1px;
|
202 |
}
|
@@ -221,7 +357,7 @@
|
|
221 |
display: inline-block;
|
222 |
font-size: 12px;
|
223 |
line-height: 16px;
|
224 |
-
margin: 0px
|
225 |
text-decoration: none;
|
226 |
text-shadow: none;
|
227 |
background: rgba(255, 255, 255, 0.7);
|
@@ -265,3 +401,93 @@
|
|
265 |
-webkit-box-shadow: none;
|
266 |
}
|
267 |
/* tabs */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
text-align: center;
|
19 |
}
|
20 |
|
21 |
+
.tools_page_wp-reset.wp-core-ui .button,
|
22 |
+
.tools_page_wp-reset.wp-core-ui .button-primary,
|
23 |
+
.tools_page_wp-reset.wp-core-ui .button-secondary {
|
24 |
border-radius: 0;
|
25 |
}
|
26 |
|
101 |
|
102 |
.tools_page_wp-reset .card {
|
103 |
padding: 1em 2em 1em 2em;
|
104 |
+
width: 520px;
|
105 |
}
|
106 |
|
107 |
+
.tools_page_wp-reset .card.collapsed p,
|
108 |
+
.tools_page_wp-reset .card.collapsed b,
|
109 |
+
.tools_page_wp-reset .card.collapsed ul {
|
110 |
display: none;
|
111 |
}
|
112 |
|
114 |
border-radius: 0;
|
115 |
}
|
116 |
|
117 |
+
.tools_page_wp-reset .create-new-snapshot-corner {
|
118 |
+
position: absolute;
|
119 |
+
top: 2em;
|
120 |
+
right: 2em;
|
121 |
+
}
|
122 |
+
|
123 |
+
.tools_page_wp-reset table {
|
124 |
+
width: calc(100% + 4em);
|
125 |
+
border-spacing: 0;
|
126 |
+
border-collapse: separate;
|
127 |
+
margin: 0 0 0 -2em;
|
128 |
+
}
|
129 |
+
|
130 |
+
.tools_page_wp-reset table td {
|
131 |
+
text-align: left;
|
132 |
+
padding: 5px;
|
133 |
+
}
|
134 |
+
|
135 |
+
.tools_page_wp-reset table tr:nth-child(even) td {
|
136 |
+
background-color: #f9f9f9;
|
137 |
+
}
|
138 |
+
|
139 |
+
.tools_page_wp-reset table .ss-actions {
|
140 |
+
width: 55px;
|
141 |
+
}
|
142 |
+
|
143 |
+
.tools_page_wp-reset table th {
|
144 |
+
text-align: left;
|
145 |
+
border-bottom: thin solid #444444;
|
146 |
+
padding: 5px;
|
147 |
+
}
|
148 |
+
|
149 |
+
.tools_page_wp-reset table .ss-action {
|
150 |
+
text-decoration: none;
|
151 |
+
margin: 0 7px 5px 0;
|
152 |
+
display: inline-block;
|
153 |
+
}
|
154 |
+
|
155 |
+
.tools_page_wp-reset table .delete-snapshot {
|
156 |
+
margin: 0;
|
157 |
+
}
|
158 |
+
|
159 |
+
.tools_page_wp-reset .no-padding-bottom {
|
160 |
+
padding-bottom: 0;
|
161 |
+
}
|
162 |
+
|
163 |
+
.wpr-table-container {
|
164 |
+
min-width: 960px;
|
165 |
+
max-width: 1200px;
|
166 |
+
margin: 0 auto 15px auto;
|
167 |
+
}
|
168 |
+
|
169 |
+
.swal2-popup.compare-snapshots {
|
170 |
+
min-height: 90%;
|
171 |
+
flex-direction: column;
|
172 |
+
justify-content: flex-start;
|
173 |
+
}
|
174 |
+
|
175 |
+
.wpr-table-container pre {
|
176 |
+
font-size: 14px;
|
177 |
+
overflow: auto;
|
178 |
+
max-width: 580px;
|
179 |
+
}
|
180 |
+
|
181 |
+
.wpr-table-container p {
|
182 |
+
font-size: 14px;
|
183 |
+
color: #545454;
|
184 |
+
}
|
185 |
+
|
186 |
+
.wpr-table-container table {
|
187 |
+
border-spacing: 0;
|
188 |
+
border-collapse: separate;
|
189 |
+
margin: 0;
|
190 |
+
padding: 0;
|
191 |
+
width: 100%;
|
192 |
+
position: relative;
|
193 |
+
}
|
194 |
+
|
195 |
+
.wpr-table-container table span.dashicons {
|
196 |
+
position: absolute;
|
197 |
+
right: 10px;
|
198 |
+
top: 13px;
|
199 |
+
}
|
200 |
+
|
201 |
+
.wpr-table-container table td {
|
202 |
+
width: 50%;
|
203 |
+
padding: 10px;
|
204 |
+
}
|
205 |
+
|
206 |
+
.wpr-table-container > table > tbody > tr:first-child {
|
207 |
+
cursor: pointer;
|
208 |
+
}
|
209 |
+
|
210 |
+
.wpr-table-container table tr td:nth-child(2) {
|
211 |
+
border-left: thin solid #00000080;
|
212 |
+
}
|
213 |
+
|
214 |
+
.wpr-table-container table td {
|
215 |
+
background-color: #f9f9f9;
|
216 |
+
}
|
217 |
+
|
218 |
+
.wpr-table-container .no-padding {
|
219 |
+
padding: 0;
|
220 |
+
}
|
221 |
+
|
222 |
+
.wpr-table-container .wpr-table-missing td {
|
223 |
+
background-color: #ff000830;
|
224 |
+
}
|
225 |
+
|
226 |
+
.wpr-table-container .wpr-table-difference td {
|
227 |
+
background-color: rgba(255, 166, 0, 0.30);
|
228 |
+
}
|
229 |
+
|
230 |
+
.wpr-table-container .wpr-table-match td {
|
231 |
+
background-color: rgba(9, 255, 0, 0.30);
|
232 |
+
}
|
233 |
+
|
234 |
+
.wpr-table-container table.table_diff tr td {
|
235 |
+
width: auto;
|
236 |
+
border-left: none;
|
237 |
+
word-break: break-all;
|
238 |
+
font-size: 14px;
|
239 |
+
font-family: monospace;
|
240 |
+
vertical-align: top;
|
241 |
+
}
|
242 |
+
|
243 |
+
.wpr-table-container .table_diff {
|
244 |
+
width: 100%;
|
245 |
+
}
|
246 |
+
|
247 |
+
|
248 |
@-moz-keyframes spin {
|
249 |
100% {
|
250 |
-moz-transform: rotate(-360deg);
|
332 |
font-weight: 600;
|
333 |
line-height: 32px;
|
334 |
color: #AAAAAA;
|
335 |
+
padding: 5px 10px;
|
336 |
display: block;
|
337 |
letter-spacing: 1px;
|
338 |
}
|
357 |
display: inline-block;
|
358 |
font-size: 12px;
|
359 |
line-height: 16px;
|
360 |
+
margin: 0px 5px 0px 5px;
|
361 |
text-decoration: none;
|
362 |
text-shadow: none;
|
363 |
background: rgba(255, 255, 255, 0.7);
|
401 |
-webkit-box-shadow: none;
|
402 |
}
|
403 |
/* tabs */
|
404 |
+
|
405 |
+
/* diff */
|
406 |
+
|
407 |
+
.Differences {
|
408 |
+
width: 100%;
|
409 |
+
border-collapse: collapse;
|
410 |
+
border-spacing: 0;
|
411 |
+
empty-cells: show;
|
412 |
+
}
|
413 |
+
|
414 |
+
.Differences thead th {
|
415 |
+
text-align: left;
|
416 |
+
border-bottom: 1px solid #000;
|
417 |
+
background: #aaa;
|
418 |
+
color: #000;
|
419 |
+
padding: 4px;
|
420 |
+
}
|
421 |
+
.Differences tbody th {
|
422 |
+
text-align: right;
|
423 |
+
background: #ccc;
|
424 |
+
width: 4em;
|
425 |
+
padding: 1px 2px;
|
426 |
+
border-right: 1px solid #00000080;
|
427 |
+
border-bottom: none;
|
428 |
+
vertical-align: middle;
|
429 |
+
font-size: 13px;
|
430 |
+
font-weight: 400;
|
431 |
+
}
|
432 |
+
|
433 |
+
.Differences td {
|
434 |
+
padding: 5px !important;
|
435 |
+
font-family: Consolas, monospace;
|
436 |
+
font-size: 13px;
|
437 |
+
}
|
438 |
+
|
439 |
+
.DifferencesSideBySide .ChangeInsert td.Left {
|
440 |
+
background: #dfd;
|
441 |
+
}
|
442 |
+
|
443 |
+
.DifferencesSideBySide .ChangeInsert td.Right {
|
444 |
+
background: #cfc;
|
445 |
+
}
|
446 |
+
|
447 |
+
.DifferencesSideBySide .ChangeDelete td.Left {
|
448 |
+
background: #f88;
|
449 |
+
}
|
450 |
+
|
451 |
+
.DifferencesSideBySide .ChangeDelete td.Right {
|
452 |
+
background: #faa;
|
453 |
+
}
|
454 |
+
|
455 |
+
.DifferencesSideBySide .ChangeReplace .Left {
|
456 |
+
background: #fe9;
|
457 |
+
}
|
458 |
+
|
459 |
+
.DifferencesSideBySide .ChangeReplace .Right {
|
460 |
+
background: #fd8;
|
461 |
+
}
|
462 |
+
|
463 |
+
.Differences ins, .Differences del {
|
464 |
+
text-decoration: none;
|
465 |
+
}
|
466 |
+
|
467 |
+
.DifferencesSideBySide .ChangeReplace ins, .DifferencesSideBySide .ChangeReplace del {
|
468 |
+
background: #fc0;
|
469 |
+
}
|
470 |
+
|
471 |
+
.Differences .Skipped {
|
472 |
+
background: #f7f7f7;
|
473 |
+
}
|
474 |
+
|
475 |
+
.DifferencesInline .ChangeReplace .Left,
|
476 |
+
.DifferencesInline .ChangeDelete .Left {
|
477 |
+
background: #fdd;
|
478 |
+
}
|
479 |
+
|
480 |
+
.DifferencesInline .ChangeReplace .Right,
|
481 |
+
.DifferencesInline .ChangeInsert .Right {
|
482 |
+
background: #dfd;
|
483 |
+
}
|
484 |
+
|
485 |
+
.DifferencesInline .ChangeReplace ins {
|
486 |
+
background: #9e9;
|
487 |
+
}
|
488 |
+
|
489 |
+
.DifferencesInline .ChangeReplace del {
|
490 |
+
background: #e99;
|
491 |
+
}
|
492 |
+
|
493 |
+
/* diff */
|
js/wp-reset.js
CHANGED
@@ -55,7 +55,161 @@ jQuery(document).ready(function($) {
|
|
55 |
}); // delete plugins
|
56 |
|
57 |
|
58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
59 |
confirm_action(wp_reset.confirm_title, $(button).data('text-confirm'), $(button).data('btn-confirm'), wp_reset.cancel_button)
|
60 |
.then((result) => {
|
61 |
if (result.value) {
|
@@ -65,16 +219,28 @@ jQuery(document).ready(function($) {
|
|
65 |
data: {
|
66 |
action: 'wp_reset_run_tool',
|
67 |
_ajax_nonce: wp_reset.nonce_run_tool,
|
68 |
-
tool: tool_name
|
|
|
69 |
}
|
70 |
}).always(function(data) {
|
71 |
swal.close();
|
72 |
}).done(function(data) {
|
73 |
if (data.success) {
|
74 |
msg = $(button).data('text-done').replace('%n', data.data);
|
75 |
-
swal({ type: 'success', title: msg })
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
76 |
} else {
|
77 |
-
swal({ type: 'error', title: wp_reset.
|
78 |
}
|
79 |
}).fail(function(data) {
|
80 |
swal({ type: 'error', title: wp_reset.undocumented_error });
|
55 |
}); // delete plugins
|
56 |
|
57 |
|
58 |
+
// compare snapshot
|
59 |
+
$('#wpr-snapshots').on('click', '.compare-snapshot', 'click', function(e) {
|
60 |
+
e.preventDefault();
|
61 |
+
uid = $(this).data('ss-uid');
|
62 |
+
button = $(this);
|
63 |
+
|
64 |
+
block_ui($(button).data('wait-msg'));
|
65 |
+
$.get({
|
66 |
+
url: ajaxurl,
|
67 |
+
data: {
|
68 |
+
action: 'wp_reset_run_tool',
|
69 |
+
_ajax_nonce: wp_reset.nonce_run_tool,
|
70 |
+
tool: 'compare_snapshots',
|
71 |
+
extra_data: uid
|
72 |
+
}
|
73 |
+
}).always(function(data) {
|
74 |
+
swal.close();
|
75 |
+
}).done(function(data) {
|
76 |
+
if (data.success) {
|
77 |
+
msg = $(button).data('title').replace('%s', $(button).data('name'));
|
78 |
+
swal({
|
79 |
+
width: '90%',
|
80 |
+
title: msg,
|
81 |
+
html: data.data,
|
82 |
+
showConfirmButton: false,
|
83 |
+
allowEnterKey:false,
|
84 |
+
focusConfirm: false,
|
85 |
+
showCloseButton: true,
|
86 |
+
customClass: 'compare-snapshots'
|
87 |
+
});
|
88 |
+
} else {
|
89 |
+
swal({ type: 'error', title: wp_reset.documented_error + ' ' + data.data });
|
90 |
+
}
|
91 |
+
}).fail(function(data) {
|
92 |
+
swal({ type: 'error', title: wp_reset.undocumented_error });
|
93 |
+
});
|
94 |
+
|
95 |
+
return false;
|
96 |
+
}); // compare snapshot
|
97 |
+
|
98 |
+
|
99 |
+
// restore snapshot
|
100 |
+
$('#wpr-snapshots').on('click', '.restore-snapshot', 'click', function(e) {
|
101 |
+
e.preventDefault();
|
102 |
+
uid = $(this).data('ss-uid');
|
103 |
+
|
104 |
+
run_tool(this, 'restore_snapshot', uid);
|
105 |
+
|
106 |
+
return false;
|
107 |
+
}); // restore snapshot
|
108 |
+
|
109 |
+
|
110 |
+
// download snapshot
|
111 |
+
$('#wpr-snapshots').on('click', '.download-snapshot', 'click', function(e) {
|
112 |
+
e.preventDefault();
|
113 |
+
uid = $(this).data('ss-uid');
|
114 |
+
button = this;
|
115 |
+
|
116 |
+
block_ui($(this).data('wait-msg'));
|
117 |
+
$.get({
|
118 |
+
url: ajaxurl,
|
119 |
+
data: {
|
120 |
+
action: 'wp_reset_run_tool',
|
121 |
+
_ajax_nonce: wp_reset.nonce_run_tool,
|
122 |
+
tool: 'download_snapshot',
|
123 |
+
extra_data: uid
|
124 |
+
}
|
125 |
+
}).always(function(data) {
|
126 |
+
swal.close();
|
127 |
+
}).done(function(data) {
|
128 |
+
if (data.success) {
|
129 |
+
msg = $(button).data('success-msg').replace('%s', data.data);
|
130 |
+
swal({ type: 'success', title: msg });
|
131 |
+
} else {
|
132 |
+
swal({ type: 'error', title: wp_reset.documented_error + ' ' + data.data });
|
133 |
+
}
|
134 |
+
}).fail(function(data) {
|
135 |
+
swal({ type: 'error', title: wp_reset.undocumented_error });
|
136 |
+
});
|
137 |
+
|
138 |
+
return false;
|
139 |
+
}); // downlod snapshot
|
140 |
+
|
141 |
+
|
142 |
+
// delete snapshot
|
143 |
+
$('#wpr-snapshots').on('click', '.delete-snapshot', 'click', function(e) {
|
144 |
+
e.preventDefault();
|
145 |
+
uid = $(this).data('ss-uid');
|
146 |
+
|
147 |
+
run_tool(this, 'delete_snapshot', uid);
|
148 |
+
|
149 |
+
return false;
|
150 |
+
}); // delete snapshot
|
151 |
+
|
152 |
+
|
153 |
+
// create snapshot
|
154 |
+
$('.tools_page_wp-reset').on('click', '.create-new-snapshot', 'click', function(e) {
|
155 |
+
e.preventDefault();
|
156 |
+
button = $('#create-new-snapshot-primary');
|
157 |
+
|
158 |
+
swal({ title: $(button).data('title'),
|
159 |
+
type: 'question',
|
160 |
+
text: $(button).data('text'),
|
161 |
+
input: 'text',
|
162 |
+
inputPlaceholder: $(button).data('placeholder'),
|
163 |
+
showCancelButton: true,
|
164 |
+
focusConfirm: false,
|
165 |
+
confirmButtonText: $(button).data('btn-confirm'),
|
166 |
+
cancelButtonText: wp_reset.cancel_button,
|
167 |
+
width: 600
|
168 |
+
}).then((result) => {
|
169 |
+
if (typeof result.value != 'undefined') {
|
170 |
+
block = block_ui($(button).data('msg-wait'));
|
171 |
+
$.get({
|
172 |
+
url: ajaxurl,
|
173 |
+
data: {
|
174 |
+
action: 'wp_reset_run_tool',
|
175 |
+
_ajax_nonce: wp_reset.nonce_run_tool,
|
176 |
+
tool: 'create_snapshot',
|
177 |
+
extra_data: result.value
|
178 |
+
}
|
179 |
+
}).always(function(data) {
|
180 |
+
swal.close();
|
181 |
+
}).done(function(data) {
|
182 |
+
if (data.success) {
|
183 |
+
swal({ type: 'success', title: $(button).data('msg-success') }).then((result) => {
|
184 |
+
location.reload();
|
185 |
+
});
|
186 |
+
} else {
|
187 |
+
swal({ type: 'error', title: wp_reset.documented_error + ' ' + data.data });
|
188 |
+
}
|
189 |
+
}).fail(function(data) {
|
190 |
+
swal({ type: 'error', title: wp_reset.undocumented_error });
|
191 |
+
});
|
192 |
+
} // if confirmed
|
193 |
+
});
|
194 |
+
|
195 |
+
return false;
|
196 |
+
}); // create snapshot
|
197 |
+
|
198 |
+
// show/hide extra table info in snapshot diff
|
199 |
+
$('body.tools_page_wp-reset').on('click', '.header-row', function(e) {
|
200 |
+
e.preventDefault();
|
201 |
+
|
202 |
+
parent = $(this).parents('div.wpr-table-container > table > tbody');
|
203 |
+
$(' > tr:not(.header-row)', parent).toggleClass('hidden');
|
204 |
+
|
205 |
+
$('span.dashicons', parent).toggleClass('dashicons-arrow-down-alt2').toggleClass('dashicons-arrow-up-alt2');
|
206 |
+
|
207 |
+
return false;
|
208 |
+
}); // show hide extra info in diff
|
209 |
+
|
210 |
+
|
211 |
+
// standard way of running a tool, with confirmation, loading and success message
|
212 |
+
function run_tool(button, tool_name, extra_data) {
|
213 |
confirm_action(wp_reset.confirm_title, $(button).data('text-confirm'), $(button).data('btn-confirm'), wp_reset.cancel_button)
|
214 |
.then((result) => {
|
215 |
if (result.value) {
|
219 |
data: {
|
220 |
action: 'wp_reset_run_tool',
|
221 |
_ajax_nonce: wp_reset.nonce_run_tool,
|
222 |
+
tool: tool_name,
|
223 |
+
extra_data: extra_data
|
224 |
}
|
225 |
}).always(function(data) {
|
226 |
swal.close();
|
227 |
}).done(function(data) {
|
228 |
if (data.success) {
|
229 |
msg = $(button).data('text-done').replace('%n', data.data);
|
230 |
+
swal({ type: 'success', title: msg }).then(() => {
|
231 |
+
if (tool_name == 'restore_snapshot') {
|
232 |
+
location.reload();
|
233 |
+
}
|
234 |
+
});
|
235 |
+
if (tool_name == 'delete_snapshot') {
|
236 |
+
$('#wpr-ss-' + extra_data).remove();
|
237 |
+
if ($('#wpr-snapshots tr').length <= 1) {
|
238 |
+
$('#wpr-snapshots').hide();
|
239 |
+
$('#ss-no-snapshots').show();
|
240 |
+
}
|
241 |
+
}
|
242 |
} else {
|
243 |
+
swal({ type: 'error', title: wp_reset.documented_error + ' ' + data.data });
|
244 |
}
|
245 |
}).fail(function(data) {
|
246 |
swal({ type: 'error', title: wp_reset.undocumented_error });
|
libs/diff.php
ADDED
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Diff
|
4 |
+
*
|
5 |
+
* A comprehensive library for generating differences between two strings
|
6 |
+
* in multiple formats (unified, side by side HTML etc)
|
7 |
+
*
|
8 |
+
* PHP version 5
|
9 |
+
*
|
10 |
+
* Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
|
11 |
+
*
|
12 |
+
* All rights reserved.
|
13 |
+
*
|
14 |
+
* Redistribution and use in source and binary forms, with or without
|
15 |
+
* modification, are permitted provided that the following conditions are met:
|
16 |
+
*
|
17 |
+
* - Redistributions of source code must retain the above copyright notice,
|
18 |
+
* this list of conditions and the following disclaimer.
|
19 |
+
* - Redistributions in binary form must reproduce the above copyright notice,
|
20 |
+
* this list of conditions and the following disclaimer in the documentation
|
21 |
+
* and/or other materials provided with the distribution.
|
22 |
+
* - Neither the name of the Chris Boulton nor the names of its contributors
|
23 |
+
* may be used to endorse or promote products derived from this software
|
24 |
+
* without specific prior written permission.
|
25 |
+
*
|
26 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
27 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
28 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
29 |
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
30 |
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
31 |
+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
32 |
+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
33 |
+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
34 |
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
35 |
+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
36 |
+
* POSSIBILITY OF SUCH DAMAGE.
|
37 |
+
*
|
38 |
+
* @package Diff
|
39 |
+
* @author Chris Boulton <chris.boulton@interspire.com>
|
40 |
+
* @copyright (c) 2009 Chris Boulton
|
41 |
+
* @license New BSD License http://www.opensource.org/licenses/bsd-license.php
|
42 |
+
* @version 1.1
|
43 |
+
* @link http://github.com/chrisboulton/php-diff
|
44 |
+
*/
|
45 |
+
|
46 |
+
class Diff
|
47 |
+
{
|
48 |
+
/**
|
49 |
+
* @var array The "old" sequence to use as the basis for the comparison.
|
50 |
+
*/
|
51 |
+
private $a = null;
|
52 |
+
|
53 |
+
/**
|
54 |
+
* @var array The "new" sequence to generate the changes for.
|
55 |
+
*/
|
56 |
+
private $b = null;
|
57 |
+
|
58 |
+
/**
|
59 |
+
* @var array Array containing the generated opcodes for the differences between the two items.
|
60 |
+
*/
|
61 |
+
private $groupedCodes = null;
|
62 |
+
|
63 |
+
/**
|
64 |
+
* @var array Associative array of the default options available for the diff class and their default value.
|
65 |
+
*/
|
66 |
+
private $defaultOptions = array(
|
67 |
+
'context' => 3,
|
68 |
+
'ignoreNewLines' => false,
|
69 |
+
'ignoreWhitespace' => false,
|
70 |
+
'ignoreCase' => false
|
71 |
+
);
|
72 |
+
|
73 |
+
/**
|
74 |
+
* @var array Array of the options that have been applied for generating the diff.
|
75 |
+
*/
|
76 |
+
private $options = array();
|
77 |
+
|
78 |
+
/**
|
79 |
+
* The constructor.
|
80 |
+
*
|
81 |
+
* @param array $a Array containing the lines of the first string to compare.
|
82 |
+
* @param array $b Array containing the lines for the second string to compare.
|
83 |
+
*/
|
84 |
+
public function __construct($a, $b, $options=array())
|
85 |
+
{
|
86 |
+
$this->a = $a;
|
87 |
+
$this->b = $b;
|
88 |
+
|
89 |
+
if (is_array($options))
|
90 |
+
$this->options = array_merge($this->defaultOptions, $options);
|
91 |
+
else
|
92 |
+
$this->options = $this->defaultOptions;
|
93 |
+
}
|
94 |
+
|
95 |
+
/**
|
96 |
+
* Render a diff using the supplied rendering class and return it.
|
97 |
+
*
|
98 |
+
* @param object $renderer An instance of the rendering object to use for generating the diff.
|
99 |
+
* @return mixed The generated diff. Exact return value depends on the rendered.
|
100 |
+
*/
|
101 |
+
public function render(Diff_Renderer_Abstract $renderer)
|
102 |
+
{
|
103 |
+
$renderer->diff = $this;
|
104 |
+
return $renderer->render();
|
105 |
+
}
|
106 |
+
|
107 |
+
/**
|
108 |
+
* Get a range of lines from $start to $end from the first comparison string
|
109 |
+
* and return them as an array. If no values are supplied, the entire string
|
110 |
+
* is returned. It's also possible to specify just one line to return only
|
111 |
+
* that line.
|
112 |
+
*
|
113 |
+
* @param int $start The starting number.
|
114 |
+
* @param int $end The ending number. If not supplied, only the item in $start will be returned.
|
115 |
+
* @return array Array of all of the lines between the specified range.
|
116 |
+
*/
|
117 |
+
public function getA($start=0, $end=null)
|
118 |
+
{
|
119 |
+
if($start == 0 && $end === null) {
|
120 |
+
return $this->a;
|
121 |
+
}
|
122 |
+
|
123 |
+
if($end === null) {
|
124 |
+
$length = 1;
|
125 |
+
}
|
126 |
+
else {
|
127 |
+
$length = $end - $start;
|
128 |
+
}
|
129 |
+
|
130 |
+
return array_slice($this->a, $start, $length);
|
131 |
+
|
132 |
+
}
|
133 |
+
|
134 |
+
/**
|
135 |
+
* Get a range of lines from $start to $end from the second comparison string
|
136 |
+
* and return them as an array. If no values are supplied, the entire string
|
137 |
+
* is returned. It's also possible to specify just one line to return only
|
138 |
+
* that line.
|
139 |
+
*
|
140 |
+
* @param int $start The starting number.
|
141 |
+
* @param int $end The ending number. If not supplied, only the item in $start will be returned.
|
142 |
+
* @return array Array of all of the lines between the specified range.
|
143 |
+
*/
|
144 |
+
public function getB($start=0, $end=null)
|
145 |
+
{
|
146 |
+
if($start == 0 && $end === null) {
|
147 |
+
return $this->b;
|
148 |
+
}
|
149 |
+
|
150 |
+
if($end === null) {
|
151 |
+
$length = 1;
|
152 |
+
}
|
153 |
+
else {
|
154 |
+
$length = $end - $start;
|
155 |
+
}
|
156 |
+
|
157 |
+
return array_slice($this->b, $start, $length);
|
158 |
+
}
|
159 |
+
|
160 |
+
/**
|
161 |
+
* Generate a list of the compiled and grouped opcodes for the differences between the
|
162 |
+
* two strings. Generally called by the renderer, this class instantiates the sequence
|
163 |
+
* matcher and performs the actual diff generation and return an array of the opcodes
|
164 |
+
* for it. Once generated, the results are cached in the diff class instance.
|
165 |
+
*
|
166 |
+
* @return array Array of the grouped opcodes for the generated diff.
|
167 |
+
*/
|
168 |
+
public function getGroupedOpcodes()
|
169 |
+
{
|
170 |
+
if(!is_null($this->groupedCodes)) {
|
171 |
+
return $this->groupedCodes;
|
172 |
+
}
|
173 |
+
|
174 |
+
require_once dirname(__FILE__).'/Diff/SequenceMatcher.php';
|
175 |
+
$sequenceMatcher = new Diff_SequenceMatcher($this->a, $this->b, null, $this->options);
|
176 |
+
$this->groupedCodes = $sequenceMatcher->getGroupedOpcodes($this->options['context']);
|
177 |
+
return $this->groupedCodes;
|
178 |
+
}
|
179 |
+
}
|
libs/diff/Renderer/Abstract.php
ADDED
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Abstract class for diff renderers in PHP DiffLib.
|
4 |
+
*
|
5 |
+
* PHP version 5
|
6 |
+
*
|
7 |
+
* Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
|
8 |
+
*
|
9 |
+
* All rights reserved.
|
10 |
+
*
|
11 |
+
* Redistribution and use in source and binary forms, with or without
|
12 |
+
* modification, are permitted provided that the following conditions are met:
|
13 |
+
*
|
14 |
+
* - Redistributions of source code must retain the above copyright notice,
|
15 |
+
* this list of conditions and the following disclaimer.
|
16 |
+
* - Redistributions in binary form must reproduce the above copyright notice,
|
17 |
+
* this list of conditions and the following disclaimer in the documentation
|
18 |
+
* and/or other materials provided with the distribution.
|
19 |
+
* - Neither the name of the Chris Boulton nor the names of its contributors
|
20 |
+
* may be used to endorse or promote products derived from this software
|
21 |
+
* without specific prior written permission.
|
22 |
+
*
|
23 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
24 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
25 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
26 |
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
27 |
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
28 |
+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
29 |
+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
30 |
+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
31 |
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
32 |
+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
33 |
+
* POSSIBILITY OF SUCH DAMAGE.
|
34 |
+
*
|
35 |
+
* @package DiffLib
|
36 |
+
* @author Chris Boulton <chris.boulton@interspire.com>
|
37 |
+
* @copyright (c) 2009 Chris Boulton
|
38 |
+
* @license New BSD License http://www.opensource.org/licenses/bsd-license.php
|
39 |
+
* @version 1.1
|
40 |
+
* @link http://github.com/chrisboulton/php-diff
|
41 |
+
*/
|
42 |
+
|
43 |
+
abstract class Diff_Renderer_Abstract
|
44 |
+
{
|
45 |
+
/**
|
46 |
+
* @var object Instance of the diff class that this renderer is generating the rendered diff for.
|
47 |
+
*/
|
48 |
+
public $diff;
|
49 |
+
|
50 |
+
/**
|
51 |
+
* @var array Array of the default options that apply to this renderer.
|
52 |
+
*/
|
53 |
+
protected $defaultOptions = array();
|
54 |
+
|
55 |
+
/**
|
56 |
+
* @var array Array containing the user applied and merged default options for the renderer.
|
57 |
+
*/
|
58 |
+
protected $options = array();
|
59 |
+
|
60 |
+
/**
|
61 |
+
* The constructor. Instantiates the rendering engine and if options are passed,
|
62 |
+
* sets the options for the renderer.
|
63 |
+
*
|
64 |
+
* @param array $options Optionally, an array of the options for the renderer.
|
65 |
+
*/
|
66 |
+
public function __construct(array $options = array())
|
67 |
+
{
|
68 |
+
$this->setOptions($options);
|
69 |
+
}
|
70 |
+
|
71 |
+
/**
|
72 |
+
* Set the options of the renderer to those supplied in the passed in array.
|
73 |
+
* Options are merged with the default to ensure that there aren't any missing
|
74 |
+
* options.
|
75 |
+
*
|
76 |
+
* @param array $options Array of options to set.
|
77 |
+
*/
|
78 |
+
public function setOptions(array $options)
|
79 |
+
{
|
80 |
+
$this->options = array_merge($this->defaultOptions, $options);
|
81 |
+
}
|
82 |
+
}
|
libs/diff/Renderer/Html/Array.php
ADDED
@@ -0,0 +1,225 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Base renderer for rendering HTML based diffs for PHP DiffLib.
|
4 |
+
*
|
5 |
+
* PHP version 5
|
6 |
+
*
|
7 |
+
* Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
|
8 |
+
*
|
9 |
+
* All rights reserved.
|
10 |
+
*
|
11 |
+
* Redistribution and use in source and binary forms, with or without
|
12 |
+
* modification, are permitted provided that the following conditions are met:
|
13 |
+
*
|
14 |
+
* - Redistributions of source code must retain the above copyright notice,
|
15 |
+
* this list of conditions and the following disclaimer.
|
16 |
+
* - Redistributions in binary form must reproduce the above copyright notice,
|
17 |
+
* this list of conditions and the following disclaimer in the documentation
|
18 |
+
* and/or other materials provided with the distribution.
|
19 |
+
* - Neither the name of the Chris Boulton nor the names of its contributors
|
20 |
+
* may be used to endorse or promote products derived from this software
|
21 |
+
* without specific prior written permission.
|
22 |
+
*
|
23 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
24 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
25 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
26 |
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
27 |
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
28 |
+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
29 |
+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
30 |
+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
31 |
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
32 |
+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
33 |
+
* POSSIBILITY OF SUCH DAMAGE.
|
34 |
+
*
|
35 |
+
* @package DiffLib
|
36 |
+
* @author Chris Boulton <chris.boulton@interspire.com>
|
37 |
+
* @copyright (c) 2009 Chris Boulton
|
38 |
+
* @license New BSD License http://www.opensource.org/licenses/bsd-license.php
|
39 |
+
* @version 1.1
|
40 |
+
* @link http://github.com/chrisboulton/php-diff
|
41 |
+
*/
|
42 |
+
|
43 |
+
require_once dirname(__FILE__).'/../Abstract.php';
|
44 |
+
|
45 |
+
class Diff_Renderer_Html_Array extends Diff_Renderer_Abstract
|
46 |
+
{
|
47 |
+
/**
|
48 |
+
* @var array Array of the default options that apply to this renderer.
|
49 |
+
*/
|
50 |
+
protected $defaultOptions = array(
|
51 |
+
'tabSize' => 4
|
52 |
+
);
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Render and return an array structure suitable for generating HTML
|
56 |
+
* based differences. Generally called by subclasses that generate a
|
57 |
+
* HTML based diff and return an array of the changes to show in the diff.
|
58 |
+
*
|
59 |
+
* @return array An array of the generated chances, suitable for presentation in HTML.
|
60 |
+
*/
|
61 |
+
public function render()
|
62 |
+
{
|
63 |
+
// As we'll be modifying a & b to include our change markers,
|
64 |
+
// we need to get the contents and store them here. That way
|
65 |
+
// we're not going to destroy the original data
|
66 |
+
$a = $this->diff->getA();
|
67 |
+
$b = $this->diff->getB();
|
68 |
+
|
69 |
+
$changes = array();
|
70 |
+
$opCodes = $this->diff->getGroupedOpcodes();
|
71 |
+
foreach($opCodes as $group) {
|
72 |
+
$blocks = array();
|
73 |
+
$lastTag = null;
|
74 |
+
$lastBlock = 0;
|
75 |
+
foreach($group as $code) {
|
76 |
+
list($tag, $i1, $i2, $j1, $j2) = $code;
|
77 |
+
|
78 |
+
if($tag == 'replace' && $i2 - $i1 == $j2 - $j1) {
|
79 |
+
for($i = 0; $i < ($i2 - $i1); ++$i) {
|
80 |
+
$fromLine = $a[$i1 + $i];
|
81 |
+
$toLine = $b[$j1 + $i];
|
82 |
+
|
83 |
+
list($start, $end) = $this->getChangeExtent($fromLine, $toLine);
|
84 |
+
if($start != 0 || $end != 0) {
|
85 |
+
$last = $end + strlen($fromLine);
|
86 |
+
$fromLine = substr_replace($fromLine, "\0", $start, 0);
|
87 |
+
$fromLine = substr_replace($fromLine, "\1", $last + 1, 0);
|
88 |
+
$last = $end + strlen($toLine);
|
89 |
+
$toLine = substr_replace($toLine, "\0", $start, 0);
|
90 |
+
$toLine = substr_replace($toLine, "\1", $last + 1, 0);
|
91 |
+
$a[$i1 + $i] = $fromLine;
|
92 |
+
$b[$j1 + $i] = $toLine;
|
93 |
+
}
|
94 |
+
}
|
95 |
+
}
|
96 |
+
|
97 |
+
if($tag != $lastTag) {
|
98 |
+
$blocks[] = array(
|
99 |
+
'tag' => $tag,
|
100 |
+
'base' => array(
|
101 |
+
'offset' => $i1,
|
102 |
+
'lines' => array()
|
103 |
+
),
|
104 |
+
'changed' => array(
|
105 |
+
'offset' => $j1,
|
106 |
+
'lines' => array()
|
107 |
+
)
|
108 |
+
);
|
109 |
+
$lastBlock = count($blocks)-1;
|
110 |
+
}
|
111 |
+
|
112 |
+
$lastTag = $tag;
|
113 |
+
|
114 |
+
if($tag == 'equal') {
|
115 |
+
$lines = array_slice($a, $i1, ($i2 - $i1));
|
116 |
+
$blocks[$lastBlock]['base']['lines'] += $this->formatLines($lines);
|
117 |
+
$lines = array_slice($b, $j1, ($j2 - $j1));
|
118 |
+
$blocks[$lastBlock]['changed']['lines'] += $this->formatLines($lines);
|
119 |
+
}
|
120 |
+
else {
|
121 |
+
if($tag == 'replace' || $tag == 'delete') {
|
122 |
+
$lines = array_slice($a, $i1, ($i2 - $i1));
|
123 |
+
$lines = $this->formatLines($lines);
|
124 |
+
$lines = str_replace(array("\0", "\1"), array('<del>', '</del>'), $lines);
|
125 |
+
$blocks[$lastBlock]['base']['lines'] += $lines;
|
126 |
+
}
|
127 |
+
|
128 |
+
if($tag == 'replace' || $tag == 'insert') {
|
129 |
+
$lines = array_slice($b, $j1, ($j2 - $j1));
|
130 |
+
$lines = $this->formatLines($lines);
|
131 |
+
$lines = str_replace(array("\0", "\1"), array('<ins>', '</ins>'), $lines);
|
132 |
+
$blocks[$lastBlock]['changed']['lines'] += $lines;
|
133 |
+
}
|
134 |
+
}
|
135 |
+
}
|
136 |
+
$changes[] = $blocks;
|
137 |
+
}
|
138 |
+
return $changes;
|
139 |
+
}
|
140 |
+
|
141 |
+
/**
|
142 |
+
* Given two strings, determine where the changes in the two strings
|
143 |
+
* begin, and where the changes in the two strings end.
|
144 |
+
*
|
145 |
+
* @param string $fromLine The first string.
|
146 |
+
* @param string $toLine The second string.
|
147 |
+
* @return array Array containing the starting position (0 by default) and the ending position (-1 by default)
|
148 |
+
*/
|
149 |
+
private function getChangeExtent($fromLine, $toLine)
|
150 |
+
{
|
151 |
+
$start = 0;
|
152 |
+
$limit = min(strlen($fromLine), strlen($toLine));
|
153 |
+
while($start < $limit && $fromLine{$start} == $toLine{$start}) {
|
154 |
+
++$start;
|
155 |
+
}
|
156 |
+
$end = -1;
|
157 |
+
$limit = $limit - $start;
|
158 |
+
while(-$end <= $limit && substr($fromLine, $end, 1) == substr($toLine, $end, 1)) {
|
159 |
+
--$end;
|
160 |
+
}
|
161 |
+
return array(
|
162 |
+
$start,
|
163 |
+
$end + 1
|
164 |
+
);
|
165 |
+
}
|
166 |
+
|
167 |
+
/**
|
168 |
+
* Format a series of lines suitable for output in a HTML rendered diff.
|
169 |
+
* This involves replacing tab characters with spaces, making the HTML safe
|
170 |
+
* for output, ensuring that double spaces are replaced with etc.
|
171 |
+
*
|
172 |
+
* @param array $lines Array of lines to format.
|
173 |
+
* @return array Array of the formatted lines.
|
174 |
+
*/
|
175 |
+
protected function formatLines($lines)
|
176 |
+
{
|
177 |
+
$lines = array_map(array($this, 'ExpandTabs'), $lines);
|
178 |
+
$lines = array_map(array($this, 'HtmlSafe'), $lines);
|
179 |
+
foreach($lines as &$line) {
|
180 |
+
$line = preg_replace_callback('# ( +)|^ #', array($this, 'fixSpaces'), $line);
|
181 |
+
}
|
182 |
+
return $lines;
|
183 |
+
}
|
184 |
+
|
185 |
+
/**
|
186 |
+
* Replace a string containing spaces with a HTML representation using .
|
187 |
+
*
|
188 |
+
* @param string[] $matches Array with preg matches.
|
189 |
+
* @return string The HTML representation of the string.
|
190 |
+
*/
|
191 |
+
private function fixSpaces(array $matches)
|
192 |
+
{
|
193 |
+
$spaces = $matches[1];
|
194 |
+
$count = strlen($spaces);
|
195 |
+
if($count == 0) {
|
196 |
+
return '';
|
197 |
+
}
|
198 |
+
|
199 |
+
$div = floor($count / 2);
|
200 |
+
$mod = $count % 2;
|
201 |
+
return str_repeat(' ', $div).str_repeat(' ', $mod);
|
202 |
+
}
|
203 |
+
|
204 |
+
/**
|
205 |
+
* Replace tabs in a single line with a number of spaces as defined by the tabSize option.
|
206 |
+
*
|
207 |
+
* @param string $line The containing tabs to convert.
|
208 |
+
* @return string The line with the tabs converted to spaces.
|
209 |
+
*/
|
210 |
+
private function expandTabs($line)
|
211 |
+
{
|
212 |
+
return str_replace("\t", str_repeat(' ', $this->options['tabSize']), $line);
|
213 |
+
}
|
214 |
+
|
215 |
+
/**
|
216 |
+
* Make a string containing HTML safe for output on a page.
|
217 |
+
*
|
218 |
+
* @param string $string The string.
|
219 |
+
* @return string The string with the HTML characters replaced by entities.
|
220 |
+
*/
|
221 |
+
private function htmlSafe($string)
|
222 |
+
{
|
223 |
+
return htmlspecialchars($string, ENT_NOQUOTES, 'UTF-8');
|
224 |
+
}
|
225 |
+
}
|
libs/diff/Renderer/Html/Inline.php
ADDED
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Inline HTML diff generator for PHP DiffLib.
|
4 |
+
*
|
5 |
+
* PHP version 5
|
6 |
+
*
|
7 |
+
* Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
|
8 |
+
*
|
9 |
+
* All rights reserved.
|
10 |
+
*
|
11 |
+
* Redistribution and use in source and binary forms, with or without
|
12 |
+
* modification, are permitted provided that the following conditions are met:
|
13 |
+
*
|
14 |
+
* - Redistributions of source code must retain the above copyright notice,
|
15 |
+
* this list of conditions and the following disclaimer.
|
16 |
+
* - Redistributions in binary form must reproduce the above copyright notice,
|
17 |
+
* this list of conditions and the following disclaimer in the documentation
|
18 |
+
* and/or other materials provided with the distribution.
|
19 |
+
* - Neither the name of the Chris Boulton nor the names of its contributors
|
20 |
+
* may be used to endorse or promote products derived from this software
|
21 |
+
* without specific prior written permission.
|
22 |
+
*
|
23 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
24 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
25 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
26 |
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
27 |
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
28 |
+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
29 |
+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
30 |
+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
31 |
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
32 |
+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
33 |
+
* POSSIBILITY OF SUCH DAMAGE.
|
34 |
+
*
|
35 |
+
* @package DiffLib
|
36 |
+
* @author Chris Boulton <chris.boulton@interspire.com>
|
37 |
+
* @copyright (c) 2009 Chris Boulton
|
38 |
+
* @license New BSD License http://www.opensource.org/licenses/bsd-license.php
|
39 |
+
* @version 1.1
|
40 |
+
* @link http://github.com/chrisboulton/php-diff
|
41 |
+
*/
|
42 |
+
|
43 |
+
require_once dirname(__FILE__).'/Array.php';
|
44 |
+
|
45 |
+
class Diff_Renderer_Html_Inline extends Diff_Renderer_Html_Array
|
46 |
+
{
|
47 |
+
/**
|
48 |
+
* Render a and return diff with changes between the two sequences
|
49 |
+
* displayed inline (under each other)
|
50 |
+
*
|
51 |
+
* @return string The generated inline diff.
|
52 |
+
*/
|
53 |
+
public function render()
|
54 |
+
{
|
55 |
+
$changes = parent::render();
|
56 |
+
$html = '';
|
57 |
+
if(empty($changes)) {
|
58 |
+
return $html;
|
59 |
+
}
|
60 |
+
|
61 |
+
$html .= '<table class="Differences DifferencesInline">';
|
62 |
+
$html .= '<thead>';
|
63 |
+
$html .= '<tr>';
|
64 |
+
$html .= '<th>Old</th>';
|
65 |
+
$html .= '<th>New</th>';
|
66 |
+
$html .= '<th>Differences</th>';
|
67 |
+
$html .= '</tr>';
|
68 |
+
$html .= '</thead>';
|
69 |
+
foreach($changes as $i => $blocks) {
|
70 |
+
// If this is a separate block, we're condensing code so output ...,
|
71 |
+
// indicating a significant portion of the code has been collapsed as
|
72 |
+
// it is the same
|
73 |
+
if($i > 0) {
|
74 |
+
$html .= '<tbody class="Skipped">';
|
75 |
+
$html .= '<th>…</th>';
|
76 |
+
$html .= '<th>…</th>';
|
77 |
+
$html .= '<td> </td>';
|
78 |
+
$html .= '</tbody>';
|
79 |
+
}
|
80 |
+
|
81 |
+
foreach($blocks as $change) {
|
82 |
+
$html .= '<tbody class="Change'.ucfirst($change['tag']).'">';
|
83 |
+
// Equal changes should be shown on both sides of the diff
|
84 |
+
if($change['tag'] == 'equal') {
|
85 |
+
foreach($change['base']['lines'] as $no => $line) {
|
86 |
+
$fromLine = $change['base']['offset'] + $no + 1;
|
87 |
+
$toLine = $change['changed']['offset'] + $no + 1;
|
88 |
+
$html .= '<tr>';
|
89 |
+
$html .= '<th>'.$fromLine.'</th>';
|
90 |
+
$html .= '<th>'.$toLine.'</th>';
|
91 |
+
$html .= '<td class="Left">'.$line.'</td>';
|
92 |
+
$html .= '</tr>';
|
93 |
+
}
|
94 |
+
}
|
95 |
+
// Added lines only on the right side
|
96 |
+
else if($change['tag'] == 'insert') {
|
97 |
+
foreach($change['changed']['lines'] as $no => $line) {
|
98 |
+
$toLine = $change['changed']['offset'] + $no + 1;
|
99 |
+
$html .= '<tr>';
|
100 |
+
$html .= '<th> </th>';
|
101 |
+
$html .= '<th>'.$toLine.'</th>';
|
102 |
+
$html .= '<td class="Right"><ins>'.$line.'</ins> </td>';
|
103 |
+
$html .= '</tr>';
|
104 |
+
}
|
105 |
+
}
|
106 |
+
// Show deleted lines only on the left side
|
107 |
+
else if($change['tag'] == 'delete') {
|
108 |
+
foreach($change['base']['lines'] as $no => $line) {
|
109 |
+
$fromLine = $change['base']['offset'] + $no + 1;
|
110 |
+
$html .= '<tr>';
|
111 |
+
$html .= '<th>'.$fromLine.'</th>';
|
112 |
+
$html .= '<th> </th>';
|
113 |
+
$html .= '<td class="Left"><del>'.$line.'</del> </td>';
|
114 |
+
$html .= '</tr>';
|
115 |
+
}
|
116 |
+
}
|
117 |
+
// Show modified lines on both sides
|
118 |
+
else if($change['tag'] == 'replace') {
|
119 |
+
foreach($change['base']['lines'] as $no => $line) {
|
120 |
+
$fromLine = $change['base']['offset'] + $no + 1;
|
121 |
+
$html .= '<tr>';
|
122 |
+
$html .= '<th>'.$fromLine.'</th>';
|
123 |
+
$html .= '<th> </th>';
|
124 |
+
$html .= '<td class="Left"><span>'.$line.'</span></td>';
|
125 |
+
$html .= '</tr>';
|
126 |
+
}
|
127 |
+
|
128 |
+
foreach($change['changed']['lines'] as $no => $line) {
|
129 |
+
$toLine = $change['changed']['offset'] + $no + 1;
|
130 |
+
$html .= '<tr>';
|
131 |
+
$html .= '<th> </th>';
|
132 |
+
$html .= '<th>'.$toLine.'</th>';
|
133 |
+
$html .= '<td class="Right"><span>'.$line.'</span></td>';
|
134 |
+
$html .= '</tr>';
|
135 |
+
}
|
136 |
+
}
|
137 |
+
$html .= '</tbody>';
|
138 |
+
}
|
139 |
+
}
|
140 |
+
$html .= '</table>';
|
141 |
+
return $html;
|
142 |
+
}
|
143 |
+
}
|
libs/diff/Renderer/Html/SideBySide.php
ADDED
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Side by Side HTML diff generator for PHP DiffLib.
|
4 |
+
*
|
5 |
+
* PHP version 5
|
6 |
+
*
|
7 |
+
* Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
|
8 |
+
*
|
9 |
+
* All rights reserved.
|
10 |
+
*
|
11 |
+
* Redistribution and use in source and binary forms, with or without
|
12 |
+
* modification, are permitted provided that the following conditions are met:
|
13 |
+
*
|
14 |
+
* - Redistributions of source code must retain the above copyright notice,
|
15 |
+
* this list of conditions and the following disclaimer.
|
16 |
+
* - Redistributions in binary form must reproduce the above copyright notice,
|
17 |
+
* this list of conditions and the following disclaimer in the documentation
|
18 |
+
* and/or other materials provided with the distribution.
|
19 |
+
* - Neither the name of the Chris Boulton nor the names of its contributors
|
20 |
+
* may be used to endorse or promote products derived from this software
|
21 |
+
* without specific prior written permission.
|
22 |
+
*
|
23 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
24 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
25 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
26 |
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
27 |
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
28 |
+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
29 |
+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
30 |
+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
31 |
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
32 |
+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
33 |
+
* POSSIBILITY OF SUCH DAMAGE.
|
34 |
+
*
|
35 |
+
* @package DiffLib
|
36 |
+
* @author Chris Boulton <chris.boulton@interspire.com>
|
37 |
+
* @copyright (c) 2009 Chris Boulton
|
38 |
+
* @license New BSD License http://www.opensource.org/licenses/bsd-license.php
|
39 |
+
* @version 1.1
|
40 |
+
* @link http://github.com/chrisboulton/php-diff
|
41 |
+
*/
|
42 |
+
|
43 |
+
require_once dirname(__FILE__).'/Array.php';
|
44 |
+
|
45 |
+
class Diff_Renderer_Html_SideBySide extends Diff_Renderer_Html_Array
|
46 |
+
{
|
47 |
+
/**
|
48 |
+
* Render a and return diff with changes between the two sequences
|
49 |
+
* displayed side by side.
|
50 |
+
*
|
51 |
+
* @return string The generated side by side diff.
|
52 |
+
*/
|
53 |
+
public function render()
|
54 |
+
{
|
55 |
+
$changes = parent::render();
|
56 |
+
|
57 |
+
$html = '';
|
58 |
+
if(empty($changes)) {
|
59 |
+
return $html;
|
60 |
+
}
|
61 |
+
|
62 |
+
$html .= '<table class="Differences DifferencesSideBySide">';
|
63 |
+
//$html .= '<thead>';
|
64 |
+
//$html .= '<tr>';
|
65 |
+
//$html .= '<th colspan="2">Old Version</th>';
|
66 |
+
//$html .= '<th colspan="2">New Version</th>';
|
67 |
+
//$html .= '</tr>';
|
68 |
+
//$html .= '</thead>';
|
69 |
+
foreach($changes as $i => $blocks) {
|
70 |
+
if($i > 0) {
|
71 |
+
$html .= '<tbody class="Skipped">';
|
72 |
+
$html .= '<th>…</th><td> </td>';
|
73 |
+
$html .= '<th>…</th><td> </td>';
|
74 |
+
$html .= '</tbody>';
|
75 |
+
}
|
76 |
+
|
77 |
+
foreach($blocks as $change) {
|
78 |
+
$html .= '<tbody class="Change'.ucfirst($change['tag']).'">';
|
79 |
+
// Equal changes should be shown on both sides of the diff
|
80 |
+
if($change['tag'] == 'equal') {
|
81 |
+
foreach($change['base']['lines'] as $no => $line) {
|
82 |
+
$fromLine = $change['base']['offset'] + $no + 1;
|
83 |
+
$toLine = $change['changed']['offset'] + $no + 1;
|
84 |
+
$html .= '<tr>';
|
85 |
+
$html .= '<th>'.$fromLine.'</th>';
|
86 |
+
$html .= '<td class="Left"><span>'.$line.'</span> </span></td>';
|
87 |
+
$html .= '<th>'.$toLine.'</th>';
|
88 |
+
$html .= '<td class="Right"><span>'.$line.'</span> </span></td>';
|
89 |
+
$html .= '</tr>';
|
90 |
+
}
|
91 |
+
}
|
92 |
+
// Added lines only on the right side
|
93 |
+
else if($change['tag'] == 'insert') {
|
94 |
+
foreach($change['changed']['lines'] as $no => $line) {
|
95 |
+
$toLine = $change['changed']['offset'] + $no + 1;
|
96 |
+
$html .= '<tr>';
|
97 |
+
$html .= '<th> </th>';
|
98 |
+
$html .= '<td class="Left"> </td>';
|
99 |
+
$html .= '<th>'.$toLine.'</th>';
|
100 |
+
$html .= '<td class="Right"><ins>'.$line.'</ins> </td>';
|
101 |
+
$html .= '</tr>';
|
102 |
+
}
|
103 |
+
}
|
104 |
+
// Show deleted lines only on the left side
|
105 |
+
else if($change['tag'] == 'delete') {
|
106 |
+
foreach($change['base']['lines'] as $no => $line) {
|
107 |
+
$fromLine = $change['base']['offset'] + $no + 1;
|
108 |
+
$html .= '<tr>';
|
109 |
+
$html .= '<th>'.$fromLine.'</th>';
|
110 |
+
$html .= '<td class="Left"><del>'.$line.'</del> </td>';
|
111 |
+
$html .= '<th> </th>';
|
112 |
+
$html .= '<td class="Right"> </td>';
|
113 |
+
$html .= '</tr>';
|
114 |
+
}
|
115 |
+
}
|
116 |
+
// Show modified lines on both sides
|
117 |
+
else if($change['tag'] == 'replace') {
|
118 |
+
if(count($change['base']['lines']) >= count($change['changed']['lines'])) {
|
119 |
+
foreach($change['base']['lines'] as $no => $line) {
|
120 |
+
$fromLine = $change['base']['offset'] + $no + 1;
|
121 |
+
$html .= '<tr>';
|
122 |
+
$html .= '<th>'.$fromLine.'</th>';
|
123 |
+
$html .= '<td class="Left"><span>'.$line.'</span> </td>';
|
124 |
+
if(!isset($change['changed']['lines'][$no])) {
|
125 |
+
$toLine = ' ';
|
126 |
+
$changedLine = ' ';
|
127 |
+
}
|
128 |
+
else {
|
129 |
+
$toLine = $change['base']['offset'] + $no + 1;
|
130 |
+
$changedLine = '<span>'.$change['changed']['lines'][$no].'</span>';
|
131 |
+
}
|
132 |
+
$html .= '<th>'.$toLine.'</th>';
|
133 |
+
$html .= '<td class="Right">'.$changedLine.'</td>';
|
134 |
+
$html .= '</tr>';
|
135 |
+
}
|
136 |
+
}
|
137 |
+
else {
|
138 |
+
foreach($change['changed']['lines'] as $no => $changedLine) {
|
139 |
+
if(!isset($change['base']['lines'][$no])) {
|
140 |
+
$fromLine = ' ';
|
141 |
+
$line = ' ';
|
142 |
+
}
|
143 |
+
else {
|
144 |
+
$fromLine = $change['base']['offset'] + $no + 1;
|
145 |
+
$line = '<span>'.$change['base']['lines'][$no].'</span>';
|
146 |
+
}
|
147 |
+
$html .= '<tr>';
|
148 |
+
$html .= '<th>'.$fromLine.'</th>';
|
149 |
+
$html .= '<td class="Left"><span>'.$line.'</span> </td>';
|
150 |
+
$toLine = $change['changed']['offset'] + $no + 1;
|
151 |
+
$html .= '<th>'.$toLine.'</th>';
|
152 |
+
$html .= '<td class="Right">'.$changedLine.'</td>';
|
153 |
+
$html .= '</tr>';
|
154 |
+
}
|
155 |
+
}
|
156 |
+
}
|
157 |
+
$html .= '</tbody>';
|
158 |
+
}
|
159 |
+
}
|
160 |
+
$html .= '</table>';
|
161 |
+
return $html;
|
162 |
+
}
|
163 |
+
}
|
libs/diff/Renderer/Text/Context.php
ADDED
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Context diff generator for PHP DiffLib.
|
4 |
+
*
|
5 |
+
* PHP version 5
|
6 |
+
*
|
7 |
+
* Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
|
8 |
+
*
|
9 |
+
* All rights reserved.
|
10 |
+
*
|
11 |
+
* Redistribution and use in source and binary forms, with or without
|
12 |
+
* modification, are permitted provided that the following conditions are met:
|
13 |
+
*
|
14 |
+
* - Redistributions of source code must retain the above copyright notice,
|
15 |
+
* this list of conditions and the following disclaimer.
|
16 |
+
* - Redistributions in binary form must reproduce the above copyright notice,
|
17 |
+
* this list of conditions and the following disclaimer in the documentation
|
18 |
+
* and/or other materials provided with the distribution.
|
19 |
+
* - Neither the name of the Chris Boulton nor the names of its contributors
|
20 |
+
* may be used to endorse or promote products derived from this software
|
21 |
+
* without specific prior written permission.
|
22 |
+
*
|
23 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
24 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
25 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
26 |
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
27 |
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
28 |
+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
29 |
+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
30 |
+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
31 |
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
32 |
+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
33 |
+
* POSSIBILITY OF SUCH DAMAGE.
|
34 |
+
*
|
35 |
+
* @package DiffLib
|
36 |
+
* @author Chris Boulton <chris.boulton@interspire.com>
|
37 |
+
* @copyright (c) 2009 Chris Boulton
|
38 |
+
* @license New BSD License http://www.opensource.org/licenses/bsd-license.php
|
39 |
+
* @version 1.1
|
40 |
+
* @link http://github.com/chrisboulton/php-diff
|
41 |
+
*/
|
42 |
+
|
43 |
+
require_once dirname(__FILE__).'/../Abstract.php';
|
44 |
+
|
45 |
+
class Diff_Renderer_Text_Context extends Diff_Renderer_Abstract
|
46 |
+
{
|
47 |
+
/**
|
48 |
+
* @var array Array of the different opcode tags and how they map to the context diff equivalent.
|
49 |
+
*/
|
50 |
+
private $tagMap = array(
|
51 |
+
'insert' => '+',
|
52 |
+
'delete' => '-',
|
53 |
+
'replace' => '!',
|
54 |
+
'equal' => ' '
|
55 |
+
);
|
56 |
+
|
57 |
+
/**
|
58 |
+
* Render and return a context formatted (old school!) diff file.
|
59 |
+
*
|
60 |
+
* @return string The generated context diff.
|
61 |
+
*/
|
62 |
+
public function render()
|
63 |
+
{
|
64 |
+
$diff = '';
|
65 |
+
$opCodes = $this->diff->getGroupedOpcodes();
|
66 |
+
foreach($opCodes as $group) {
|
67 |
+
$diff .= "***************\n";
|
68 |
+
$lastItem = count($group)-1;
|
69 |
+
$i1 = $group[0][1];
|
70 |
+
$i2 = $group[$lastItem][2];
|
71 |
+
$j1 = $group[0][3];
|
72 |
+
$j2 = $group[$lastItem][4];
|
73 |
+
|
74 |
+
if($i2 - $i1 >= 2) {
|
75 |
+
$diff .= '*** '.($group[0][1] + 1).','.$i2." ****\n";
|
76 |
+
}
|
77 |
+
else {
|
78 |
+
$diff .= '*** '.$i2." ****\n";
|
79 |
+
}
|
80 |
+
|
81 |
+
if($j2 - $j1 >= 2) {
|
82 |
+
$separator = '--- '.($j1 + 1).','.$j2." ----\n";
|
83 |
+
}
|
84 |
+
else {
|
85 |
+
$separator = '--- '.$j2." ----\n";
|
86 |
+
}
|
87 |
+
|
88 |
+
$hasVisible = false;
|
89 |
+
foreach($group as $code) {
|
90 |
+
if($code[0] == 'replace' || $code[0] == 'delete') {
|
91 |
+
$hasVisible = true;
|
92 |
+
break;
|
93 |
+
}
|
94 |
+
}
|
95 |
+
|
96 |
+
if($hasVisible) {
|
97 |
+
foreach($group as $code) {
|
98 |
+
list($tag, $i1, $i2, $j1, $j2) = $code;
|
99 |
+
if($tag == 'insert') {
|
100 |
+
continue;
|
101 |
+
}
|
102 |
+
$diff .= $this->tagMap[$tag].' '.implode("\n".$this->tagMap[$tag].' ', $this->diff->GetA($i1, $i2))."\n";
|
103 |
+
}
|
104 |
+
}
|
105 |
+
|
106 |
+
$hasVisible = false;
|
107 |
+
foreach($group as $code) {
|
108 |
+
if($code[0] == 'replace' || $code[0] == 'insert') {
|
109 |
+
$hasVisible = true;
|
110 |
+
break;
|
111 |
+
}
|
112 |
+
}
|
113 |
+
|
114 |
+
$diff .= $separator;
|
115 |
+
|
116 |
+
if($hasVisible) {
|
117 |
+
foreach($group as $code) {
|
118 |
+
list($tag, $i1, $i2, $j1, $j2) = $code;
|
119 |
+
if($tag == 'delete') {
|
120 |
+
continue;
|
121 |
+
}
|
122 |
+
$diff .= $this->tagMap[$tag].' '.implode("\n".$this->tagMap[$tag].' ', $this->diff->GetB($j1, $j2))."\n";
|
123 |
+
}
|
124 |
+
}
|
125 |
+
}
|
126 |
+
return $diff;
|
127 |
+
}
|
128 |
+
}
|
libs/diff/Renderer/Text/Unified.php
ADDED
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Unified diff generator for PHP DiffLib.
|
4 |
+
*
|
5 |
+
* PHP version 5
|
6 |
+
*
|
7 |
+
* Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
|
8 |
+
*
|
9 |
+
* All rights reserved.
|
10 |
+
*
|
11 |
+
* Redistribution and use in source and binary forms, with or without
|
12 |
+
* modification, are permitted provided that the following conditions are met:
|
13 |
+
*
|
14 |
+
* - Redistributions of source code must retain the above copyright notice,
|
15 |
+
* this list of conditions and the following disclaimer.
|
16 |
+
* - Redistributions in binary form must reproduce the above copyright notice,
|
17 |
+
* this list of conditions and the following disclaimer in the documentation
|
18 |
+
* and/or other materials provided with the distribution.
|
19 |
+
* - Neither the name of the Chris Boulton nor the names of its contributors
|
20 |
+
* may be used to endorse or promote products derived from this software
|
21 |
+
* without specific prior written permission.
|
22 |
+
*
|
23 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
24 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
25 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
26 |
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
27 |
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
28 |
+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
29 |
+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
30 |
+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
31 |
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
32 |
+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
33 |
+
* POSSIBILITY OF SUCH DAMAGE.
|
34 |
+
*
|
35 |
+
* @package DiffLib
|
36 |
+
* @author Chris Boulton <chris.boulton@interspire.com>
|
37 |
+
* @copyright (c) 2009 Chris Boulton
|
38 |
+
* @license New BSD License http://www.opensource.org/licenses/bsd-license.php
|
39 |
+
* @version 1.1
|
40 |
+
* @link http://github.com/chrisboulton/php-diff
|
41 |
+
*/
|
42 |
+
|
43 |
+
require_once dirname(__FILE__).'/../Abstract.php';
|
44 |
+
|
45 |
+
class Diff_Renderer_Text_Unified extends Diff_Renderer_Abstract
|
46 |
+
{
|
47 |
+
/**
|
48 |
+
* Render and return a unified diff.
|
49 |
+
*
|
50 |
+
* @return string The unified diff.
|
51 |
+
*/
|
52 |
+
public function render()
|
53 |
+
{
|
54 |
+
$diff = '';
|
55 |
+
$opCodes = $this->diff->getGroupedOpcodes();
|
56 |
+
foreach($opCodes as $group) {
|
57 |
+
$lastItem = count($group)-1;
|
58 |
+
$i1 = $group[0][1];
|
59 |
+
$i2 = $group[$lastItem][2];
|
60 |
+
$j1 = $group[0][3];
|
61 |
+
$j2 = $group[$lastItem][4];
|
62 |
+
|
63 |
+
if($i1 == 0 && $i2 == 0) {
|
64 |
+
$i1 = -1;
|
65 |
+
$i2 = -1;
|
66 |
+
}
|
67 |
+
|
68 |
+
$diff .= '@@ -'.($i1 + 1).','.($i2 - $i1).' +'.($j1 + 1).','.($j2 - $j1)." @@\n";
|
69 |
+
foreach($group as $code) {
|
70 |
+
list($tag, $i1, $i2, $j1, $j2) = $code;
|
71 |
+
if($tag == 'equal') {
|
72 |
+
$diff .= ' '.implode("\n ", $this->diff->GetA($i1, $i2))."\n";
|
73 |
+
}
|
74 |
+
else {
|
75 |
+
if($tag == 'replace' || $tag == 'delete') {
|
76 |
+
$diff .= '-'.implode("\n-", $this->diff->GetA($i1, $i2))."\n";
|
77 |
+
}
|
78 |
+
|
79 |
+
if($tag == 'replace' || $tag == 'insert') {
|
80 |
+
$diff .= '+'.implode("\n+", $this->diff->GetB($j1, $j2))."\n";
|
81 |
+
}
|
82 |
+
}
|
83 |
+
}
|
84 |
+
}
|
85 |
+
return $diff;
|
86 |
+
}
|
87 |
+
}
|
libs/diff/SequenceMatcher.php
ADDED
@@ -0,0 +1,742 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Sequence matcher for Diff
|
4 |
+
*
|
5 |
+
* PHP version 5
|
6 |
+
*
|
7 |
+
* Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
|
8 |
+
*
|
9 |
+
* All rights reserved.
|
10 |
+
*
|
11 |
+
* Redistribution and use in source and binary forms, with or without
|
12 |
+
* modification, are permitted provided that the following conditions are met:
|
13 |
+
*
|
14 |
+
* - Redistributions of source code must retain the above copyright notice,
|
15 |
+
* this list of conditions and the following disclaimer.
|
16 |
+
* - Redistributions in binary form must reproduce the above copyright notice,
|
17 |
+
* this list of conditions and the following disclaimer in the documentation
|
18 |
+
* and/or other materials provided with the distribution.
|
19 |
+
* - Neither the name of the Chris Boulton nor the names of its contributors
|
20 |
+
* may be used to endorse or promote products derived from this software
|
21 |
+
* without specific prior written permission.
|
22 |
+
*
|
23 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
24 |
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
25 |
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
26 |
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
27 |
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
28 |
+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
29 |
+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
30 |
+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
31 |
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
32 |
+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
33 |
+
* POSSIBILITY OF SUCH DAMAGE.
|
34 |
+
*
|
35 |
+
* @package Diff
|
36 |
+
* @author Chris Boulton <chris.boulton@interspire.com>
|
37 |
+
* @copyright (c) 2009 Chris Boulton
|
38 |
+
* @license New BSD License http://www.opensource.org/licenses/bsd-license.php
|
39 |
+
* @version 1.1
|
40 |
+
* @link http://github.com/chrisboulton/php-diff
|
41 |
+
*/
|
42 |
+
|
43 |
+
class Diff_SequenceMatcher
|
44 |
+
{
|
45 |
+
/**
|
46 |
+
* @var string|array Either a string or an array containing a callback function to determine if a line is "junk" or not.
|
47 |
+
*/
|
48 |
+
private $junkCallback = null;
|
49 |
+
|
50 |
+
/**
|
51 |
+
* @var array The first sequence to compare against.
|
52 |
+
*/
|
53 |
+
private $a = null;
|
54 |
+
|
55 |
+
/**
|
56 |
+
* @var array The second sequence.
|
57 |
+
*/
|
58 |
+
private $b = null;
|
59 |
+
|
60 |
+
/**
|
61 |
+
* @var array Array of characters that are considered junk from the second sequence. Characters are the array key.
|
62 |
+
*/
|
63 |
+
private $junkDict = array();
|
64 |
+
|
65 |
+
/**
|
66 |
+
* @var array Array of indices that do not contain junk elements.
|
67 |
+
*/
|
68 |
+
private $b2j = array();
|
69 |
+
|
70 |
+
private $options = array();
|
71 |
+
|
72 |
+
private $defaultOptions = array(
|
73 |
+
'ignoreNewLines' => false,
|
74 |
+
'ignoreWhitespace' => false,
|
75 |
+
'ignoreCase' => false
|
76 |
+
);
|
77 |
+
|
78 |
+
/**
|
79 |
+
* The constructor. With the sequences being passed, they'll be set for the
|
80 |
+
* sequence matcher and it will perform a basic cleanup & calculate junk
|
81 |
+
* elements.
|
82 |
+
*
|
83 |
+
* @param string|array $a A string or array containing the lines to compare against.
|
84 |
+
* @param string|array $b A string or array containing the lines to compare.
|
85 |
+
* @param string|array $junkCallback Either an array or string that references a callback function (if there is one) to determine 'junk' characters.
|
86 |
+
*/
|
87 |
+
public function __construct($a, $b, $junkCallback=null, $options)
|
88 |
+
{
|
89 |
+
$this->a = null;
|
90 |
+
$this->b = null;
|
91 |
+
$this->junkCallback = $junkCallback;
|
92 |
+
$this->setOptions($options);
|
93 |
+
$this->setSequences($a, $b);
|
94 |
+
}
|
95 |
+
|
96 |
+
public function setOptions($options)
|
97 |
+
{
|
98 |
+
$this->options = array_merge($this->defaultOptions, $options);
|
99 |
+
}
|
100 |
+
|
101 |
+
/**
|
102 |
+
* Set the first and second sequences to use with the sequence matcher.
|
103 |
+
*
|
104 |
+
* @param string|array $a A string or array containing the lines to compare against.
|
105 |
+
* @param string|array $b A string or array containing the lines to compare.
|
106 |
+
*/
|
107 |
+
public function setSequences($a, $b)
|
108 |
+
{
|
109 |
+
$this->setSeq1($a);
|
110 |
+
$this->setSeq2($b);
|
111 |
+
}
|
112 |
+
|
113 |
+
/**
|
114 |
+
* Set the first sequence ($a) and reset any internal caches to indicate that
|
115 |
+
* when calling the calculation methods, we need to recalculate them.
|
116 |
+
*
|
117 |
+
* @param string|array $a The sequence to set as the first sequence.
|
118 |
+
*/
|
119 |
+
public function setSeq1($a)
|
120 |
+
{
|
121 |
+
if(!is_array($a)) {
|
122 |
+
$a = str_split($a);
|
123 |
+
}
|
124 |
+
if($a == $this->a) {
|
125 |
+
return;
|
126 |
+
}
|
127 |
+
|
128 |
+
$this->a= $a;
|
129 |
+
$this->matchingBlocks = null;
|
130 |
+
$this->opCodes = null;
|
131 |
+
}
|
132 |
+
|
133 |
+
/**
|
134 |
+
* Set the second sequence ($b) and reset any internal caches to indicate that
|
135 |
+
* when calling the calculation methods, we need to recalculate them.
|
136 |
+
*
|
137 |
+
* @param string|array $b The sequence to set as the second sequence.
|
138 |
+
*/
|
139 |
+
public function setSeq2($b)
|
140 |
+
{
|
141 |
+
if(!is_array($b)) {
|
142 |
+
$b = str_split($b);
|
143 |
+
}
|
144 |
+
if($b == $this->b) {
|
145 |
+
return;
|
146 |
+
}
|
147 |
+
|
148 |
+
$this->b = $b;
|
149 |
+
$this->matchingBlocks = null;
|
150 |
+
$this->opCodes = null;
|
151 |
+
$this->fullBCount = null;
|
152 |
+
$this->chainB();
|
153 |
+
}
|
154 |
+
|
155 |
+
/**
|
156 |
+
* Generate the internal arrays containing the list of junk and non-junk
|
157 |
+
* characters for the second ($b) sequence.
|
158 |
+
*/
|
159 |
+
private function chainB()
|
160 |
+
{
|
161 |
+
$length = count ($this->b);
|
162 |
+
$this->b2j = array();
|
163 |
+
$popularDict = array();
|
164 |
+
|
165 |
+
for($i = 0; $i < $length; ++$i) {
|
166 |
+
$char = $this->b[$i];
|
167 |
+
if(isset($this->b2j[$char])) {
|
168 |
+
if($length >= 200 && count($this->b2j[$char]) * 100 > $length) {
|
169 |
+
$popularDict[$char] = 1;
|
170 |
+
unset($this->b2j[$char]);
|
171 |
+
}
|
172 |
+
else {
|
173 |
+
$this->b2j[$char][] = $i;
|
174 |
+
}
|
175 |
+
}
|
176 |
+
else {
|
177 |
+
$this->b2j[$char] = array(
|
178 |
+
$i
|
179 |
+
);
|
180 |
+
}
|
181 |
+
}
|
182 |
+
|
183 |
+
// Remove leftovers
|
184 |
+
foreach(array_keys($popularDict) as $char) {
|
185 |
+
unset($this->b2j[$char]);
|
186 |
+
}
|
187 |
+
|
188 |
+
$this->junkDict = array();
|
189 |
+
if(is_callable($this->junkCallback)) {
|
190 |
+
foreach(array_keys($popularDict) as $char) {
|
191 |
+
if(call_user_func($this->junkCallback, $char)) {
|
192 |
+
$this->junkDict[$char] = 1;
|
193 |
+
unset($popularDict[$char]);
|
194 |
+
}
|
195 |
+
}
|
196 |
+
|
197 |
+
foreach(array_keys($this->b2j) as $char) {
|
198 |
+
if(call_user_func($this->junkCallback, $char)) {
|
199 |
+
$this->junkDict[$char] = 1;
|
200 |
+
unset($this->b2j[$char]);
|
201 |
+
}
|
202 |
+
}
|
203 |
+
}
|
204 |
+
}
|
205 |
+
|
206 |
+
/**
|
207 |
+
* Checks if a particular character is in the junk dictionary
|
208 |
+
* for the list of junk characters.
|
209 |
+
*
|
210 |
+
* @return boolean $b True if the character is considered junk. False if not.
|
211 |
+
*/
|
212 |
+
private function isBJunk($b)
|
213 |
+
{
|
214 |
+
if(isset($this->juncDict[$b])) {
|
215 |
+
return true;
|
216 |
+
}
|
217 |
+
|
218 |
+
return false;
|
219 |
+
}
|
220 |
+
|
221 |
+
/**
|
222 |
+
* Find the longest matching block in the two sequences, as defined by the
|
223 |
+
* lower and upper constraints for each sequence. (for the first sequence,
|
224 |
+
* $alo - $ahi and for the second sequence, $blo - $bhi)
|
225 |
+
*
|
226 |
+
* Essentially, of all of the maximal matching blocks, return the one that
|
227 |
+
* startest earliest in $a, and all of those maximal matching blocks that
|
228 |
+
* start earliest in $a, return the one that starts earliest in $b.
|
229 |
+
*
|
230 |
+
* If the junk callback is defined, do the above but with the restriction
|
231 |
+
* that the junk element appears in the block. Extend it as far as possible
|
232 |
+
* by matching only junk elements in both $a and $b.
|
233 |
+
*
|
234 |
+
* @param int $alo The lower constraint for the first sequence.
|
235 |
+
* @param int $ahi The upper constraint for the first sequence.
|
236 |
+
* @param int $blo The lower constraint for the second sequence.
|
237 |
+
* @param int $bhi The upper constraint for the second sequence.
|
238 |
+
* @return array Array containing the longest match that includes the starting position in $a, start in $b and the length/size.
|
239 |
+
*/
|
240 |
+
public function findLongestMatch($alo, $ahi, $blo, $bhi)
|
241 |
+
{
|
242 |
+
$a = $this->a;
|
243 |
+
$b = $this->b;
|
244 |
+
|
245 |
+
$bestI = $alo;
|
246 |
+
$bestJ = $blo;
|
247 |
+
$bestSize = 0;
|
248 |
+
|
249 |
+
$j2Len = array();
|
250 |
+
$nothing = array();
|
251 |
+
|
252 |
+
for($i = $alo; $i < $ahi; ++$i) {
|
253 |
+
$newJ2Len = array();
|
254 |
+
$jDict = $this->arrayGetDefault($this->b2j, $a[$i], $nothing);
|
255 |
+
foreach($jDict as $jKey => $j) {
|
256 |
+
if($j < $blo) {
|
257 |
+
continue;
|
258 |
+
}
|
259 |
+
else if($j >= $bhi) {
|
260 |
+
break;
|
261 |
+
}
|
262 |
+
|
263 |
+
$k = $this->arrayGetDefault($j2Len, $j -1, 0) + 1;
|
264 |
+
$newJ2Len[$j] = $k;
|
265 |
+
if($k > $bestSize) {
|
266 |
+
$bestI = $i - $k + 1;
|
267 |
+
$bestJ = $j - $k + 1;
|
268 |
+
$bestSize = $k;
|
269 |
+
}
|
270 |
+
}
|
271 |
+
|
272 |
+
$j2Len = $newJ2Len;
|
273 |
+
}
|
274 |
+
|
275 |
+
while($bestI > $alo && $bestJ > $blo && !$this->isBJunk($b[$bestJ - 1]) &&
|
276 |
+
!$this->linesAreDifferent($bestI - 1, $bestJ - 1)) {
|
277 |
+
--$bestI;
|
278 |
+
--$bestJ;
|
279 |
+
++$bestSize;
|
280 |
+
}
|
281 |
+
|
282 |
+
while($bestI + $bestSize < $ahi && ($bestJ + $bestSize) < $bhi &&
|
283 |
+
!$this->isBJunk($b[$bestJ + $bestSize]) && !$this->linesAreDifferent($bestI + $bestSize, $bestJ + $bestSize)) {
|
284 |
+
++$bestSize;
|
285 |
+
}
|
286 |
+
|
287 |
+
while($bestI > $alo && $bestJ > $blo && $this->isBJunk($b[$bestJ - 1]) &&
|
288 |
+
!$this->isLineDifferent($bestI - 1, $bestJ - 1)) {
|
289 |
+
--$bestI;
|
290 |
+
--$bestJ;
|
291 |
+
++$bestSize;
|
292 |
+
}
|
293 |
+
|
294 |
+
while($bestI + $bestSize < $ahi && $bestJ + $bestSize < $bhi &&
|
295 |
+
$this->isBJunk($b[$bestJ + $bestSize]) && !$this->linesAreDifferent($bestI + $bestSize, $bestJ + $bestSize)) {
|
296 |
+
++$bestSize;
|
297 |
+
}
|
298 |
+
|
299 |
+
return array(
|
300 |
+
$bestI,
|
301 |
+
$bestJ,
|
302 |
+
$bestSize
|
303 |
+
);
|
304 |
+
}
|
305 |
+
|
306 |
+
/**
|
307 |
+
* Check if the two lines at the given indexes are different or not.
|
308 |
+
*
|
309 |
+
* @param int $aIndex Line number to check against in a.
|
310 |
+
* @param int $bIndex Line number to check against in b.
|
311 |
+
* @return boolean True if the lines are different and false if not.
|
312 |
+
*/
|
313 |
+
public function linesAreDifferent($aIndex, $bIndex)
|
314 |
+
{
|
315 |
+
$lineA = $this->a[$aIndex];
|
316 |
+
$lineB = $this->b[$bIndex];
|
317 |
+
|
318 |
+
if($this->options['ignoreWhitespace']) {
|
319 |
+
$replace = array("\t", ' ');
|
320 |
+
$lineA = str_replace($replace, '', $lineA);
|
321 |
+
$lineB = str_replace($replace, '', $lineB);
|
322 |
+
}
|
323 |
+
|
324 |
+
if($this->options['ignoreCase']) {
|
325 |
+
$lineA = strtolower($lineA);
|
326 |
+
$lineB = strtolower($lineB);
|
327 |
+
}
|
328 |
+
|
329 |
+
if($lineA != $lineB) {
|
330 |
+
return true;
|
331 |
+
}
|
332 |
+
|
333 |
+
return false;
|
334 |
+
}
|
335 |
+
|
336 |
+
/**
|
337 |
+
* Return a nested set of arrays for all of the matching sub-sequences
|
338 |
+
* in the strings $a and $b.
|
339 |
+
*
|
340 |
+
* Each block contains the lower constraint of the block in $a, the lower
|
341 |
+
* constraint of the block in $b and finally the number of lines that the
|
342 |
+
* block continues for.
|
343 |
+
*
|
344 |
+
* @return array Nested array of the matching blocks, as described by the function.
|
345 |
+
*/
|
346 |
+
public function getMatchingBlocks()
|
347 |
+
{
|
348 |
+
if(!empty($this->matchingBlocks)) {
|
349 |
+
return $this->matchingBlocks;
|
350 |
+
}
|
351 |
+
|
352 |
+
$aLength = count($this->a);
|
353 |
+
$bLength = count($this->b);
|
354 |
+
|
355 |
+
$queue = array(
|
356 |
+
array(
|
357 |
+
0,
|
358 |
+
$aLength,
|
359 |
+
0,
|
360 |
+
$bLength
|
361 |
+
)
|
362 |
+
);
|
363 |
+
|
364 |
+
$matchingBlocks = array();
|
365 |
+
while(!empty($queue)) {
|
366 |
+
list($alo, $ahi, $blo, $bhi) = array_pop($queue);
|
367 |
+
$x = $this->findLongestMatch($alo, $ahi, $blo, $bhi);
|
368 |
+
list($i, $j, $k) = $x;
|
369 |
+
if($k) {
|
370 |
+
$matchingBlocks[] = $x;
|
371 |
+
if($alo < $i && $blo < $j) {
|
372 |
+
$queue[] = array(
|
373 |
+
$alo,
|
374 |
+
$i,
|
375 |
+
$blo,
|
376 |
+
$j
|
377 |
+
);
|
378 |
+
}
|
379 |
+
|
380 |
+
if($i + $k < $ahi && $j + $k < $bhi) {
|
381 |
+
$queue[] = array(
|
382 |
+
$i + $k,
|
383 |
+
$ahi,
|
384 |
+
$j + $k,
|
385 |
+
$bhi
|
386 |
+
);
|
387 |
+
}
|
388 |
+
}
|
389 |
+
}
|
390 |
+
|
391 |
+
usort($matchingBlocks, array($this, 'tupleSort'));
|
392 |
+
|
393 |
+
$i1 = 0;
|
394 |
+
$j1 = 0;
|
395 |
+
$k1 = 0;
|
396 |
+
$nonAdjacent = array();
|
397 |
+
foreach($matchingBlocks as $block) {
|
398 |
+
list($i2, $j2, $k2) = $block;
|
399 |
+
if($i1 + $k1 == $i2 && $j1 + $k1 == $j2) {
|
400 |
+
$k1 += $k2;
|
401 |
+
}
|
402 |
+
else {
|
403 |
+
if($k1) {
|
404 |
+
$nonAdjacent[] = array(
|
405 |
+
$i1,
|
406 |
+
$j1,
|
407 |
+
$k1
|
408 |
+
);
|
409 |
+
}
|
410 |
+
|
411 |
+
$i1 = $i2;
|
412 |
+
$j1 = $j2;
|
413 |
+
$k1 = $k2;
|
414 |
+
}
|
415 |
+
}
|
416 |
+
|
417 |
+
if($k1) {
|
418 |
+
$nonAdjacent[] = array(
|
419 |
+
$i1,
|
420 |
+
$j1,
|
421 |
+
$k1
|
422 |
+
);
|
423 |
+
}
|
424 |
+
|
425 |
+
$nonAdjacent[] = array(
|
426 |
+
$aLength,
|
427 |
+
$bLength,
|
428 |
+
0
|
429 |
+
);
|
430 |
+
|
431 |
+
$this->matchingBlocks = $nonAdjacent;
|
432 |
+
return $this->matchingBlocks;
|
433 |
+
}
|
434 |
+
|
435 |
+
/**
|
436 |
+
* Return a list of all of the opcodes for the differences between the
|
437 |
+
* two strings.
|
438 |
+
*
|
439 |
+
* The nested array returned contains an array describing the opcode
|
440 |
+
* which includes:
|
441 |
+
* 0 - The type of tag (as described below) for the opcode.
|
442 |
+
* 1 - The beginning line in the first sequence.
|
443 |
+
* 2 - The end line in the first sequence.
|
444 |
+
* 3 - The beginning line in the second sequence.
|
445 |
+
* 4 - The end line in the second sequence.
|
446 |
+
*
|
447 |
+
* The different types of tags include:
|
448 |
+
* replace - The string from $i1 to $i2 in $a should be replaced by
|
449 |
+
* the string in $b from $j1 to $j2.
|
450 |
+
* delete - The string in $a from $i1 to $j2 should be deleted.
|
451 |
+
* insert - The string in $b from $j1 to $j2 should be inserted at
|
452 |
+
* $i1 in $a.
|
453 |
+
* equal - The two strings with the specified ranges are equal.
|
454 |
+
*
|
455 |
+
* @return array Array of the opcodes describing the differences between the strings.
|
456 |
+
*/
|
457 |
+
public function getOpCodes()
|
458 |
+
{
|
459 |
+
if(!empty($this->opCodes)) {
|
460 |
+
return $this->opCodes;
|
461 |
+
}
|
462 |
+
|
463 |
+
$i = 0;
|
464 |
+
$j = 0;
|
465 |
+
$this->opCodes = array();
|
466 |
+
|
467 |
+
$blocks = $this->getMatchingBlocks();
|
468 |
+
foreach($blocks as $block) {
|
469 |
+
list($ai, $bj, $size) = $block;
|
470 |
+
$tag = '';
|
471 |
+
if($i < $ai && $j < $bj) {
|
472 |
+
$tag = 'replace';
|
473 |
+
}
|
474 |
+
else if($i < $ai) {
|
475 |
+
$tag = 'delete';
|
476 |
+
}
|
477 |
+
else if($j < $bj) {
|
478 |
+
$tag = 'insert';
|
479 |
+
}
|
480 |
+
|
481 |
+
if($tag) {
|
482 |
+
$this->opCodes[] = array(
|
483 |
+
$tag,
|
484 |
+
$i,
|
485 |
+
$ai,
|
486 |
+
$j,
|
487 |
+
$bj
|
488 |
+
);
|
489 |
+
}
|
490 |
+
|
491 |
+
$i = $ai + $size;
|
492 |
+
$j = $bj + $size;
|
493 |
+
|
494 |
+
if($size) {
|
495 |
+
$this->opCodes[] = array(
|
496 |
+
'equal',
|
497 |
+
$ai,
|
498 |
+
$i,
|
499 |
+
$bj,
|
500 |
+
$j
|
501 |
+
);
|
502 |
+
}
|
503 |
+
}
|
504 |
+
return $this->opCodes;
|
505 |
+
}
|
506 |
+
|
507 |
+
/**
|
508 |
+
* Return a series of nested arrays containing different groups of generated
|
509 |
+
* opcodes for the differences between the strings with up to $context lines
|
510 |
+
* of surrounding content.
|
511 |
+
*
|
512 |
+
* Essentially what happens here is any big equal blocks of strings are stripped
|
513 |
+
* out, the smaller subsets of changes are then arranged in to their groups.
|
514 |
+
* This means that the sequence matcher and diffs do not need to include the full
|
515 |
+
* content of the different files but can still provide context as to where the
|
516 |
+
* changes are.
|
517 |
+
*
|
518 |
+
* @param int $context The number of lines of context to provide around the groups.
|
519 |
+
* @return array Nested array of all of the grouped opcodes.
|
520 |
+
*/
|
521 |
+
public function getGroupedOpcodes($context=3)
|
522 |
+
{
|
523 |
+
$opCodes = $this->getOpCodes();
|
524 |
+
if(empty($opCodes)) {
|
525 |
+
$opCodes = array(
|
526 |
+
array(
|
527 |
+
'equal',
|
528 |
+
0,
|
529 |
+
1,
|
530 |
+
0,
|
531 |
+
1
|
532 |
+
)
|
533 |
+
);
|
534 |
+
}
|
535 |
+
|
536 |
+
if($opCodes[0][0] == 'equal') {
|
537 |
+
$opCodes[0] = array(
|
538 |
+
$opCodes[0][0],
|
539 |
+
max($opCodes[0][1], $opCodes[0][2] - $context),
|
540 |
+
$opCodes[0][2],
|
541 |
+
max($opCodes[0][3], $opCodes[0][4] - $context),
|
542 |
+
$opCodes[0][4]
|
543 |
+
);
|
544 |
+
}
|
545 |
+
|
546 |
+
$lastItem = count($opCodes) - 1;
|
547 |
+
if($opCodes[$lastItem][0] == 'equal') {
|
548 |
+
list($tag, $i1, $i2, $j1, $j2) = $opCodes[$lastItem];
|
549 |
+
$opCodes[$lastItem] = array(
|
550 |
+
$tag,
|
551 |
+
$i1,
|
552 |
+
min($i2, $i1 + $context),
|
553 |
+
$j1,
|
554 |
+
min($j2, $j1 + $context)
|
555 |
+
);
|
556 |
+
}
|
557 |
+
|
558 |
+
$maxRange = $context * 2;
|
559 |
+
$groups = array();
|
560 |
+
$group = array();
|
561 |
+
foreach($opCodes as $code) {
|
562 |
+
list($tag, $i1, $i2, $j1, $j2) = $code;
|
563 |
+
if($tag == 'equal' && $i2 - $i1 > $maxRange) {
|
564 |
+
$group[] = array(
|
565 |
+
$tag,
|
566 |
+
$i1,
|
567 |
+
min($i2, $i1 + $context),
|
568 |
+
$j1,
|
569 |
+
min($j2, $j1 + $context)
|
570 |
+
);
|
571 |
+
$groups[] = $group;
|
572 |
+
$group = array();
|
573 |
+
$i1 = max($i1, $i2 - $context);
|
574 |
+
$j1 = max($j1, $j2 - $context);
|
575 |
+
}
|
576 |
+
$group[] = array(
|
577 |
+
$tag,
|
578 |
+
$i1,
|
579 |
+
$i2,
|
580 |
+
$j1,
|
581 |
+
$j2
|
582 |
+
);
|
583 |
+
}
|
584 |
+
|
585 |
+
if(!empty($group) && !(count($group) == 1 && $group[0][0] == 'equal')) {
|
586 |
+
$groups[] = $group;
|
587 |
+
}
|
588 |
+
|
589 |
+
return $groups;
|
590 |
+
}
|
591 |
+
|
592 |
+
/**
|
593 |
+
* Return a measure of the similarity between the two sequences.
|
594 |
+
* This will be a float value between 0 and 1.
|
595 |
+
*
|
596 |
+
* Out of all of the ratio calculation functions, this is the most
|
597 |
+
* expensive to call if getMatchingBlocks or getOpCodes is yet to be
|
598 |
+
* called. The other calculation methods (quickRatio and realquickRatio)
|
599 |
+
* can be used to perform quicker calculations but may be less accurate.
|
600 |
+
*
|
601 |
+
* The ratio is calculated as (2 * number of matches) / total number of
|
602 |
+
* elements in both sequences.
|
603 |
+
*
|
604 |
+
* @return float The calculated ratio.
|
605 |
+
*/
|
606 |
+
public function Ratio()
|
607 |
+
{
|
608 |
+
$matches = array_reduce($this->getMatchingBlocks(), array($this, 'ratioReduce'), 0);
|
609 |
+
return $this->calculateRatio($matches, count ($this->a) + count ($this->b));
|
610 |
+
}
|
611 |
+
|
612 |
+
/**
|
613 |
+
* Helper function to calculate the number of matches for Ratio().
|
614 |
+
*
|
615 |
+
* @param int $sum The running total for the number of matches.
|
616 |
+
* @param array $triple Array containing the matching block triple to add to the running total.
|
617 |
+
* @return int The new running total for the number of matches.
|
618 |
+
*/
|
619 |
+
private function ratioReduce($sum, $triple)
|
620 |
+
{
|
621 |
+
return $sum + ($triple[count($triple) - 1]);
|
622 |
+
}
|
623 |
+
|
624 |
+
/**
|
625 |
+
* Quickly return an upper bound ratio for the similarity of the strings.
|
626 |
+
* This is quicker to compute than Ratio().
|
627 |
+
*
|
628 |
+
* @return float The calculated ratio.
|
629 |
+
*/
|
630 |
+
private function quickRatio()
|
631 |
+
{
|
632 |
+
if($this->fullBCount === null) {
|
633 |
+
$this->fullBCount = array();
|
634 |
+
$bLength = count ($b);
|
635 |
+
for($i = 0; $i < $bLength; ++$i) {
|
636 |
+
$char = $this->b[$i];
|
637 |
+
$this->fullBCount[$char] = $this->arrayGetDefault($this->fullBCount, $char, 0) + 1;
|
638 |
+
}
|
639 |
+
}
|
640 |
+
|
641 |
+
$avail = array();
|
642 |
+
$matches = 0;
|
643 |
+
$aLength = count ($this->a);
|
644 |
+
for($i = 0; $i < $aLength; ++$i) {
|
645 |
+
$char = $this->a[$i];
|
646 |
+
if(isset($avail[$char])) {
|
647 |
+
$numb = $avail[$char];
|
648 |
+
}
|
649 |
+
else {
|
650 |
+
$numb = $this->arrayGetDefault($this->fullBCount, $char, 0);
|
651 |
+
}
|
652 |
+
$avail[$char] = $numb - 1;
|
653 |
+
if($numb > 0) {
|
654 |
+
++$matches;
|
655 |
+
}
|
656 |
+
}
|
657 |
+
|
658 |
+
$this->calculateRatio($matches, count ($this->a) + count ($this->b));
|
659 |
+
}
|
660 |
+
|
661 |
+
/**
|
662 |
+
* Return an upper bound ratio really quickly for the similarity of the strings.
|
663 |
+
* This is quicker to compute than Ratio() and quickRatio().
|
664 |
+
*
|
665 |
+
* @return float The calculated ratio.
|
666 |
+
*/
|
667 |
+
private function realquickRatio()
|
668 |
+
{
|
669 |
+
$aLength = count ($this->a);
|
670 |
+
$bLength = count ($this->b);
|
671 |
+
|
672 |
+
return $this->calculateRatio(min($aLength, $bLength), $aLength + $bLength);
|
673 |
+
}
|
674 |
+
|
675 |
+
/**
|
676 |
+
* Helper function for calculating the ratio to measure similarity for the strings.
|
677 |
+
* The ratio is defined as being 2 * (number of matches / total length)
|
678 |
+
*
|
679 |
+
* @param int $matches The number of matches in the two strings.
|
680 |
+
* @param int $length The length of the two strings.
|
681 |
+
* @return float The calculated ratio.
|
682 |
+
*/
|
683 |
+
private function calculateRatio($matches, $length=0)
|
684 |
+
{
|
685 |
+
if($length) {
|
686 |
+
return 2 * ($matches / $length);
|
687 |
+
}
|
688 |
+
else {
|
689 |
+
return 1;
|
690 |
+
}
|
691 |
+
}
|
692 |
+
|
693 |
+
/**
|
694 |
+
* Helper function that provides the ability to return the value for a key
|
695 |
+
* in an array of it exists, or if it doesn't then return a default value.
|
696 |
+
* Essentially cleaner than doing a series of if(isset()) {} else {} calls.
|
697 |
+
*
|
698 |
+
* @param array $array The array to search.
|
699 |
+
* @param string $key The key to check that exists.
|
700 |
+
* @param mixed $default The value to return as the default value if the key doesn't exist.
|
701 |
+
* @return mixed The value from the array if the key exists or otherwise the default.
|
702 |
+
*/
|
703 |
+
private function arrayGetDefault($array, $key, $default)
|
704 |
+
{
|
705 |
+
if(isset($array[$key])) {
|
706 |
+
return $array[$key];
|
707 |
+
}
|
708 |
+
else {
|
709 |
+
return $default;
|
710 |
+
}
|
711 |
+
}
|
712 |
+
|
713 |
+
/**
|
714 |
+
* Sort an array by the nested arrays it contains. Helper function for getMatchingBlocks
|
715 |
+
*
|
716 |
+
* @param array $a First array to compare.
|
717 |
+
* @param array $b Second array to compare.
|
718 |
+
* @return int -1, 0 or 1, as expected by the usort function.
|
719 |
+
*/
|
720 |
+
private function tupleSort($a, $b)
|
721 |
+
{
|
722 |
+
$max = max(count($a), count($b));
|
723 |
+
for($i = 0; $i < $max; ++$i) {
|
724 |
+
if($a[$i] < $b[$i]) {
|
725 |
+
return -1;
|
726 |
+
}
|
727 |
+
else if($a[$i] > $b[$i]) {
|
728 |
+
return 1;
|
729 |
+
}
|
730 |
+
}
|
731 |
+
|
732 |
+
if(count($a) == $count($b)) {
|
733 |
+
return 0;
|
734 |
+
}
|
735 |
+
else if(count($a) < count($b)) {
|
736 |
+
return -1;
|
737 |
+
}
|
738 |
+
else {
|
739 |
+
return 1;
|
740 |
+
}
|
741 |
+
}
|
742 |
+
}
|
libs/dumper.php
ADDED
@@ -0,0 +1,514 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Abstract dump file: provides common interface for writing
|
4 |
+
* data to dump files.
|
5 |
+
* (c) 2create Studio, Bulgaria
|
6 |
+
* http://2create.bg/
|
7 |
+
*/
|
8 |
+
abstract class Shuttle_Dump_File {
|
9 |
+
/**
|
10 |
+
* File Handle
|
11 |
+
*/
|
12 |
+
protected $fh;
|
13 |
+
|
14 |
+
/**
|
15 |
+
* Location of the dump file on the disk
|
16 |
+
*/
|
17 |
+
protected $file_location;
|
18 |
+
|
19 |
+
abstract function write($string);
|
20 |
+
abstract function end();
|
21 |
+
|
22 |
+
static function create($filename) {
|
23 |
+
if (self::is_gzip($filename)) {
|
24 |
+
return new Shuttle_Dump_File_Gzip($filename);
|
25 |
+
}
|
26 |
+
return new Shuttle_Dump_File_Plaintext($filename);
|
27 |
+
}
|
28 |
+
function __construct($file) {
|
29 |
+
$this->file_location = $file;
|
30 |
+
$this->fh = $this->open();
|
31 |
+
|
32 |
+
if (!$this->fh) {
|
33 |
+
throw new Shuttle_Exception("Couldn't create gz file");
|
34 |
+
}
|
35 |
+
}
|
36 |
+
|
37 |
+
public static function is_gzip($filename) {
|
38 |
+
return preg_match('~gz$~i', $filename);
|
39 |
+
}
|
40 |
+
}
|
41 |
+
|
42 |
+
/**
|
43 |
+
* Plain text implementation. Uses standard file functions in PHP.
|
44 |
+
*/
|
45 |
+
class Shuttle_Dump_File_Plaintext extends Shuttle_Dump_File {
|
46 |
+
function open() {
|
47 |
+
return fopen($this->file_location, 'w');
|
48 |
+
}
|
49 |
+
function write($string) {
|
50 |
+
return fwrite($this->fh, $string);
|
51 |
+
}
|
52 |
+
function end() {
|
53 |
+
return fclose($this->fh);
|
54 |
+
}
|
55 |
+
}
|
56 |
+
|
57 |
+
/**
|
58 |
+
* Gzip implementation. Uses gz* functions.
|
59 |
+
*/
|
60 |
+
class Shuttle_Dump_File_Gzip extends Shuttle_Dump_File {
|
61 |
+
function open() {
|
62 |
+
return gzopen($this->file_location, 'wb9');
|
63 |
+
}
|
64 |
+
function write($string) {
|
65 |
+
return gzwrite($this->fh, $string);
|
66 |
+
}
|
67 |
+
function end() {
|
68 |
+
return gzclose($this->fh);
|
69 |
+
}
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* MySQL insert statement builder.
|
74 |
+
*/
|
75 |
+
class Shuttle_Insert_Statement {
|
76 |
+
private $rows = array();
|
77 |
+
private $length = 0;
|
78 |
+
private $table;
|
79 |
+
|
80 |
+
function __construct($table) {
|
81 |
+
$this->table = $table;
|
82 |
+
}
|
83 |
+
|
84 |
+
function reset() {
|
85 |
+
$this->rows = array();
|
86 |
+
$this->length = 0;
|
87 |
+
}
|
88 |
+
|
89 |
+
function add_row($row) {
|
90 |
+
$row = '(' . implode(",", $row) . ')';
|
91 |
+
$this->rows[] = $row;
|
92 |
+
$this->length += strlen($row);
|
93 |
+
}
|
94 |
+
|
95 |
+
function get_sql() {
|
96 |
+
if (empty($this->rows)) {
|
97 |
+
return false;
|
98 |
+
}
|
99 |
+
|
100 |
+
return 'INSERT INTO `' . $this->table . '` VALUES ' .
|
101 |
+
implode(",\n", $this->rows) . '; ';
|
102 |
+
}
|
103 |
+
|
104 |
+
function get_length() {
|
105 |
+
return $this->length;
|
106 |
+
}
|
107 |
+
}
|
108 |
+
|
109 |
+
/**
|
110 |
+
* Main facade
|
111 |
+
*/
|
112 |
+
abstract class Shuttle_Dumper {
|
113 |
+
/**
|
114 |
+
* Maximum length of single insert statement
|
115 |
+
*/
|
116 |
+
const INSERT_THRESHOLD = 838860;
|
117 |
+
|
118 |
+
/**
|
119 |
+
* @var Shuttle_DBConn
|
120 |
+
*/
|
121 |
+
public $db;
|
122 |
+
|
123 |
+
/**
|
124 |
+
* @var Shuttle_Dump_File
|
125 |
+
*/
|
126 |
+
public $dump_file;
|
127 |
+
|
128 |
+
/**
|
129 |
+
* End of line style used in the dump
|
130 |
+
*/
|
131 |
+
public $eol = "\r\n";
|
132 |
+
|
133 |
+
/**
|
134 |
+
* Specificed tables to include
|
135 |
+
*/
|
136 |
+
public $include_tables;
|
137 |
+
|
138 |
+
/**
|
139 |
+
* Specified tables to exclude
|
140 |
+
*/
|
141 |
+
public $exclude_tables = array();
|
142 |
+
|
143 |
+
/**
|
144 |
+
* Factory method for dumper on current hosts's configuration.
|
145 |
+
*/
|
146 |
+
static function create($db_options) {
|
147 |
+
$db = Shuttle_DBConn::create($db_options);
|
148 |
+
|
149 |
+
$db->connect();
|
150 |
+
|
151 |
+
if (self::has_shell_access()
|
152 |
+
&& self::is_shell_command_available('mysqldump')
|
153 |
+
&& self::is_shell_command_available('gzip')
|
154 |
+
) {
|
155 |
+
$dumper = new Shuttle_Dumper_ShellCommand($db);
|
156 |
+
} else {
|
157 |
+
$dumper = new Shuttle_Dumper_Native($db);
|
158 |
+
}
|
159 |
+
|
160 |
+
if (isset($db_options['include_tables'])) {
|
161 |
+
$dumper->include_tables = $db_options['include_tables'];
|
162 |
+
}
|
163 |
+
if (isset($db_options['exclude_tables'])) {
|
164 |
+
$dumper->exclude_tables = $db_options['exclude_tables'];
|
165 |
+
}
|
166 |
+
|
167 |
+
return $dumper;
|
168 |
+
}
|
169 |
+
|
170 |
+
function __construct(Shuttle_DBConn $db) {
|
171 |
+
$this->db = $db;
|
172 |
+
}
|
173 |
+
|
174 |
+
public static function has_shell_access() {
|
175 |
+
if (!is_callable('shell_exec')) {
|
176 |
+
return false;
|
177 |
+
}
|
178 |
+
$disabled_functions = ini_get('disable_functions');
|
179 |
+
return stripos($disabled_functions, 'shell_exec') === false;
|
180 |
+
}
|
181 |
+
|
182 |
+
public static function is_shell_command_available($command) {
|
183 |
+
if (preg_match('~win~i', PHP_OS)) {
|
184 |
+
/*
|
185 |
+
On Windows, the `where` command checks for availabilty in PATH. According
|
186 |
+
to the manual(`where /?`), there is quiet mode:
|
187 |
+
....
|
188 |
+
/Q Returns only the exit code, without displaying the list
|
189 |
+
of matched files. (Quiet mode)
|
190 |
+
....
|
191 |
+
*/
|
192 |
+
$output = array();
|
193 |
+
exec('where /Q ' . $command, $output, $return_val);
|
194 |
+
|
195 |
+
if (intval($return_val) === 1) {
|
196 |
+
return false;
|
197 |
+
} else {
|
198 |
+
return true;
|
199 |
+
}
|
200 |
+
|
201 |
+
} else {
|
202 |
+
$last_line = exec('which ' . $command);
|
203 |
+
$last_line = trim($last_line);
|
204 |
+
|
205 |
+
// Whenever there is at least one line in the output,
|
206 |
+
// it should be the path to the executable
|
207 |
+
if (empty($last_line)) {
|
208 |
+
return false;
|
209 |
+
} else {
|
210 |
+
return true;
|
211 |
+
}
|
212 |
+
}
|
213 |
+
|
214 |
+
}
|
215 |
+
|
216 |
+
/**
|
217 |
+
* Create an export file from the tables with that prefix.
|
218 |
+
* @param string $export_file_location the file to put the dump to.
|
219 |
+
* Note that whenever the file has .gz extension the dump will be comporessed with gzip
|
220 |
+
* @param string $table_prefix Allow to export only tables with particular prefix
|
221 |
+
* @return void
|
222 |
+
*/
|
223 |
+
abstract public function dump($export_file_location, $table_prefix='');
|
224 |
+
|
225 |
+
protected function get_tables($table_prefix) {
|
226 |
+
if (!empty($this->include_tables)) {
|
227 |
+
return $this->include_tables;
|
228 |
+
}
|
229 |
+
|
230 |
+
// $tables will only include the tables and not views.
|
231 |
+
// TODO - Handle views also, edits to be made in function 'get_create_table_sql' line 336
|
232 |
+
$tables = $this->db->fetch_numeric('
|
233 |
+
SHOW FULL TABLES WHERE Table_Type = "BASE TABLE" AND Tables_in_'.$this->db->name.' LIKE "' . $this->db->escape_like($table_prefix) . '%"
|
234 |
+
');
|
235 |
+
|
236 |
+
$tables_list = array();
|
237 |
+
foreach ($tables as $table_row) {
|
238 |
+
$table_name = $table_row[0];
|
239 |
+
if (!in_array($table_name, $this->exclude_tables)) {
|
240 |
+
$tables_list[] = $table_name;
|
241 |
+
}
|
242 |
+
}
|
243 |
+
return $tables_list;
|
244 |
+
}
|
245 |
+
}
|
246 |
+
|
247 |
+
class Shuttle_Dumper_ShellCommand extends Shuttle_Dumper {
|
248 |
+
function dump($export_file_location, $table_prefix='') {
|
249 |
+
$command = 'mysqldump -h ' . escapeshellarg($this->db->host) .
|
250 |
+
' -u ' . escapeshellarg($this->db->username) .
|
251 |
+
' --password=' . escapeshellarg($this->db->password) .
|
252 |
+
' ' . escapeshellarg($this->db->name);
|
253 |
+
|
254 |
+
$include_all_tables = empty($table_prefix) &&
|
255 |
+
empty($this->include_tables) &&
|
256 |
+
empty($this->exclude_tables);
|
257 |
+
|
258 |
+
if (!$include_all_tables) {
|
259 |
+
$tables = $this->get_tables($table_prefix);
|
260 |
+
$command .= ' ' . implode(' ', array_map('escapeshellarg', $tables));
|
261 |
+
}
|
262 |
+
|
263 |
+
$error_file = tempnam(sys_get_temp_dir(), 'err');
|
264 |
+
|
265 |
+
$command .= ' 2> ' . escapeshellarg($error_file);
|
266 |
+
|
267 |
+
if (Shuttle_Dump_File::is_gzip($export_file_location)) {
|
268 |
+
$command .= ' | gzip';
|
269 |
+
}
|
270 |
+
|
271 |
+
$command .= ' > ' . escapeshellarg($export_file_location);
|
272 |
+
|
273 |
+
exec($command, $output, $return_val);
|
274 |
+
|
275 |
+
if ($return_val !== 0) {
|
276 |
+
$error_text = file_get_contents($error_file);
|
277 |
+
unlink($error_file);
|
278 |
+
throw new Shuttle_Exception('Couldn\'t export database: ' . $error_text);
|
279 |
+
}
|
280 |
+
|
281 |
+
unlink($error_file);
|
282 |
+
}
|
283 |
+
}
|
284 |
+
|
285 |
+
class Shuttle_Dumper_Native extends Shuttle_Dumper {
|
286 |
+
public function dump($export_file_location, $table_prefix='') {
|
287 |
+
$eol = $this->eol;
|
288 |
+
|
289 |
+
$this->dump_file = Shuttle_Dump_File::create($export_file_location);
|
290 |
+
|
291 |
+
$this->dump_file->write("-- Generation time: " . date('r') . $eol);
|
292 |
+
$this->dump_file->write("-- Host: " . $this->db->host . $eol);
|
293 |
+
$this->dump_file->write("-- DB name: " . $this->db->name . $eol);
|
294 |
+
$this->dump_file->write("/*!40030 SET NAMES UTF8 */;$eol");
|
295 |
+
|
296 |
+
$this->dump_file->write("/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;$eol");
|
297 |
+
$this->dump_file->write("/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;$eol");
|
298 |
+
$this->dump_file->write("/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;$eol");
|
299 |
+
$this->dump_file->write("/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;$eol");
|
300 |
+
$this->dump_file->write("/*!40103 SET TIME_ZONE='+00:00' */;$eol");
|
301 |
+
$this->dump_file->write("/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;$eol");
|
302 |
+
$this->dump_file->write("/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;$eol");
|
303 |
+
$this->dump_file->write("/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;$eol");
|
304 |
+
$this->dump_file->write("/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;$eol$eol");
|
305 |
+
|
306 |
+
|
307 |
+
$tables = $this->get_tables($table_prefix);
|
308 |
+
foreach ($tables as $table) {
|
309 |
+
$this->dump_table($table);
|
310 |
+
}
|
311 |
+
|
312 |
+
$this->dump_file->write("$eol$eol");
|
313 |
+
$this->dump_file->write("/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;$eol");
|
314 |
+
$this->dump_file->write("/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;$eol");
|
315 |
+
$this->dump_file->write("/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;$eol");
|
316 |
+
$this->dump_file->write("/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;$eol");
|
317 |
+
$this->dump_file->write("/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;$eol");
|
318 |
+
$this->dump_file->write("/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;$eol");
|
319 |
+
$this->dump_file->write("/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;$eol$eol");
|
320 |
+
|
321 |
+
unset($this->dump_file);
|
322 |
+
}
|
323 |
+
|
324 |
+
protected function dump_table($table) {
|
325 |
+
$eol = $this->eol;
|
326 |
+
|
327 |
+
$this->dump_file->write("DROP TABLE IF EXISTS `$table`;$eol");
|
328 |
+
|
329 |
+
$create_table_sql = $this->get_create_table_sql($table);
|
330 |
+
$this->dump_file->write($create_table_sql . $eol . $eol);
|
331 |
+
|
332 |
+
$data = $this->db->query("SELECT * FROM `$table`");
|
333 |
+
|
334 |
+
$insert = new Shuttle_Insert_Statement($table);
|
335 |
+
|
336 |
+
while ($row = $this->db->fetch_row($data)) {
|
337 |
+
$row_values = array();
|
338 |
+
foreach ($row as $value) {
|
339 |
+
$row_values[] = $this->db->escape($value);
|
340 |
+
}
|
341 |
+
$insert->add_row( $row_values );
|
342 |
+
|
343 |
+
if ($insert->get_length() > self::INSERT_THRESHOLD) {
|
344 |
+
// The insert got too big: write the SQL and create
|
345 |
+
// new insert statement
|
346 |
+
$this->dump_file->write($insert->get_sql() . $eol);
|
347 |
+
$insert->reset();
|
348 |
+
}
|
349 |
+
}
|
350 |
+
|
351 |
+
$sql = $insert->get_sql();
|
352 |
+
if ($sql) {
|
353 |
+
$this->dump_file->write($insert->get_sql() . $eol);
|
354 |
+
}
|
355 |
+
$this->dump_file->write($eol . $eol);
|
356 |
+
}
|
357 |
+
|
358 |
+
public function get_create_table_sql($table) {
|
359 |
+
$create_table_sql = $this->db->fetch('SHOW CREATE TABLE `' . $table . '`');
|
360 |
+
return $create_table_sql[0]['Create Table'] . ';';
|
361 |
+
}
|
362 |
+
}
|
363 |
+
|
364 |
+
class Shuttle_DBConn {
|
365 |
+
public $host;
|
366 |
+
public $username;
|
367 |
+
public $password;
|
368 |
+
public $name;
|
369 |
+
|
370 |
+
protected $connection;
|
371 |
+
|
372 |
+
function __construct($options) {
|
373 |
+
$this->host = $options['host'];
|
374 |
+
if (empty($this->host)) {
|
375 |
+
$this->host = '127.0.0.1';
|
376 |
+
}
|
377 |
+
$this->username = $options['username'];
|
378 |
+
$this->password = $options['password'];
|
379 |
+
$this->name = $options['db_name'];
|
380 |
+
}
|
381 |
+
|
382 |
+
static function create($options) {
|
383 |
+
if (class_exists('mysqli')) {
|
384 |
+
$class_name = "Shuttle_DBConn_Mysqli";
|
385 |
+
} else {
|
386 |
+
$class_name = "Shuttle_DBConn_Mysql";
|
387 |
+
}
|
388 |
+
|
389 |
+
return new $class_name($options);
|
390 |
+
}
|
391 |
+
}
|
392 |
+
|
393 |
+
class Shuttle_DBConn_Mysql extends Shuttle_DBConn {
|
394 |
+
function connect() {
|
395 |
+
$this->connection = @mysql_connect($this->host, $this->username, $this->password);
|
396 |
+
if (!$this->connection) {
|
397 |
+
throw new Shuttle_Exception("Couldn't connect to the database: " . mysql_error());
|
398 |
+
}
|
399 |
+
|
400 |
+
$select_db_res = mysql_select_db($this->name, $this->connection);
|
401 |
+
if (!$select_db_res) {
|
402 |
+
throw new Shuttle_Exception("Couldn't select database: " . mysql_error($this->connection));
|
403 |
+
}
|
404 |
+
|
405 |
+
return true;
|
406 |
+
}
|
407 |
+
|
408 |
+
function query($q) {
|
409 |
+
if (!$this->connection) {
|
410 |
+
$this->connect();
|
411 |
+
}
|
412 |
+
$res = mysql_query($q);
|
413 |
+
if (!$res) {
|
414 |
+
throw new Shuttle_Exception("SQL error: " . mysql_error($this->connection));
|
415 |
+
}
|
416 |
+
return $res;
|
417 |
+
}
|
418 |
+
|
419 |
+
function fetch_numeric($query) {
|
420 |
+
return $this->fetch($query, MYSQL_NUM);
|
421 |
+
}
|
422 |
+
|
423 |
+
function fetch($query, $result_type=MYSQL_ASSOC) {
|
424 |
+
$result = $this->query($query, $this->connection);
|
425 |
+
$return = array();
|
426 |
+
while ( $row = mysql_fetch_array($result, $result_type) ) {
|
427 |
+
$return[] = $row;
|
428 |
+
}
|
429 |
+
return $return;
|
430 |
+
}
|
431 |
+
|
432 |
+
function escape($value) {
|
433 |
+
if (is_null($value)) {
|
434 |
+
return "NULL";
|
435 |
+
}
|
436 |
+
return "'" . mysql_real_escape_string($value) . "'";
|
437 |
+
}
|
438 |
+
|
439 |
+
function escape_like($search) {
|
440 |
+
return str_replace(array('_', '%'), array('\_', '\%'), $search);
|
441 |
+
}
|
442 |
+
|
443 |
+
function get_var($sql) {
|
444 |
+
$result = $this->query($sql);
|
445 |
+
$row = mysql_fetch_array($result);
|
446 |
+
return $row[0];
|
447 |
+
}
|
448 |
+
|
449 |
+
function fetch_row($data) {
|
450 |
+
return mysql_fetch_assoc($data);
|
451 |
+
}
|
452 |
+
}
|
453 |
+
|
454 |
+
|
455 |
+
class Shuttle_DBConn_Mysqli extends Shuttle_DBConn {
|
456 |
+
function connect() {
|
457 |
+
$this->connection = @new MySQLi($this->host, $this->username, $this->password, $this->name);
|
458 |
+
|
459 |
+
if ($this->connection->connect_error) {
|
460 |
+
throw new Shuttle_Exception("Couldn't connect to the database: " . $this->connection->connect_error);
|
461 |
+
}
|
462 |
+
|
463 |
+
return true;
|
464 |
+
}
|
465 |
+
|
466 |
+
function query($q) {
|
467 |
+
if (!$this->connection) {
|
468 |
+
$this->connect();
|
469 |
+
}
|
470 |
+
$res = $this->connection->query($q);
|
471 |
+
|
472 |
+
if (!$res) {
|
473 |
+
throw new Shuttle_Exception("SQL error: " . $this->connection->error);
|
474 |
+
}
|
475 |
+
|
476 |
+
return $res;
|
477 |
+
}
|
478 |
+
|
479 |
+
function fetch_numeric($query) {
|
480 |
+
return $this->fetch($query, MYSQLI_NUM);
|
481 |
+
}
|
482 |
+
|
483 |
+
function fetch($query, $result_type=MYSQLI_ASSOC) {
|
484 |
+
$result = $this->query($query, $this->connection);
|
485 |
+
$return = array();
|
486 |
+
while ( $row = $result->fetch_array($result_type) ) {
|
487 |
+
$return[] = $row;
|
488 |
+
}
|
489 |
+
return $return;
|
490 |
+
}
|
491 |
+
|
492 |
+
function escape($value) {
|
493 |
+
if (is_null($value)) {
|
494 |
+
return "NULL";
|
495 |
+
}
|
496 |
+
return "'" . $this->connection->real_escape_string($value) . "'";
|
497 |
+
}
|
498 |
+
|
499 |
+
function escape_like($search) {
|
500 |
+
return str_replace(array('_', '%'), array('\_', '\%'), $search);
|
501 |
+
}
|
502 |
+
|
503 |
+
function get_var($sql) {
|
504 |
+
$result = $this->query($sql);
|
505 |
+
$row = $result->fetch_array($result, MYSQLI_NUM);
|
506 |
+
return $row[0];
|
507 |
+
}
|
508 |
+
|
509 |
+
function fetch_row($data) {
|
510 |
+
return $data->fetch_array(MYSQLI_ASSOC);
|
511 |
+
}
|
512 |
+
}
|
513 |
+
|
514 |
+
class Shuttle_Exception extends Exception {};
|
readme.txt
CHANGED
@@ -4,7 +4,7 @@ Contributors: WebFactory, wpreset, googlemapswidget, securityninja, underconstru
|
|
4 |
Requires at least: 4.0
|
5 |
Requires PHP: 5.2
|
6 |
Tested up to: 4.9
|
7 |
-
Stable tag: 1.
|
8 |
License: GPLv2 or later
|
9 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
10 |
|
@@ -12,16 +12,17 @@ WordPress Reset resets any WordPress site to the default values without modifyin
|
|
12 |
|
13 |
== Description ==
|
14 |
|
15 |
-
<a href="https://wpreset.com/?utm_source=wordpressorg&utm_medium=content&utm_campaign=wp-reset&utm_term=wp-reset-top">WP Reset</a> quickly resets the site's database to the default installation values without modifying any files. It deletes all customizations and content. WP Reset is fast and safe to use
|
16 |
|
17 |
https://youtu.be/qMnkCW2PFoI?rel=0
|
18 |
|
19 |
-
> WP Reset is proudly sponsored by <a target="_blank" href="https://ipgeolocation.io/">IP Geolocation</a>, a **premium GeoIP service for developers**. See how you can use their <a href="https://wpreset.com/geoip-transform-boring-data-better-user-experience/">GeoIP service</a> to make boring IP addresses more interesting for users. They offer <a href="https://ipgeolocation.io/signup">50,000 API requests a month FREE for developers</a>, and keep WP Reset updated & maintained.
|
20 |
-
|
21 |
For support please use the <a href="https://wordpress.org/support/plugin/wp-reset">forums</a>, and if you need more information visit <a href="https://wpreset.com/?utm_source=wordpressorg&utm_medium=content&utm_campaign=wp-reset&utm_term=wpreset.com">wpreset.com</a> and be sure to check out the <a href="https://wpreset.com/roadmap/?utm_source=wordpressorg&utm_medium=content&utm_campaign=wp-reset&utm_term=roadmap">roadmap</a> for the list of upcoming features.
|
22 |
|
23 |
Access WP Reset admin page via the "Tools" menu.
|
24 |
|
|
|
|
|
|
|
25 |
**Please read carefully before proceeding to understand what WP Reset does**
|
26 |
|
27 |
#### Resetting will delete:
|
@@ -49,6 +50,14 @@ Access WP Reset admin page via the "Tools" menu.
|
|
49 |
|
50 |
WP Reset comes with full WP-CLI support. Help on our WP-CLI commands is available via _wp help reset_. By default the commands have to be confirmed but you can use the `--yes` option to skip confirmation. Instead of the active user, the first user with admin privileges found in the database will be restored after reset. Please be careful when using WP Reset with WP-CLI - as with using the GUI there is no undo.
|
51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
#### Multisite (WP-MU) Support
|
53 |
|
54 |
WP Reset has yet to be completely tested with multisite! Please be careful when using it with multisite enabled. We don't recommend to resetting the main site. Sub-sites should be OK. We're working on making WP Reset fully compatible with WP-MU. Till then please be careful. Thank you for understanding.
|
@@ -89,6 +98,12 @@ Or if needed, upload manually;
|
|
89 |
3. Additional tools for resetting and deleting various WordPress objects
|
90 |
|
91 |
== Changelog ==
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
|
93 |
= v1.35 =
|
94 |
* 2018/09/18
|
4 |
Requires at least: 4.0
|
5 |
Requires PHP: 5.2
|
6 |
Tested up to: 4.9
|
7 |
+
Stable tag: 1.40
|
8 |
License: GPLv2 or later
|
9 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
10 |
|
12 |
|
13 |
== Description ==
|
14 |
|
15 |
+
<a href="https://wpreset.com/?utm_source=wordpressorg&utm_medium=content&utm_campaign=wp-reset&utm_term=wp-reset-top">WP Reset</a> quickly resets the site's database to the default installation values without modifying any files. It deletes all customizations and content. WP Reset is fast and safe to use. It has multiple fail-safe mechanisms so you can never accidentally lose data. WP Reset is extremely helpful for plugin and theme developers. It **speeds up testing and debugging** by providing a quick way to reset settings and re-test code. It was developed by developers for developers.
|
16 |
|
17 |
https://youtu.be/qMnkCW2PFoI?rel=0
|
18 |
|
|
|
|
|
19 |
For support please use the <a href="https://wordpress.org/support/plugin/wp-reset">forums</a>, and if you need more information visit <a href="https://wpreset.com/?utm_source=wordpressorg&utm_medium=content&utm_campaign=wp-reset&utm_term=wpreset.com">wpreset.com</a> and be sure to check out the <a href="https://wpreset.com/roadmap/?utm_source=wordpressorg&utm_medium=content&utm_campaign=wp-reset&utm_term=roadmap">roadmap</a> for the list of upcoming features.
|
20 |
|
21 |
Access WP Reset admin page via the "Tools" menu.
|
22 |
|
23 |
+
> WP Reset is proudly sponsored by <a target="_blank" href="https://ipgeolocation.io/">IP Geolocation</a>, a **premium GeoIP service for developers**. See how you can use their <a href="https://wpreset.com/geoip-transform-boring-data-better-user-experience/">GeoIP service</a> to make boring IP addresses more interesting for users. They offer <a href="https://ipgeolocation.io/signup">50,000 API requests a month FREE for developers</a>, and keep WP Reset updated & maintained.
|
24 |
+
|
25 |
+
|
26 |
**Please read carefully before proceeding to understand what WP Reset does**
|
27 |
|
28 |
#### Resetting will delete:
|
50 |
|
51 |
WP Reset comes with full WP-CLI support. Help on our WP-CLI commands is available via _wp help reset_. By default the commands have to be confirmed but you can use the `--yes` option to skip confirmation. Instead of the active user, the first user with admin privileges found in the database will be restored after reset. Please be careful when using WP Reset with WP-CLI - as with using the GUI there is no undo.
|
52 |
|
53 |
+
#### Database Snapshots
|
54 |
+
|
55 |
+
Database snapshot is a copy of all WP database tables, standard and custom ones, saved in the currently used database (as set by _wp-config.php_). Files are not saved or included in snapshots in any way.
|
56 |
+
Snapshots are primarily a development tool. Although they can be used for backups (and downloaded as gzipped SQL dumps), we suggest finding a more suitable tool for doing backups of live sites. Use snapshots to find out what changes a plugin made to your database - what custom tables were created, modified, deleted or what changes were made to site's settings. Or use it to quickly restore the development environment after testing database related changes.
|
57 |
+
Restoring a snapshot does not affect other snapshots, or WP Reset settings. Snapshots can be compared to current database tables, restored (by overwriting current tables), exported ad gzipped SQL dumps, or deleted. Creating a snapshot on an average WordPress installation takes 1-2 seconds.
|
58 |
+
|
59 |
+
https://youtu.be/xBfMmS12vMY
|
60 |
+
|
61 |
#### Multisite (WP-MU) Support
|
62 |
|
63 |
WP Reset has yet to be completely tested with multisite! Please be careful when using it with multisite enabled. We don't recommend to resetting the main site. Sub-sites should be OK. We're working on making WP Reset fully compatible with WP-MU. Till then please be careful. Thank you for understanding.
|
98 |
3. Additional tools for resetting and deleting various WordPress objects
|
99 |
|
100 |
== Changelog ==
|
101 |
+
= v1.40 =
|
102 |
+
* 2018/10/24
|
103 |
+
* new tool: DB Snapshots
|
104 |
+
* rewrote code documentation for most functions
|
105 |
+
* some parts of Snapshots need refactoring
|
106 |
+
* 70k users hit on 2018/10/16 with 373,300 downloads; 30 days for +10k & 50k downloads
|
107 |
|
108 |
= v1.35 =
|
109 |
* 2018/09/18
|
wp-reset.php
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
Plugin Name: WP Reset
|
4 |
Plugin URI: https://wpreset.com/
|
5 |
Description: Reset the site to default installation values without modifying any files. Deletes all customizations and content.
|
6 |
-
Version: 1.
|
7 |
Author: WebFactory Ltd
|
8 |
Author URI: https://www.webfactoryltd.com/
|
9 |
Text Domain: wp-reset
|
@@ -26,7 +26,7 @@
|
|
26 |
|
27 |
// include only file
|
28 |
if (!defined('ABSPATH')) {
|
29 |
-
|
30 |
}
|
31 |
|
32 |
|
@@ -41,8 +41,10 @@ class WP_Reset {
|
|
41 |
public $version = 0;
|
42 |
public $plugin_url = '';
|
43 |
public $plugin_dir = '';
|
|
|
44 |
protected $options = array();
|
45 |
private $delete_count = 0;
|
|
|
46 |
|
47 |
|
48 |
/**
|
@@ -79,6 +81,8 @@ class WP_Reset {
|
|
79 |
add_filter('plugin_action_links_' . plugin_basename(__FILE__), array($this, 'plugin_action_links'));
|
80 |
add_filter('plugin_row_meta', array($this, 'plugin_meta_links'), 10, 2);
|
81 |
add_filter('admin_footer_text', array($this, 'admin_footer_text'));
|
|
|
|
|
82 |
} // __construct
|
83 |
|
84 |
|
@@ -142,6 +146,8 @@ class WP_Reset {
|
|
142 |
/**
|
143 |
* Get all dismissed notices, or check for one specific notice
|
144 |
*
|
|
|
|
|
145 |
* @return bool|array
|
146 |
*/
|
147 |
function get_dismissed_notices($notice_name = '') {
|
@@ -164,6 +170,8 @@ class WP_Reset {
|
|
164 |
*
|
165 |
* todo: not completed
|
166 |
*
|
|
|
|
|
167 |
* @return array
|
168 |
*/
|
169 |
function get_options($key = '') {
|
@@ -176,6 +184,9 @@ class WP_Reset {
|
|
176 |
*
|
177 |
* todo: this handles the entire options array although it should only do the options part - it's confusing
|
178 |
*
|
|
|
|
|
|
|
179 |
* @return bool
|
180 |
*/
|
181 |
function update_options($key, $data) {
|
@@ -216,6 +227,8 @@ class WP_Reset {
|
|
216 |
/**
|
217 |
* Dismiss notice by adding it to dismissed_notices options array
|
218 |
*
|
|
|
|
|
219 |
* @return bool
|
220 |
*/
|
221 |
function dismiss_notice($notice_name) {
|
@@ -278,6 +291,7 @@ class WP_Reset {
|
|
278 |
$options = $this->get_options();
|
279 |
|
280 |
$js_localize = array('undocumented_error' => __('An undocumented error has occured. Please refresh the page and try again.', 'wp-reset'),
|
|
|
281 |
'plugin_name' => __('WP Reset', 'wp-reset'),
|
282 |
'settings_url' => admin_url('tools.php?page=wp-reset'),
|
283 |
'icon_url' => $this->plugin_url . 'img/wp-reset-icon.png',
|
@@ -335,12 +349,12 @@ class WP_Reset {
|
|
335 |
/**
|
336 |
* Deletes all transients.
|
337 |
*
|
338 |
-
* @return int
|
339 |
-
|
340 |
-
|
341 |
global $wpdb;
|
342 |
|
343 |
-
|
344 |
|
345 |
return $count;
|
346 |
} // do_delete_transients
|
@@ -349,22 +363,25 @@ class WP_Reset {
|
|
349 |
/**
|
350 |
* Deletes all files in uploads folder.
|
351 |
*
|
352 |
-
* @return int
|
353 |
-
|
354 |
-
|
355 |
$upload_dir = wp_get_upload_dir();
|
356 |
|
357 |
$this->delete_folder($upload_dir['basedir'], $upload_dir['basedir']);
|
358 |
|
359 |
return $this->delete_count;
|
360 |
-
|
361 |
|
362 |
|
363 |
/**
|
364 |
* Recursively deletes a folder
|
365 |
*
|
|
|
|
|
|
|
366 |
* @return bool
|
367 |
-
|
368 |
private function delete_folder($folder, $base_folder) {
|
369 |
$files = array_diff(scandir($folder), array('.', '..'));
|
370 |
|
@@ -375,7 +392,7 @@ class WP_Reset {
|
|
375 |
$tmp = @unlink($folder . DIRECTORY_SEPARATOR . $file);
|
376 |
$this->delete_count = $this->delete_count + (int) $tmp;
|
377 |
}
|
378 |
-
|
379 |
|
380 |
if ($folder != $base_folder) {
|
381 |
$tmp = @rmdir($folder);
|
@@ -390,9 +407,10 @@ class WP_Reset {
|
|
390 |
/**
|
391 |
* Deactivate and delete all plugins
|
392 |
*
|
393 |
-
* @param bool
|
394 |
-
* @param bool
|
395 |
-
*
|
|
|
396 |
*/
|
397 |
function do_delete_plugins($keep_wp_reset = true, $silent_deactivate = false) {
|
398 |
if (!function_exists('get_plugins')) {
|
@@ -425,8 +443,9 @@ class WP_Reset {
|
|
425 |
/**
|
426 |
* Delete all themes
|
427 |
*
|
428 |
-
* @param bool
|
429 |
-
*
|
|
|
430 |
*/
|
431 |
function do_delete_themes($keep_default_theme = true) {
|
432 |
$default_theme = 'twentyseventeen';
|
@@ -450,7 +469,7 @@ class WP_Reset {
|
|
450 |
|
451 |
|
452 |
/**
|
453 |
-
* Run tool via AJAX call
|
454 |
*
|
455 |
* @return null
|
456 |
*/
|
@@ -458,6 +477,7 @@ class WP_Reset {
|
|
458 |
check_ajax_referer('wp-reset_run_tool');
|
459 |
|
460 |
$tool = trim(@$_GET['tool']);
|
|
|
461 |
|
462 |
if ($tool == 'delete_transients') {
|
463 |
$cnt = $this->do_delete_transients();
|
@@ -471,6 +491,42 @@ class WP_Reset {
|
|
471 |
} elseif ($tool == 'delete_uploads') {
|
472 |
$cnt = $this->do_delete_uploads();
|
473 |
wp_send_json_success($cnt);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
474 |
} else {
|
475 |
wp_send_json_error(__('Unknown tool.', 'wp-reset'));
|
476 |
}
|
@@ -482,6 +538,8 @@ class WP_Reset {
|
|
482 |
* There are no failsafes in the function - it reinstalls when called
|
483 |
* Redirects when done
|
484 |
*
|
|
|
|
|
485 |
* @return null
|
486 |
*/
|
487 |
function do_reinstall($params = array()) {
|
@@ -504,6 +562,7 @@ class WP_Reset {
|
|
504 |
$wplang = get_option('wplang');
|
505 |
$siteurl = get_option('siteurl');
|
506 |
$home = get_option('home');
|
|
|
507 |
|
508 |
$active_plugins = get_option('active_plugins');
|
509 |
$active_theme = wp_get_theme();
|
@@ -537,6 +596,7 @@ class WP_Reset {
|
|
537 |
update_option('siteurl', $siteurl);
|
538 |
update_option('home', $home);
|
539 |
update_option('wp-reset', $this->options);
|
|
|
540 |
|
541 |
// remove password nag
|
542 |
if (get_user_meta($user_id, 'default_password_nag')) {
|
@@ -583,7 +643,7 @@ class WP_Reset {
|
|
583 |
* Checks wp_reset post value and performs all actions
|
584 |
* todo: handle messages for various actions
|
585 |
*
|
586 |
-
* @return null
|
587 |
*/
|
588 |
function do_all_actions() {
|
589 |
// only admins can perform actions
|
@@ -616,12 +676,14 @@ class WP_Reset {
|
|
616 |
|
617 |
$this->do_reinstall($params);
|
618 |
}
|
619 |
-
} //
|
620 |
|
621 |
|
622 |
/**
|
623 |
* Add "Reset WordPress" action link to plugins table, left part
|
624 |
*
|
|
|
|
|
625 |
* @return array
|
626 |
*/
|
627 |
function plugin_action_links($links) {
|
@@ -636,6 +698,9 @@ class WP_Reset {
|
|
636 |
/**
|
637 |
* Add links to plugin's description in plugins table
|
638 |
*
|
|
|
|
|
|
|
639 |
* @return array
|
640 |
*/
|
641 |
function plugin_meta_links($links, $file) {
|
@@ -645,16 +710,18 @@ class WP_Reset {
|
|
645 |
|
646 |
$support_link = '<a target="_blank" href="https://wordpress.org/support/plugin/wp-reset" title="' . __('Get help', 'wp-reset') . '">' . __('Support', 'wp-reset') . '</a>';
|
647 |
$home_link = '<a target="_blank" href="' . $this->generate_web_link('plugins-table-right') . '" title="' . __('Plugin Homepage', 'wp-reset') . '">' . __('Plugin Homepage', 'wp-reset') . '</a>';
|
|
|
648 |
|
649 |
$links[] = $support_link;
|
650 |
$links[] = $home_link;
|
|
|
651 |
|
652 |
return $links;
|
653 |
} // plugin_meta_links
|
654 |
|
655 |
|
656 |
/**
|
657 |
-
* Test if we're on
|
658 |
*
|
659 |
* @return bool
|
660 |
*/
|
@@ -672,7 +739,9 @@ class WP_Reset {
|
|
672 |
/**
|
673 |
* Add powered by text in admin footer
|
674 |
*
|
675 |
-
* @
|
|
|
|
|
676 |
*/
|
677 |
function admin_footer_text($text) {
|
678 |
if (!$this->is_plugin_page()) {
|
@@ -686,7 +755,7 @@ class WP_Reset {
|
|
686 |
|
687 |
|
688 |
/**
|
689 |
-
* Loads
|
690 |
*
|
691 |
* @return null
|
692 |
*/
|
@@ -716,6 +785,7 @@ class WP_Reset {
|
|
716 |
$notice_shown = false;
|
717 |
$meta = $this->get_meta();
|
718 |
$notices = $this->get_dismissed_notices();
|
|
|
719 |
|
720 |
// double check for admin priv
|
721 |
if (!current_user_can('administrator')) {
|
@@ -729,13 +799,13 @@ class WP_Reset {
|
|
729 |
|
730 |
if (false === $notice_shown && is_multisite()) {
|
731 |
echo '<div class="card notice-wrapper notice-error">';
|
732 |
-
echo '<h2>' . __('WP Reset
|
733 |
echo '<p>' . __('Please be careful when using WP Reset with multisite enabled. It\'s not recommended to reset the main site. Sub-sites should be OK. We\'re working on making it fully compatible with WP-MU. <b>Till then please be careful.</b> Thank you for understanding.', 'wp-reset') . '</p>';
|
734 |
echo '</div>';
|
735 |
$notice_shown = true;
|
736 |
}
|
737 |
|
738 |
-
if (!empty($meta['reset_count']) && false === $notice_shown && false == $this->get_dismissed_notices('rate')) {
|
739 |
echo '<div class="card notice-wrapper">';
|
740 |
echo '<h2>' . __('Please help us keep the plugin free & up-to-date', 'wp-reset') . '</h2>';
|
741 |
echo '<p>' . __('If you use & enjoy WP Reset, <b>please rate it on WordPress.org</b>. It only takes a second and helps us keep the plugin free and maintained. Thank you!', 'wp-reset') . '</p>';
|
@@ -744,6 +814,7 @@ class WP_Reset {
|
|
744 |
$notice_shown = true;
|
745 |
}
|
746 |
|
|
|
747 |
// disabled for now
|
748 |
if (false && false === $notice_shown && $meta['reset_count'] >= 2 && false == $this->get_dismissed_notices('tidy')) {
|
749 |
echo '<div class="card notice-wrapper">';
|
@@ -760,6 +831,7 @@ class WP_Reset {
|
|
760 |
echo '<ul class="wpr-main-tab">';
|
761 |
echo '<li><a href="#tab-reset">' . __('Reset', 'wp-reset') . '</a></li>';
|
762 |
echo '<li><a href="#tab-tools">' . __('Tools', 'wp-reset') . '</a></li>';
|
|
|
763 |
echo '<li><a href="#tab-support">' . __('Support', 'wp-reset') . '</a></li>';
|
764 |
if (empty($notices['geoip_tab'])) {
|
765 |
echo '<li><a href="#tab-geoip">' . __('IP Geolocation', 'wp-reset') . '</a></li>';
|
@@ -773,6 +845,10 @@ class WP_Reset {
|
|
773 |
echo '<div style="display: none;" id="tab-tools">';
|
774 |
$this->tab_tools();
|
775 |
echo '</div>';
|
|
|
|
|
|
|
|
|
776 |
|
777 |
echo '<div style="display: none;" id="tab-support">';
|
778 |
$this->tab_support();
|
@@ -908,9 +984,101 @@ class WP_Reset {
|
|
908 |
echo '<h2>' . __('Private contact', 'wp-reset') . '</h2>';
|
909 |
echo '<p>' . __('If there\'s a need to contact us privately send emails to <a href="mailto:wpreset@webfactoryltd.com">wpreset@webfactoryltd.com</a>. Please know that although we\'ll gladly have a look at issues you are having with any site, we can\'t promise we\'ll fix them. Thank you for understanding.', 'wp-reset') . '</p>';
|
910 |
echo '</div>';
|
|
|
|
|
|
|
|
|
|
|
911 |
} // tab_support
|
912 |
|
913 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
914 |
/**
|
915 |
* Echoes content for sponsor tab
|
916 |
*
|
@@ -941,6 +1109,11 @@ class WP_Reset {
|
|
941 |
/**
|
942 |
* Helper function for generating UTM tagged links
|
943 |
*
|
|
|
|
|
|
|
|
|
|
|
944 |
* @return string
|
945 |
*/
|
946 |
function generate_web_link($placement = '', $page = '/', $params = array(), $anchor = '') {
|
@@ -965,6 +1138,520 @@ class WP_Reset {
|
|
965 |
} // generate_web_link
|
966 |
|
967 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
968 |
/**
|
969 |
* Clean up on uninstall; no action on deactive at the moment
|
970 |
*
|
@@ -972,6 +1659,7 @@ class WP_Reset {
|
|
972 |
*/
|
973 |
static function uninstall() {
|
974 |
delete_option('wp-reset');
|
|
|
975 |
} // uninstall
|
976 |
|
977 |
|
3 |
Plugin Name: WP Reset
|
4 |
Plugin URI: https://wpreset.com/
|
5 |
Description: Reset the site to default installation values without modifying any files. Deletes all customizations and content.
|
6 |
+
Version: 1.40
|
7 |
Author: WebFactory Ltd
|
8 |
Author URI: https://www.webfactoryltd.com/
|
9 |
Text Domain: wp-reset
|
26 |
|
27 |
// include only file
|
28 |
if (!defined('ABSPATH')) {
|
29 |
+
wp_die(__('Do not open this file directly.', 'wp-error'));
|
30 |
}
|
31 |
|
32 |
|
41 |
public $version = 0;
|
42 |
public $plugin_url = '';
|
43 |
public $plugin_dir = '';
|
44 |
+
public $snapshots_folder = 'wp-reset-snapshots-export';
|
45 |
protected $options = array();
|
46 |
private $delete_count = 0;
|
47 |
+
private $core_tables = array('commentmeta', 'comments', 'links', 'options', 'postmeta', 'posts', 'term_relationships', 'term_taxonomy', 'termmeta', 'terms', 'usermeta', 'users');
|
48 |
|
49 |
|
50 |
/**
|
81 |
add_filter('plugin_action_links_' . plugin_basename(__FILE__), array($this, 'plugin_action_links'));
|
82 |
add_filter('plugin_row_meta', array($this, 'plugin_meta_links'), 10, 2);
|
83 |
add_filter('admin_footer_text', array($this, 'admin_footer_text'));
|
84 |
+
|
85 |
+
$this->core_tables = array_map(function($tbl) { global $wpdb; return $wpdb->prefix . $tbl; }, $this->core_tables);
|
86 |
} // __construct
|
87 |
|
88 |
|
146 |
/**
|
147 |
* Get all dismissed notices, or check for one specific notice
|
148 |
*
|
149 |
+
* @param string $notice_name Optional. Check if specified notice is dismissed.
|
150 |
+
*
|
151 |
* @return bool|array
|
152 |
*/
|
153 |
function get_dismissed_notices($notice_name = '') {
|
170 |
*
|
171 |
* todo: not completed
|
172 |
*
|
173 |
+
* @param string $key Optional.
|
174 |
+
*
|
175 |
* @return array
|
176 |
*/
|
177 |
function get_options($key = '') {
|
184 |
*
|
185 |
* todo: this handles the entire options array although it should only do the options part - it's confusing
|
186 |
*
|
187 |
+
* @param string $key Data to save.
|
188 |
+
* @param string $data Option key.
|
189 |
+
*
|
190 |
* @return bool
|
191 |
*/
|
192 |
function update_options($key, $data) {
|
227 |
/**
|
228 |
* Dismiss notice by adding it to dismissed_notices options array
|
229 |
*
|
230 |
+
* @param string $notice_name Notice to dismiss.
|
231 |
+
*
|
232 |
* @return bool
|
233 |
*/
|
234 |
function dismiss_notice($notice_name) {
|
291 |
$options = $this->get_options();
|
292 |
|
293 |
$js_localize = array('undocumented_error' => __('An undocumented error has occured. Please refresh the page and try again.', 'wp-reset'),
|
294 |
+
'documented_error' => __('An error has occured.', 'wp-reset'),
|
295 |
'plugin_name' => __('WP Reset', 'wp-reset'),
|
296 |
'settings_url' => admin_url('tools.php?page=wp-reset'),
|
297 |
'icon_url' => $this->plugin_url . 'img/wp-reset-icon.png',
|
349 |
/**
|
350 |
* Deletes all transients.
|
351 |
*
|
352 |
+
* @return int Number of deleted transient DB entries
|
353 |
+
*/
|
354 |
+
function do_delete_transients() {
|
355 |
global $wpdb;
|
356 |
|
357 |
+
$count = $wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE '\_transient\_%' OR option_name LIKE '\_site\_transient\_%'");
|
358 |
|
359 |
return $count;
|
360 |
} // do_delete_transients
|
363 |
/**
|
364 |
* Deletes all files in uploads folder.
|
365 |
*
|
366 |
+
* @return int Number of deleted files and folders.
|
367 |
+
*/
|
368 |
+
function do_delete_uploads() {
|
369 |
$upload_dir = wp_get_upload_dir();
|
370 |
|
371 |
$this->delete_folder($upload_dir['basedir'], $upload_dir['basedir']);
|
372 |
|
373 |
return $this->delete_count;
|
374 |
+
} // do_delete_uploads
|
375 |
|
376 |
|
377 |
/**
|
378 |
* Recursively deletes a folder
|
379 |
*
|
380 |
+
* @param string $folder Recursive param.
|
381 |
+
* @param string $base_folder Base folder.
|
382 |
+
*
|
383 |
* @return bool
|
384 |
+
*/
|
385 |
private function delete_folder($folder, $base_folder) {
|
386 |
$files = array_diff(scandir($folder), array('.', '..'));
|
387 |
|
392 |
$tmp = @unlink($folder . DIRECTORY_SEPARATOR . $file);
|
393 |
$this->delete_count = $this->delete_count + (int) $tmp;
|
394 |
}
|
395 |
+
} // foreach
|
396 |
|
397 |
if ($folder != $base_folder) {
|
398 |
$tmp = @rmdir($folder);
|
407 |
/**
|
408 |
* Deactivate and delete all plugins
|
409 |
*
|
410 |
+
* @param bool $keep_wp_reset Keep WP Reset active and installed
|
411 |
+
* @param bool $silent_deactivate Skip individual plugin deactivation functions when deactivating
|
412 |
+
*
|
413 |
+
* @return int Number of deleted plugins.
|
414 |
*/
|
415 |
function do_delete_plugins($keep_wp_reset = true, $silent_deactivate = false) {
|
416 |
if (!function_exists('get_plugins')) {
|
443 |
/**
|
444 |
* Delete all themes
|
445 |
*
|
446 |
+
* @param bool $keep_default_theme Keep default theme
|
447 |
+
*
|
448 |
+
* @return int Number of deleted themes.
|
449 |
*/
|
450 |
function do_delete_themes($keep_default_theme = true) {
|
451 |
$default_theme = 'twentyseventeen';
|
469 |
|
470 |
|
471 |
/**
|
472 |
+
* Run one tool via AJAX call
|
473 |
*
|
474 |
* @return null
|
475 |
*/
|
477 |
check_ajax_referer('wp-reset_run_tool');
|
478 |
|
479 |
$tool = trim(@$_GET['tool']);
|
480 |
+
$extra_data = trim(@$_GET['extra_data']);
|
481 |
|
482 |
if ($tool == 'delete_transients') {
|
483 |
$cnt = $this->do_delete_transients();
|
491 |
} elseif ($tool == 'delete_uploads') {
|
492 |
$cnt = $this->do_delete_uploads();
|
493 |
wp_send_json_success($cnt);
|
494 |
+
} elseif ($tool == 'delete_snapshot') {
|
495 |
+
$res = $this->do_delete_snapshot($extra_data);
|
496 |
+
if (is_wp_error($res)) {
|
497 |
+
wp_send_json_error($res->get_error_message());
|
498 |
+
} else {
|
499 |
+
wp_send_json_success();
|
500 |
+
}
|
501 |
+
} elseif ($tool == 'download_snapshot') {
|
502 |
+
$res = $this->do_export_snapshot($extra_data);
|
503 |
+
if (is_wp_error($res)) {
|
504 |
+
wp_send_json_error($res->get_error_message());
|
505 |
+
} else {
|
506 |
+
$url = content_url() . '/' . $this->snapshots_folder . '/' . $res;
|
507 |
+
wp_send_json_success($url);
|
508 |
+
}
|
509 |
+
} elseif ($tool == 'restore_snapshot') {
|
510 |
+
$res = $this->do_restore_snapshot($extra_data);
|
511 |
+
if (is_wp_error($res)) {
|
512 |
+
wp_send_json_error($res->get_error_message());
|
513 |
+
} else {
|
514 |
+
wp_send_json_success();
|
515 |
+
}
|
516 |
+
} elseif ($tool == 'compare_snapshots') {
|
517 |
+
$res = $this->do_compare_snapshots($extra_data);
|
518 |
+
if (is_wp_error($res)) {
|
519 |
+
wp_send_json_error($res->get_error_message());
|
520 |
+
} else {
|
521 |
+
wp_send_json_success($res);
|
522 |
+
}
|
523 |
+
} elseif ($tool == 'create_snapshot') {
|
524 |
+
$res = $this->do_create_snapshot($extra_data);
|
525 |
+
if (is_wp_error($res)) {
|
526 |
+
wp_send_json_error($res->get_error_message());
|
527 |
+
} else {
|
528 |
+
wp_send_json_success();
|
529 |
+
}
|
530 |
} else {
|
531 |
wp_send_json_error(__('Unknown tool.', 'wp-reset'));
|
532 |
}
|
538 |
* There are no failsafes in the function - it reinstalls when called
|
539 |
* Redirects when done
|
540 |
*
|
541 |
+
* @param array $params Optional.
|
542 |
+
*
|
543 |
* @return null
|
544 |
*/
|
545 |
function do_reinstall($params = array()) {
|
562 |
$wplang = get_option('wplang');
|
563 |
$siteurl = get_option('siteurl');
|
564 |
$home = get_option('home');
|
565 |
+
$snapshots = $this->get_snapshots();
|
566 |
|
567 |
$active_plugins = get_option('active_plugins');
|
568 |
$active_theme = wp_get_theme();
|
596 |
update_option('siteurl', $siteurl);
|
597 |
update_option('home', $home);
|
598 |
update_option('wp-reset', $this->options);
|
599 |
+
update_option('wp-reset-snapshots', $snapshots);
|
600 |
|
601 |
// remove password nag
|
602 |
if (get_user_meta($user_id, 'default_password_nag')) {
|
643 |
* Checks wp_reset post value and performs all actions
|
644 |
* todo: handle messages for various actions
|
645 |
*
|
646 |
+
* @return null|bool
|
647 |
*/
|
648 |
function do_all_actions() {
|
649 |
// only admins can perform actions
|
676 |
|
677 |
$this->do_reinstall($params);
|
678 |
}
|
679 |
+
} // do_all_actions
|
680 |
|
681 |
|
682 |
/**
|
683 |
* Add "Reset WordPress" action link to plugins table, left part
|
684 |
*
|
685 |
+
* @param array $links Initial list of links.
|
686 |
+
*
|
687 |
* @return array
|
688 |
*/
|
689 |
function plugin_action_links($links) {
|
698 |
/**
|
699 |
* Add links to plugin's description in plugins table
|
700 |
*
|
701 |
+
* @param array $links Initial list of links.
|
702 |
+
* @param string $file Basename of current plugin.
|
703 |
+
*
|
704 |
* @return array
|
705 |
*/
|
706 |
function plugin_meta_links($links, $file) {
|
710 |
|
711 |
$support_link = '<a target="_blank" href="https://wordpress.org/support/plugin/wp-reset" title="' . __('Get help', 'wp-reset') . '">' . __('Support', 'wp-reset') . '</a>';
|
712 |
$home_link = '<a target="_blank" href="' . $this->generate_web_link('plugins-table-right') . '" title="' . __('Plugin Homepage', 'wp-reset') . '">' . __('Plugin Homepage', 'wp-reset') . '</a>';
|
713 |
+
$rate_link = '<a target="_blank" href="https://wordpress.org/support/plugin/wp-reset/reviews/#new-post" title="' . __('Rate the plugin', 'wp-reset') . '">' . __('Rate the plugin ★★★★★', 'wp-reset') . '</a>';
|
714 |
|
715 |
$links[] = $support_link;
|
716 |
$links[] = $home_link;
|
717 |
+
$links[] = $rate_link;
|
718 |
|
719 |
return $links;
|
720 |
} // plugin_meta_links
|
721 |
|
722 |
|
723 |
/**
|
724 |
+
* Test if we're on WPR's admin page
|
725 |
*
|
726 |
* @return bool
|
727 |
*/
|
739 |
/**
|
740 |
* Add powered by text in admin footer
|
741 |
*
|
742 |
+
* @param string $text Default footer text.
|
743 |
+
*
|
744 |
+
* @return string
|
745 |
*/
|
746 |
function admin_footer_text($text) {
|
747 |
if (!$this->is_plugin_page()) {
|
755 |
|
756 |
|
757 |
/**
|
758 |
+
* Loads plugin's translated strings
|
759 |
*
|
760 |
* @return null
|
761 |
*/
|
785 |
$notice_shown = false;
|
786 |
$meta = $this->get_meta();
|
787 |
$notices = $this->get_dismissed_notices();
|
788 |
+
$snapshots = $this->get_snapshots();
|
789 |
|
790 |
// double check for admin priv
|
791 |
if (!current_user_can('administrator')) {
|
799 |
|
800 |
if (false === $notice_shown && is_multisite()) {
|
801 |
echo '<div class="card notice-wrapper notice-error">';
|
802 |
+
echo '<h2>' . __('WP Reset is not compatible with multisite!', 'wp-reset') . '</h2>';
|
803 |
echo '<p>' . __('Please be careful when using WP Reset with multisite enabled. It\'s not recommended to reset the main site. Sub-sites should be OK. We\'re working on making it fully compatible with WP-MU. <b>Till then please be careful.</b> Thank you for understanding.', 'wp-reset') . '</p>';
|
804 |
echo '</div>';
|
805 |
$notice_shown = true;
|
806 |
}
|
807 |
|
808 |
+
if ((!empty($meta['reset_count']) || !empty($snapshots)) && false === $notice_shown && false == $this->get_dismissed_notices('rate')) {
|
809 |
echo '<div class="card notice-wrapper">';
|
810 |
echo '<h2>' . __('Please help us keep the plugin free & up-to-date', 'wp-reset') . '</h2>';
|
811 |
echo '<p>' . __('If you use & enjoy WP Reset, <b>please rate it on WordPress.org</b>. It only takes a second and helps us keep the plugin free and maintained. Thank you!', 'wp-reset') . '</p>';
|
814 |
$notice_shown = true;
|
815 |
}
|
816 |
|
817 |
+
// Tidy Repo ad
|
818 |
// disabled for now
|
819 |
if (false && false === $notice_shown && $meta['reset_count'] >= 2 && false == $this->get_dismissed_notices('tidy')) {
|
820 |
echo '<div class="card notice-wrapper">';
|
831 |
echo '<ul class="wpr-main-tab">';
|
832 |
echo '<li><a href="#tab-reset">' . __('Reset', 'wp-reset') . '</a></li>';
|
833 |
echo '<li><a href="#tab-tools">' . __('Tools', 'wp-reset') . '</a></li>';
|
834 |
+
echo '<li><a href="#tab-snapshots">' . __('DB Snapshots', 'wp-reset') . '</a></li>';
|
835 |
echo '<li><a href="#tab-support">' . __('Support', 'wp-reset') . '</a></li>';
|
836 |
if (empty($notices['geoip_tab'])) {
|
837 |
echo '<li><a href="#tab-geoip">' . __('IP Geolocation', 'wp-reset') . '</a></li>';
|
845 |
echo '<div style="display: none;" id="tab-tools">';
|
846 |
$this->tab_tools();
|
847 |
echo '</div>';
|
848 |
+
|
849 |
+
echo '<div style="display: none;" id="tab-snapshots">';
|
850 |
+
$this->tab_snapshots();
|
851 |
+
echo '</div>';
|
852 |
|
853 |
echo '<div style="display: none;" id="tab-support">';
|
854 |
$this->tab_support();
|
984 |
echo '<h2>' . __('Private contact', 'wp-reset') . '</h2>';
|
985 |
echo '<p>' . __('If there\'s a need to contact us privately send emails to <a href="mailto:wpreset@webfactoryltd.com">wpreset@webfactoryltd.com</a>. Please know that although we\'ll gladly have a look at issues you are having with any site, we can\'t promise we\'ll fix them. Thank you for understanding.', 'wp-reset') . '</p>';
|
986 |
echo '</div>';
|
987 |
+
|
988 |
+
echo '<div class="card">';
|
989 |
+
echo '<h2>' . __('Care to help out?', 'wp-reset') . '</h2>';
|
990 |
+
echo '<p>' . __('No need for donations or anything like that :) If you can give us a <a href="https://wordpress.org/support/plugin/wp-reset/reviews/#new-post" target="_blank">five star rating</a> you\'ll help out more than you can imagine. Thank you!', 'wp-reset') . '</p>';
|
991 |
+
echo '</div>';
|
992 |
} // tab_support
|
993 |
|
994 |
|
995 |
+
/**
|
996 |
+
* Echoes content for snapshots tab
|
997 |
+
*
|
998 |
+
* @return null
|
999 |
+
*/
|
1000 |
+
private function tab_snapshots() {
|
1001 |
+
global $wpdb;
|
1002 |
+
$tbl_core = $tbl_custom = $tbl_size = $tbl_rows = 0;
|
1003 |
+
|
1004 |
+
echo '<div class="card" id="card-snapshots">';
|
1005 |
+
echo '<a class="toggle-card" href="#" title="' . __('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a>';
|
1006 |
+
echo '<h2>' . __('Database Snapshots', 'wp-reset') . '</h2>';
|
1007 |
+
echo '<p>A snapshot is a copy of all WP database tables, standard and custom ones, saved in your database. Files are not saved or included in snapshots in any way.<br>
|
1008 |
+
Snapshots are primarily a development tool. Although they can be used for backups (and downloaded), we suggest finding a more suitable tool for live sites, such as <a href="https://wordpress.org/plugins/updraftplus/" target="_blank">UpdraftPlus</a>. Use snapshots to find out what changes a plugin made to your database or to quickly restore the dev environment after testing database related changes.<br>Restoring a snapshot does not affect other snapshots, or WP Reset settings.</p>';
|
1009 |
+
echo '<p>Snapshots are still in development. If you see a bug or just have an idea how to make the tool better, please let us know <a href="https://twitter.com/WebFactoryLtd" target="_blank">@webfactoryltd</a> or <a href="mailto:wpreset@webfactoryltd.com?subject=WPR%20DB%20Snapshots%20Feedback">email us</a>. Thank you!</p>';
|
1010 |
+
|
1011 |
+
$table_status = $wpdb->get_results('SHOW TABLE STATUS');
|
1012 |
+
if (is_array($table_status)) {
|
1013 |
+
foreach ($table_status as $index => $table) {
|
1014 |
+
if (0 !== stripos($table->Name, $wpdb->prefix)) {
|
1015 |
+
continue;
|
1016 |
+
}
|
1017 |
+
if (empty($table->Engine)) {
|
1018 |
+
continue;
|
1019 |
+
}
|
1020 |
+
|
1021 |
+
$tbl_rows += $table->Rows;
|
1022 |
+
$tbl_size += $table->Data_length + $table->Index_length;
|
1023 |
+
if (in_array($table->Name, $this->core_tables)) {
|
1024 |
+
$tbl_core++;
|
1025 |
+
} else {
|
1026 |
+
$tbl_custom++;
|
1027 |
+
}
|
1028 |
+
} // foreach
|
1029 |
+
|
1030 |
+
echo '<p><b>Currently used WordPress tables</b>, prefixed with <i>' . $wpdb->prefix . '</i>, consist of ' . $tbl_core . ' standard and ';
|
1031 |
+
if ($tbl_custom) {
|
1032 |
+
echo $tbl_custom . ' custom table' . ($tbl_custom == 1? '': 's');
|
1033 |
+
} else {
|
1034 |
+
echo 'no custom tables';
|
1035 |
+
}
|
1036 |
+
echo ' totaling ' . $this->format_size($tbl_size) .' in ' . number_format($tbl_rows) . ' rows.</p>';
|
1037 |
+
}
|
1038 |
+
|
1039 |
+
echo '';
|
1040 |
+
echo '</div>';
|
1041 |
+
|
1042 |
+
echo '<div class="card no-padding-bottom">';
|
1043 |
+
echo '<a id="create-new-snapshot-primary" data-msg-success="Snapshot created!" data-msg-wait="Creating snapshot. Please wait." data-btn-confirm="Create snapshot" data-placeholder="Snapshot name or brief description, ie: before plugin install" data-text="Enter snapshot name or brief description, up to 64 characters." data-title="Create a new snapshot" title="Create a new database snapshot" href="#" class="button button-primary create-new-snapshot create-new-snapshot-corner">' . __('Create new', 'wp-reset') . '</a>';
|
1044 |
+
echo '<h2>' . __('Saved Snapshots', 'wp-reset') . '</h2>';
|
1045 |
+
|
1046 |
+
if ($snapshots = $this->get_snapshots()) {
|
1047 |
+
echo '<table id="wpr-snapshots">';
|
1048 |
+
echo '<tr><th>Name</th><th>Info & Size</th><th class="ss-actions">Actions</th></tr>';
|
1049 |
+
foreach ($snapshots as $ss) {
|
1050 |
+
echo '<tr id="wpr-ss-' . $ss['uid'] . '">';
|
1051 |
+
if (!empty($ss['name'])) {
|
1052 |
+
echo '<td title="Created on ' . date(get_option('date_format'), strtotime($ss['timestamp'])) . ' @ ' . date(get_option('time_format'), strtotime($ss['timestamp'])) . '">' . $ss['name'] . '</td>';
|
1053 |
+
$name = $ss['name'];
|
1054 |
+
} else {
|
1055 |
+
echo '<td title="Created on ' . date(get_option('date_format'), strtotime($ss['timestamp'])) . ' @ ' . date(get_option('time_format'), strtotime($ss['timestamp'])) . '">' . '' . date(get_option('date_format'), strtotime($ss['timestamp'])) . '<br>@ ' . date(get_option('time_format'), strtotime($ss['timestamp'])) . '</td>';
|
1056 |
+
$name = 'created on ' . date(get_option('date_format'), strtotime($ss['timestamp'])) . ' @ ' . date(get_option('time_format'), strtotime($ss['timestamp']));
|
1057 |
+
}
|
1058 |
+
echo '<td>' . $ss['tbl_core'] . ' standard & ';
|
1059 |
+
if ($ss['tbl_custom']) {
|
1060 |
+
echo $ss['tbl_custom'] . ' custom table' . ($ss['tbl_custom'] == 1? '': 's');
|
1061 |
+
} else {
|
1062 |
+
echo 'no custom tables';
|
1063 |
+
}
|
1064 |
+
echo ' totaling ' . $this->format_size($ss['tbl_size']) . ' in ' . number_format($ss['tbl_rows']) . ' rows</td>';
|
1065 |
+
echo '<td>';
|
1066 |
+
echo '<a data-title="Current DB tables compared to snapshot %s" data-wait-msg="Comparing. Please wait." data-name="' . $name . '" title="Compare snapshot to current database tables" href="#" class="ss-action compare-snapshot" data-ss-uid="' . $ss['uid'] . '"><span class="dashicons dashicons-visibility"></span></a>';
|
1067 |
+
echo '<a data-btn-confirm="Restore snapshot" data-text-wait="Restoring snapshot. Please wait." data-text-confirm="Are you sure you want to restore the selected snapshot? There is NO UNDO.<br>Restoring the snapshot will delete all current standard and custom tables and replace them with tables from the snapshot." data-text-done="Snapshot has been restored. Click OK to reload the page with new data." title="Restore snapshot by overwriting current database tables" href="#" class="ss-action restore-snapshot" data-ss-uid="' . $ss['uid'] . '"><span class="dashicons dashicons-backup"></span></a>';
|
1068 |
+
echo '<a data-success-msg="Snapshot export created!<br><a href=\'%s\'>Download it</a>" data-wait-msg="Exporting snapshot. Please wait." title="Download snapshot as gzipped SQL dump" href="#" class="ss-action download-snapshot" data-ss-uid="' . $ss['uid'] . '"><span class="dashicons dashicons-download"></span></a>';
|
1069 |
+
echo '<a data-btn-confirm="Delete snapshot" data-text-wait="Deleting snapshot. Please wait." data-text-confirm="Are you sure you want to delete the selected snapshot and all its data? There is NO UNDO.<br>Deleting the snapshot will not affect the active database tables in any way." data-text-done="Snapshot has been deleted." title="Permanently delete snapshot" href="#" class="ss-action delete-snapshot" data-ss-uid="' . $ss['uid'] . '"><span class="dashicons dashicons-trash"></span></a></td>';
|
1070 |
+
echo '</tr>';
|
1071 |
+
} // foreach
|
1072 |
+
echo '</table>';
|
1073 |
+
echo '<p id="ss-no-snapshots" class="hidden">There are no saved snapshots. <a href="#" class="create-new-snapshot">Create a new snapshot.</a></p>';
|
1074 |
+
} else {
|
1075 |
+
echo '<p id="ss-no-snapshots">There are no saved snapshots. <a href="#" class="create-new-snapshot">Create a new snapshot.</a></p>';
|
1076 |
+
}
|
1077 |
+
|
1078 |
+
echo '</div>';
|
1079 |
+
} // tab_snapshots
|
1080 |
+
|
1081 |
+
|
1082 |
/**
|
1083 |
* Echoes content for sponsor tab
|
1084 |
*
|
1109 |
/**
|
1110 |
* Helper function for generating UTM tagged links
|
1111 |
*
|
1112 |
+
* @param string $placement Optional. UTM content param.
|
1113 |
+
* @param string $page Optional. Page to link to.
|
1114 |
+
* @param array $params Optional. Extra URL params.
|
1115 |
+
* @param string $anchor Optional. URL anchor part.
|
1116 |
+
*
|
1117 |
* @return string
|
1118 |
*/
|
1119 |
function generate_web_link($placement = '', $page = '/', $params = array(), $anchor = '') {
|
1138 |
} // generate_web_link
|
1139 |
|
1140 |
|
1141 |
+
/**
|
1142 |
+
* Returns all saved snapshots from DB
|
1143 |
+
*
|
1144 |
+
* @return array
|
1145 |
+
*/
|
1146 |
+
function get_snapshots() {
|
1147 |
+
$snapshots = get_option('wp-reset-snapshots', array());
|
1148 |
+
|
1149 |
+
return $snapshots;
|
1150 |
+
} // get_snapshots
|
1151 |
+
|
1152 |
+
|
1153 |
+
/**
|
1154 |
+
* Format file size to human readable string
|
1155 |
+
*
|
1156 |
+
* @param int $bytes Size in bytes to format.
|
1157 |
+
*
|
1158 |
+
* @return string
|
1159 |
+
*/
|
1160 |
+
function format_size($bytes) {
|
1161 |
+
if ($bytes > 1073741824) {
|
1162 |
+
return number_format_i18n($bytes / 1073741824, 2) . ' GB';
|
1163 |
+
} elseif ($bytes > 1048576) {
|
1164 |
+
return number_format_i18n($bytes / 1048576, 1) . ' MB';
|
1165 |
+
} elseif ($bytes > 1024) {
|
1166 |
+
return number_format_i18n($bytes / 1024, 1) . ' KB';
|
1167 |
+
} else {
|
1168 |
+
return number_format_i18n($bytes, 0) . ' bytes';
|
1169 |
+
}
|
1170 |
+
} // format_size
|
1171 |
+
|
1172 |
+
|
1173 |
+
/**
|
1174 |
+
* Creates snapshot of current tables by copying them in the DB and saving metadata.
|
1175 |
+
*
|
1176 |
+
* @param int $name Optional. Name for the new snapshot.
|
1177 |
+
*
|
1178 |
+
* @return array|WP_Error Snapshot details in array on success, or error object on fail.
|
1179 |
+
*/
|
1180 |
+
function do_create_snapshot($name = '') {
|
1181 |
+
global $wpdb;
|
1182 |
+
$snapshots = $this->get_snapshots();
|
1183 |
+
$snapshot = array();
|
1184 |
+
$uid = $this->generate_snapshot_uid();
|
1185 |
+
$tbl_core = $tbl_custom = $tbl_size = $tbl_rows = 0;
|
1186 |
+
|
1187 |
+
if (!$uid) {
|
1188 |
+
return new WP_Error(1, 'Unable to generate a valid snapshot UID.');
|
1189 |
+
}
|
1190 |
+
|
1191 |
+
if ($name) {
|
1192 |
+
$snapshot['name'] = substr(trim($name), 0, 64);
|
1193 |
+
} else {
|
1194 |
+
$snapshot['name'] = '';
|
1195 |
+
}
|
1196 |
+
$snapshot['uid'] = $uid;
|
1197 |
+
$snapshot['timestamp'] = current_time('mysql');
|
1198 |
+
|
1199 |
+
$table_status = $wpdb->get_results('SHOW TABLE STATUS');
|
1200 |
+
if (is_array($table_status)) {
|
1201 |
+
foreach ($table_status as $index => $table) {
|
1202 |
+
if (0 !== stripos($table->Name, $wpdb->prefix)) {
|
1203 |
+
continue;
|
1204 |
+
}
|
1205 |
+
if (empty($table->Engine)) {
|
1206 |
+
continue;
|
1207 |
+
}
|
1208 |
+
|
1209 |
+
$tbl_rows += $table->Rows;
|
1210 |
+
$tbl_size += $table->Data_length + $table->Index_length;
|
1211 |
+
if (in_array($table->Name, $this->core_tables)) {
|
1212 |
+
$tbl_core++;
|
1213 |
+
} else {
|
1214 |
+
$tbl_custom++;
|
1215 |
+
}
|
1216 |
+
|
1217 |
+
$wpdb->query('OPTIMIZE TABLE ' . $table->Name);
|
1218 |
+
$wpdb->query('CREATE TABLE ' . $uid . '_' . $table->Name .' LIKE ' . $table->Name);
|
1219 |
+
$wpdb->query('INSERT ' . $uid . '_' . $table->Name . ' SELECT * FROM ' . $table->Name);
|
1220 |
+
} // foreach
|
1221 |
+
} else {
|
1222 |
+
return new WP_Error(1, 'Can\'t get table status data.');
|
1223 |
+
}
|
1224 |
+
|
1225 |
+
$snapshot['tbl_core'] = $tbl_core;
|
1226 |
+
$snapshot['tbl_custom'] = $tbl_custom;
|
1227 |
+
$snapshot['tbl_rows'] = $tbl_rows;
|
1228 |
+
$snapshot['tbl_size'] = $tbl_size;
|
1229 |
+
|
1230 |
+
|
1231 |
+
$snapshots[$uid] = $snapshot;
|
1232 |
+
update_option('wp-reset-snapshots', $snapshots);
|
1233 |
+
|
1234 |
+
return $snapshot;
|
1235 |
+
} // create_snapshot
|
1236 |
+
|
1237 |
+
|
1238 |
+
/**
|
1239 |
+
* Delete snapshot metadata and tables from DB
|
1240 |
+
*
|
1241 |
+
* @param string $uid Snapshot unique 6-char ID.
|
1242 |
+
*
|
1243 |
+
* @return bool|WP_Error True on success, or error object on fail.
|
1244 |
+
*/
|
1245 |
+
function do_delete_snapshot($uid = '') {
|
1246 |
+
global $wpdb;
|
1247 |
+
$snapshots = $this->get_snapshots();
|
1248 |
+
|
1249 |
+
if (strlen($uid) != 6) {
|
1250 |
+
return new WP_Error(1, 'Invalid UID format.');
|
1251 |
+
}
|
1252 |
+
|
1253 |
+
if (!isset($snapshots[$uid])) {
|
1254 |
+
return new WP_Error(1, 'Unknown snapshot ID.');
|
1255 |
+
}
|
1256 |
+
|
1257 |
+
$tables = $wpdb->get_col($wpdb->prepare('SHOW TABLES LIKE %s', array($uid . '\_%')));
|
1258 |
+
foreach ($tables as $table) {
|
1259 |
+
$wpdb->query('DROP TABLE IF EXISTS ' . $table);
|
1260 |
+
}
|
1261 |
+
|
1262 |
+
unset($snapshots[$uid]);
|
1263 |
+
update_option('wp-reset-snapshots', $snapshots);
|
1264 |
+
|
1265 |
+
return true;
|
1266 |
+
} // delete_snapshot
|
1267 |
+
|
1268 |
+
|
1269 |
+
/**
|
1270 |
+
* Exports snapshot as SQL dump; saved in gzipped file in WP_CONTENT folder.
|
1271 |
+
*
|
1272 |
+
* @param string $uid Snapshot unique 6-char ID.
|
1273 |
+
*
|
1274 |
+
* @return string|WP_Error Export base filename, or error object on fail.
|
1275 |
+
*/
|
1276 |
+
function do_export_snapshot($uid = '') {
|
1277 |
+
global $wpdb;
|
1278 |
+
$snapshots = $this->get_snapshots();
|
1279 |
+
|
1280 |
+
if (strlen($uid) != 6) {
|
1281 |
+
return new WP_Error(1, 'Invalid snapshot ID format.');
|
1282 |
+
}
|
1283 |
+
|
1284 |
+
if (!isset($snapshots[$uid])) {
|
1285 |
+
return new WP_Error(1, 'Unknown snapshot ID.');
|
1286 |
+
}
|
1287 |
+
|
1288 |
+
require_once $this->plugin_dir . 'libs/dumper.php';
|
1289 |
+
|
1290 |
+
try {
|
1291 |
+
$world_dumper = Shuttle_Dumper::create(array(
|
1292 |
+
'host' => DB_HOST,
|
1293 |
+
'username' => DB_USER,
|
1294 |
+
'password' => DB_PASSWORD,
|
1295 |
+
'db_name' => DB_NAME,
|
1296 |
+
));
|
1297 |
+
|
1298 |
+
$folder = wp_mkdir_p(trailingslashit(WP_CONTENT_DIR) . $this->snapshots_folder);
|
1299 |
+
if (!$folder) {
|
1300 |
+
return new WP_Error(1, 'Unable to create wp-content/' . $this->snapshots_folder . '/ folder.');
|
1301 |
+
}
|
1302 |
+
|
1303 |
+
$world_dumper->dump(trailingslashit(WP_CONTENT_DIR) . $this->snapshots_folder . '/wp-reset-snapshot-' . $uid . '.sql.gz', $uid . '_');
|
1304 |
+
} catch(Shuttle_Exception $e) {
|
1305 |
+
return new WP_Error(1, "Couldn't dump snapshot: " . $e->getMessage());
|
1306 |
+
}
|
1307 |
+
|
1308 |
+
return 'wp-reset-snapshot-' . $uid . '.sql.gz';
|
1309 |
+
} // export_snapshot
|
1310 |
+
|
1311 |
+
|
1312 |
+
/**
|
1313 |
+
* Replace current tables with ones in snapshot.
|
1314 |
+
*
|
1315 |
+
* @param string $uid Snapshot unique 6-char ID.
|
1316 |
+
*
|
1317 |
+
* @return bool|WP_Error True on success, or error object on fail.
|
1318 |
+
*/
|
1319 |
+
function do_restore_snapshot($uid = '') {
|
1320 |
+
global $wpdb;
|
1321 |
+
$new_tables = array();
|
1322 |
+
$snapshots = $this->get_snapshots();
|
1323 |
+
|
1324 |
+
if (($res = $this->verify_snapshot_integrity($uid)) !== true) {
|
1325 |
+
return $res;
|
1326 |
+
}
|
1327 |
+
|
1328 |
+
$table_status = $wpdb->get_results('SHOW TABLE STATUS');
|
1329 |
+
if (is_array($table_status)) {
|
1330 |
+
foreach ($table_status as $index => $table) {
|
1331 |
+
if (0 !== stripos($table->Name, $uid . '_')) {
|
1332 |
+
continue;
|
1333 |
+
}
|
1334 |
+
if (empty($table->Engine)) {
|
1335 |
+
continue;
|
1336 |
+
}
|
1337 |
+
|
1338 |
+
$new_tables[] = $table->Name;
|
1339 |
+
} // foreach
|
1340 |
+
} else {
|
1341 |
+
return new WP_Error(1, 'Can\'t get table status data.');
|
1342 |
+
}
|
1343 |
+
|
1344 |
+
foreach ($table_status as $index => $table) {
|
1345 |
+
if (0 !== stripos($table->Name, $wpdb->prefix)) {
|
1346 |
+
continue;
|
1347 |
+
}
|
1348 |
+
if (empty($table->Engine)) {
|
1349 |
+
continue;
|
1350 |
+
}
|
1351 |
+
|
1352 |
+
$wpdb->query('DROP TABLE ' . $table->Name);
|
1353 |
+
} // foreach
|
1354 |
+
|
1355 |
+
// copy snapshot tables to original name
|
1356 |
+
foreach ($new_tables as $table) {
|
1357 |
+
$new_name = str_replace($uid . '_', '', $table);
|
1358 |
+
|
1359 |
+
$wpdb->query('CREATE TABLE ' . $new_name . ' LIKE ' . $table);
|
1360 |
+
$wpdb->query('INSERT ' . $new_name . ' SELECT * FROM ' . $table);
|
1361 |
+
}
|
1362 |
+
|
1363 |
+
wp_cache_flush();
|
1364 |
+
update_option('wp-reset', $this->options);
|
1365 |
+
update_option('wp-reset-snapshots', $snapshots);
|
1366 |
+
|
1367 |
+
return true;
|
1368 |
+
} // restore_snapshot
|
1369 |
+
|
1370 |
+
|
1371 |
+
/**
|
1372 |
+
* Verifies snapshot integrity by comparing metadata and data in DB
|
1373 |
+
*
|
1374 |
+
* @param string $uid Snapshot unique 6-char ID.
|
1375 |
+
*
|
1376 |
+
* @return bool|WP_Error True on success, or error object on fail.
|
1377 |
+
*/
|
1378 |
+
function verify_snapshot_integrity($uid) {
|
1379 |
+
global $wpdb;
|
1380 |
+
$tbl_core = $tbl_custom = 0;
|
1381 |
+
$snapshots = $this->get_snapshots();
|
1382 |
+
|
1383 |
+
if (strlen($uid) != 6) {
|
1384 |
+
return new WP_Error(1, 'Invalid snapshot ID format.');
|
1385 |
+
}
|
1386 |
+
|
1387 |
+
if (!isset($snapshots[$uid])) {
|
1388 |
+
return new WP_Error(1, 'Unknown snapshot ID.');
|
1389 |
+
}
|
1390 |
+
|
1391 |
+
$snapshot = $snapshots[$uid];
|
1392 |
+
|
1393 |
+
$table_status = $wpdb->get_results('SHOW TABLE STATUS');
|
1394 |
+
if (is_array($table_status)) {
|
1395 |
+
foreach ($table_status as $index => $table) {
|
1396 |
+
if (0 !== stripos($table->Name, $uid . '_')) {
|
1397 |
+
continue;
|
1398 |
+
}
|
1399 |
+
if (empty($table->Engine)) {
|
1400 |
+
continue;
|
1401 |
+
}
|
1402 |
+
|
1403 |
+
if (in_array(str_replace($uid . '_', '', $table->Name), $this->core_tables)) {
|
1404 |
+
$tbl_core++;
|
1405 |
+
} else {
|
1406 |
+
$tbl_custom++;
|
1407 |
+
}
|
1408 |
+
} // foreach
|
1409 |
+
|
1410 |
+
if ($tbl_core != $snapshot['tbl_core'] || $tbl_custom != $snapshot['tbl_custom']) {
|
1411 |
+
return new WP_Error(1, 'Snapshot data has been compromised. Saved metadata does not match data in the DB. Contact WP Reset support if data is critical, or restore it via a MySQL GUI.');
|
1412 |
+
}
|
1413 |
+
} else {
|
1414 |
+
return new WP_Error(1, 'Can\'t get table status data.');
|
1415 |
+
}
|
1416 |
+
|
1417 |
+
return true;
|
1418 |
+
} // verify_snapshot_integrity
|
1419 |
+
|
1420 |
+
|
1421 |
+
/**
|
1422 |
+
* Compares a selected snapshot with the current table set in DB
|
1423 |
+
*
|
1424 |
+
* @param string $uid Snapshot unique 6-char ID.
|
1425 |
+
*
|
1426 |
+
* @return string|WP_Error Formatted table with details on success, or error object on fail.
|
1427 |
+
*/
|
1428 |
+
function do_compare_snapshots($uid) {
|
1429 |
+
global $wpdb;
|
1430 |
+
$tbl_core = $tbl_custom = 0;
|
1431 |
+
$current = $snapshot = array();
|
1432 |
+
$out = $out2 = $out3 = '';
|
1433 |
+
|
1434 |
+
if (($res = $this->verify_snapshot_integrity($uid)) !== true) {
|
1435 |
+
return $res;
|
1436 |
+
}
|
1437 |
+
|
1438 |
+
$table_status = $wpdb->get_results('SHOW TABLE STATUS');
|
1439 |
+
foreach ($table_status as $index => $table) {
|
1440 |
+
if (empty($table->Engine)) {
|
1441 |
+
continue;
|
1442 |
+
}
|
1443 |
+
|
1444 |
+
if (0 !== stripos($table->Name, $uid . '_') && 0 !== stripos($table->Name, $wpdb->prefix)) {
|
1445 |
+
continue;
|
1446 |
+
}
|
1447 |
+
|
1448 |
+
$info = array();
|
1449 |
+
$info['rows'] = $table->Rows;
|
1450 |
+
$info['size_data'] = $table->Data_length;
|
1451 |
+
$info['size_index'] = $table->Index_length;
|
1452 |
+
$schema = $wpdb->get_row('SHOW CREATE TABLE ' . $table->Name, ARRAY_N);
|
1453 |
+
$info['schema'] = $schema[1];
|
1454 |
+
$info['engine'] = $table->Engine;
|
1455 |
+
$info['fullname'] = $table->Name;
|
1456 |
+
$basename = str_replace(array($uid . '_'), array(''), $table->Name);
|
1457 |
+
$info['basename'] = $basename;
|
1458 |
+
$info['corename'] = str_replace(array($wpdb->prefix), array(''), $basename);
|
1459 |
+
$info['uid'] = $uid;
|
1460 |
+
|
1461 |
+
if (0 === stripos($table->Name, $uid . '_')) {
|
1462 |
+
$snapshot[$basename] = $info;
|
1463 |
+
}
|
1464 |
+
|
1465 |
+
if (0 === stripos($table->Name, $wpdb->prefix)) {
|
1466 |
+
$info['uid'] = '';
|
1467 |
+
$current[$basename] = $info;
|
1468 |
+
}
|
1469 |
+
} // foreach
|
1470 |
+
|
1471 |
+
$in_both = array_keys(array_intersect_key($current, $snapshot));
|
1472 |
+
$in_current_only = array_diff_key($current, $snapshot);
|
1473 |
+
$in_snapshot_only = array_diff_key($snapshot, $current);
|
1474 |
+
|
1475 |
+
$out .= '<br><br>';
|
1476 |
+
foreach ($in_current_only as $table) {
|
1477 |
+
$out .= '<div class="wpr-table-container in-current-only" data-table="' . $table['basename'] . '">';
|
1478 |
+
$out .= '<table>';
|
1479 |
+
$out .= '<tr title="Click to show/hide more info" class="wpr-table-missing header-row">';
|
1480 |
+
$out .= '<td><b>' . $table['fullname'] . '</b></td>';
|
1481 |
+
$out .= '<td>table is not present in snapshot<span class="dashicons dashicons-arrow-down-alt2"></span></td>';
|
1482 |
+
$out .= '</tr>';
|
1483 |
+
$out .= '<tr class="hidden">';
|
1484 |
+
$out .= '<td>';
|
1485 |
+
$out .= '<p>' . number_format($table['rows']) . ' row' . ($table['rows'] == 1? '': 's') . ' totaling ' . $this->format_size($table['size_data']) . ' in data and ' . $this->format_size($table['size_index']) . ' in index.</p>';
|
1486 |
+
$out .= '<pre>' . $table['schema'] . '</pre>';
|
1487 |
+
$out .= '</td>';
|
1488 |
+
$out .= '<td> </td>';
|
1489 |
+
$out .= '</tr>';
|
1490 |
+
$out .= '</table>';
|
1491 |
+
$out .= '</div>';
|
1492 |
+
} // foreach in current only
|
1493 |
+
|
1494 |
+
foreach ($in_snapshot_only as $table) {
|
1495 |
+
$out .= '<div class="wpr-table-container in-snapshot-only" data-table="' . $table['basename'] . '">';
|
1496 |
+
$out .= '<table>';
|
1497 |
+
$out .= '<tr title="Click to show/hide more info" class="wpr-table-missing header-row">';
|
1498 |
+
$out .= '<td>table is not present in current tables</td>';
|
1499 |
+
$out .= '<td><b>' . $table['fullname'] . '</b><span class="dashicons dashicons-arrow-down-alt2"></span></td>';
|
1500 |
+
$out .= '</tr>';
|
1501 |
+
$out .= '<tr class="hidden">';
|
1502 |
+
$out .= '<td> </td>';
|
1503 |
+
$out .= '<td>';
|
1504 |
+
$out .= '<p>' . number_format($table['rows']) . ' row' . ($table['rows'] == 1? '': 's') . ' totaling ' . $this->format_size($table['size_data']) . ' in data and ' . $this->format_size($table['size_index']) . ' in index.</p>';
|
1505 |
+
$out .= '<pre>' . $table['schema'] . '</pre>';
|
1506 |
+
$out .= '</td>';
|
1507 |
+
$out .= '</tr>';
|
1508 |
+
$out .= '</table>';
|
1509 |
+
$out .= '</div>';
|
1510 |
+
} // foreach in snapshot only
|
1511 |
+
|
1512 |
+
foreach ($in_both as $tablename) {
|
1513 |
+
$tbl_current = $current[$tablename];
|
1514 |
+
$tbl_snapshot = $snapshot[$tablename];
|
1515 |
+
|
1516 |
+
$schema1 = preg_replace('/(auto_increment=)([0-9]*) /i', '${1}1 ', $tbl_current['schema'], 1);
|
1517 |
+
$schema2 = preg_replace('/(auto_increment=)([0-9]*) /i', '${1}1 ', $tbl_snapshot['schema'], 1);
|
1518 |
+
$tbl_snapshot['tmp_schema'] = str_replace($tbl_snapshot['uid'] . '_' . $tablename, $tablename, $tbl_snapshot['schema']);
|
1519 |
+
$schema2 = str_replace($tbl_snapshot['uid'] . '_' . $tablename, $tablename, $schema2);
|
1520 |
+
|
1521 |
+
if ($tbl_current['rows'] == $tbl_snapshot['rows'] && $tbl_current['schema'] == $tbl_snapshot['tmp_schema']) {
|
1522 |
+
$out3 .= '<div class="wpr-table-container identical" data-table="' . $tablename . '">';
|
1523 |
+
$out3 .= '<table>';
|
1524 |
+
$out3 .= '<tr title="Click to show/hide more info" class="wpr-table-match header-row">';
|
1525 |
+
$out3 .= '<td><b>' . $tbl_current['fullname'] . '</b></td>';
|
1526 |
+
$out3 .= '<td><b>' . $tbl_snapshot['fullname'] . '</b><span class="dashicons dashicons-arrow-down-alt2"></span></td>';
|
1527 |
+
$out3 .= '</tr>';
|
1528 |
+
$out3 .= '<tr class="hidden">';
|
1529 |
+
$out3 .= '<td>';
|
1530 |
+
$out3 .= '<p>' . number_format($tbl_current['rows']) . ' rows totaling ' . $this->format_size($tbl_current['size_data']) . ' in data and ' . $this->format_size($tbl_current['size_index']) . ' in index.</p>';
|
1531 |
+
$out3 .= '<pre>' . $tbl_current['schema'] . '</pre>';
|
1532 |
+
$out3 .= '</td>';
|
1533 |
+
$out3 .= '<td>';
|
1534 |
+
$out3 .= '<p>' . number_format($tbl_snapshot['rows']) . ' rows totaling ' . $this->format_size($tbl_snapshot['size_data']) . ' in data and ' . $this->format_size($tbl_snapshot['size_index']) . ' in index.</p>';
|
1535 |
+
$out3 .= '<pre>' . $tbl_snapshot['schema'] . '</pre>';
|
1536 |
+
$out3 .= '</td>';
|
1537 |
+
$out3 .= '</tr>';
|
1538 |
+
$out3 .= '</table>';
|
1539 |
+
$out3 .= '</div>';
|
1540 |
+
} elseif ($schema1 != $schema2) {
|
1541 |
+
require_once $this->plugin_dir . 'libs/diff.php';
|
1542 |
+
require_once $this->plugin_dir . 'libs/diff/Renderer/Html/SideBySide.php';
|
1543 |
+
$diff = new Diff(explode("\n", $tbl_current['schema']), explode("\n", $tbl_snapshot['schema']), array('ignoreWhitespace' => false));
|
1544 |
+
$renderer = new Diff_Renderer_Html_SideBySide;
|
1545 |
+
|
1546 |
+
$out2 .= '<div class="wpr-table-container" data-table="' . $tbl_current['basename'] . '">';
|
1547 |
+
$out2 .= '<table>';
|
1548 |
+
$out2 .= '<tr title="Click to show/hide more info" class="wpr-table-difference header-row">';
|
1549 |
+
$out2 .= '<td><b>' . $tbl_current['fullname'] . '</b> table schemas do not match</td>';
|
1550 |
+
$out2 .= '<td><b>' . $tbl_snapshot['fullname'] . '</b> table schemas do not match<span class="dashicons dashicons-arrow-down-alt2"></span></td>';
|
1551 |
+
$out2 .= '</tr>';
|
1552 |
+
$out2 .= '<tr class="hidden">';
|
1553 |
+
$out2 .= '<td>';
|
1554 |
+
$out2 .= '<p>' . number_format($tbl_current['rows']) . ' rows totaling ' . $this->format_size($tbl_current['size_data']) . ' in data and ' . $this->format_size($tbl_current['size_index']) . ' in index.</p>';
|
1555 |
+
$out2 .= '</td>';
|
1556 |
+
$out2 .= '<td>';
|
1557 |
+
$out2 .= '<p>' . number_format($tbl_snapshot['rows']) . ' rows totaling ' . $this->format_size($tbl_snapshot['size_data']) . ' in data and ' . $this->format_size($tbl_snapshot['size_index']) . ' in index.</p>';
|
1558 |
+
$out2 .= '</td>';
|
1559 |
+
$out2 .= '</tr>';
|
1560 |
+
$out2 .= '<tr class="hidden">';
|
1561 |
+
$out2 .= '<td colspan="2" class="no-padding">';
|
1562 |
+
$out2 .= $diff->Render($renderer);
|
1563 |
+
$out2 .= '</td>';
|
1564 |
+
$out2 .= '</tr>';
|
1565 |
+
$out2 .= '</table>';
|
1566 |
+
$out2 .= '</div>';
|
1567 |
+
} else {
|
1568 |
+
$out2 .= '<div class="wpr-table-container" data-table="' . $tbl_current['basename'] . '">';
|
1569 |
+
$out2 .= '<table>';
|
1570 |
+
$out2 .= '<tr title="Click to show/hide more info" class="wpr-table-difference header-row">';
|
1571 |
+
$out2 .= '<td><b>' . $tbl_current['fullname'] . '</b> data in tables does not match</td>';
|
1572 |
+
$out2 .= '<td><b>' . $tbl_snapshot['fullname'] . '</b> data in tables does not match<span class="dashicons dashicons-arrow-down-alt2"></span></td>';
|
1573 |
+
$out2 .= '</tr>';
|
1574 |
+
$out2 .= '<tr class="hidden">';
|
1575 |
+
$out2 .= '<td>';
|
1576 |
+
$out2 .= '<p>' . number_format($tbl_current['rows']) . ' rows totaling ' . $this->format_size($tbl_current['size_data']) . ' in data and ' . $this->format_size($tbl_current['size_index']) . ' in index.</p>';
|
1577 |
+
$out2 .= '</td>';
|
1578 |
+
$out2 .= '<td>';
|
1579 |
+
$out2 .= '<p>' . number_format($tbl_snapshot['rows']) . ' rows totaling ' . $this->format_size($tbl_snapshot['size_data']) . ' in data and ' . $this->format_size($tbl_snapshot['size_index']) . ' in index.</p>';
|
1580 |
+
$out2 .= '</td>';
|
1581 |
+
$out2 .= '</tr>';
|
1582 |
+
|
1583 |
+
$out2 .= '<tr class="hidden">';
|
1584 |
+
$out2 .= '<td colspan="2">';
|
1585 |
+
if ($tbl_current['corename'] == 'options') {
|
1586 |
+
$ss_prefix = $tbl_snapshot['uid'] . '_' . $wpdb->prefix;
|
1587 |
+
$diff_rows = $wpdb->get_results("SELECT {$wpdb->prefix}options.option_name, {$wpdb->prefix}options.option_value AS current_value, {$ss_prefix}options.option_value AS snapshot_value FROM {$wpdb->prefix}options LEFT JOIN {$ss_prefix}options ON {$ss_prefix}options.option_name = {$wpdb->prefix}options.option_name WHERE {$wpdb->prefix}options.option_value != {$ss_prefix}options.option_value LIMIT 100;");
|
1588 |
+
$only_current = $wpdb->get_results("SELECT {$wpdb->prefix}options.option_name, {$wpdb->prefix}options.option_value AS current_value, {$ss_prefix}options.option_value AS snapshot_value FROM {$wpdb->prefix}options LEFT JOIN {$ss_prefix}options ON {$ss_prefix}options.option_name = {$wpdb->prefix}options.option_name WHERE {$ss_prefix}options.option_value IS NULL LIMIT 100;");
|
1589 |
+
$only_snapshot = $wpdb->get_results("SELECT {$wpdb->prefix}options.option_name, {$wpdb->prefix}options.option_value AS current_value, {$ss_prefix}options.option_value AS snapshot_value FROM {$wpdb->prefix}options LEFT JOIN {$ss_prefix}options ON {$ss_prefix}options.option_name = {$wpdb->prefix}options.option_name WHERE {$wpdb->prefix}options.option_value IS NULL LIMIT 100;");
|
1590 |
+
$out2 .= '<table class="table_diff">';
|
1591 |
+
$out2 .= '<tr><td style="width: 100px;"><b>Option Name</b></td><td><b>Current Value</b></td><td><b>Snapshot Value</b></td></tr>';
|
1592 |
+
foreach ($diff_rows as $row) {
|
1593 |
+
$out2 .= '<tr>';
|
1594 |
+
$out2 .= '<td style="width: 100px;">' . $row->option_name . '</td>';
|
1595 |
+
$out2 .= '<td>' . (empty($row->current_value)? '<i>empty</i>': $row->current_value) . '</td>';
|
1596 |
+
$out2 .= '<td>' . (empty($row->snapshot_value)? '<i>empty</i>': $row->snapshot_value) . '</td>';
|
1597 |
+
$out2 .= '</tr>';
|
1598 |
+
} // foreach
|
1599 |
+
foreach ($only_current as $row) {
|
1600 |
+
$out2 .= '<tr>';
|
1601 |
+
$out2 .= '<td style="width: 100px;">' . $row->option_name . '</td>';
|
1602 |
+
$out2 .= '<td>' . (empty($row->current_value)? '<i>empty</i>': $row->current_value) . '</td>';
|
1603 |
+
$out2 .= '<td><i>not found in snapshot</i></td>';
|
1604 |
+
$out2 .= '</tr>';
|
1605 |
+
} // foreach
|
1606 |
+
foreach ($only_current as $row) {
|
1607 |
+
$out2 .= '<tr>';
|
1608 |
+
$out2 .= '<td style="width: 100px;">' . $row->option_name . '</td>';
|
1609 |
+
$out2 .= '<td><i>not found in current tables</i></td>';
|
1610 |
+
$out2 .= '<td>' . (empty($row->snapshot_value)? '<i>empty</i>': $row->snapshot_value) . '</td>';
|
1611 |
+
$out2 .= '</tr>';
|
1612 |
+
} // foreach
|
1613 |
+
$out2 .= '</table>';
|
1614 |
+
} else {
|
1615 |
+
$out2 .= '<p class="textcenter">Detailed data diff is not available for this table.</p>';
|
1616 |
+
}
|
1617 |
+
$out2 .= '</td>';
|
1618 |
+
$out2 .= '</tr>';
|
1619 |
+
|
1620 |
+
$out2 .= '</table>';
|
1621 |
+
$out2 .= '</div>';
|
1622 |
+
}
|
1623 |
+
} // foreach in both
|
1624 |
+
|
1625 |
+
return $out . $out2 . $out3;
|
1626 |
+
} // do_compare_snapshots
|
1627 |
+
|
1628 |
+
|
1629 |
+
/**
|
1630 |
+
* Generates a unique 6-char snapshot ID; verified non-existing
|
1631 |
+
*
|
1632 |
+
* @return string
|
1633 |
+
*/
|
1634 |
+
function generate_snapshot_uid() {
|
1635 |
+
global $wpdb;
|
1636 |
+
$snapshots = $this->get_snapshots();
|
1637 |
+
$cnt = 0;
|
1638 |
+
$uid = false;
|
1639 |
+
|
1640 |
+
do {
|
1641 |
+
$cnt++;
|
1642 |
+
$uid = sprintf('%06x', mt_rand(0, 0xFFFFFF));
|
1643 |
+
|
1644 |
+
$verify_db = $wpdb->get_col($wpdb->prepare('SHOW TABLES LIKE %s', array('%' . $uid . '%')));
|
1645 |
+
} while (!empty($verify_db) && isset($snapshots[$uid]) && $cnt < 30);
|
1646 |
+
|
1647 |
+
if ($cnt == 30) {
|
1648 |
+
$uid = false;
|
1649 |
+
}
|
1650 |
+
|
1651 |
+
return $uid;
|
1652 |
+
} // generate_snapshot_uid
|
1653 |
+
|
1654 |
+
|
1655 |
/**
|
1656 |
* Clean up on uninstall; no action on deactive at the moment
|
1657 |
*
|
1659 |
*/
|
1660 |
static function uninstall() {
|
1661 |
delete_option('wp-reset');
|
1662 |
+
delete_option('wp-reset-snapshots');
|
1663 |
} // uninstall
|
1664 |
|
1665 |
|