WP Reset – Fastest WordPress Reset Plugin - Version 1.75

Version Description

Download this release

Release Info

Developer WebFactory
Plugin Icon 128x128 WP Reset – Fastest WordPress Reset Plugin
Version 1.75
Comparing to
See all releases

Code changes from version 1.70 to 1.75

css/font/fontello.eot ADDED
Binary file
css/font/fontello.svg ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
+ <svg xmlns="http://www.w3.org/2000/svg">
4
+ <metadata>Copyright (C) 2019 by original authors @ fontello.com</metadata>
5
+ <defs>
6
+ <font id="fontello" horiz-adv-x="1000" >
7
+ <font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
8
+ <missing-glyph horiz-adv-x="1000" />
9
+ <glyph glyph-name="doc-text-inv" unicode="&#xf15c;" d="M819 584q8-7 16-20h-264v264q13-8 21-16z m-265-91h303v-589q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h446v-304q0-22 16-37t38-16z m89-411v36q0 8-5 13t-13 5h-393q-8 0-13-5t-5-13v-36q0-8 5-13t13-5h393q8 0 13 5t5 13z m0 143v36q0 8-5 13t-13 5h-393q-8 0-13-5t-5-13v-36q0-8 5-13t13-5h393q8 0 13 5t5 13z m0 143v36q0 7-5 12t-13 5h-393q-8 0-13-5t-5-12v-36q0-8 5-13t13-5h393q8 0 13 5t5 13z" horiz-adv-x="857.1" />
10
+
11
+ <glyph glyph-name="database" unicode="&#xf1c0;" d="M429 421q132 0 247 24t181 71v-95q0-38-57-71t-157-52-214-19-215 19-156 52-58 71v95q66-47 181-71t248-24z m0-428q132 0 247 24t181 71v-95q0-39-57-72t-157-52-214-19-215 19-156 52-58 72v95q66-47 181-71t248-24z m0 214q132 0 247 24t181 71v-95q0-38-57-71t-157-52-214-20-215 20-156 52-58 71v95q66-47 181-71t248-24z m0 643q116 0 214-19t157-52 57-72v-71q0-39-57-72t-157-52-214-19-215 19-156 52-58 72v71q0 39 58 72t156 52 215 19z" horiz-adv-x="857.1" />
12
+ </font>
13
+ </defs>
14
+ </svg>
css/font/fontello.ttf ADDED
Binary file
css/font/fontello.woff ADDED
Binary file
css/font/fontello.woff2 ADDED
Binary file
css/wp-reset.css CHANGED
@@ -17,6 +17,21 @@
17
  padding: 0;
18
  }
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  #wpbody-content {
21
  background: #f9fbfd;
22
  }
@@ -47,6 +62,7 @@
47
  #wpfooter {
48
  padding: 20px;
49
  border-top: 1px solid #e5e5e5;
 
50
  }
51
 
52
  #wpfooter a {
@@ -57,6 +73,74 @@
57
  color: #ffb900;
58
  }
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  .tools_page_wp-reset #wp_reset_confirm {
61
  vertical-align: top;
62
  }
@@ -71,6 +155,12 @@
71
  text-align: center;
72
  }
73
 
 
 
 
 
 
 
74
  .tools_page_wp-reset .button.disabled {
75
  pointer-events: none;
76
  cursor: not-allowed;
@@ -82,14 +172,35 @@
82
  border-radius: 0;
83
  }
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  .tools_page_wp-reset .plain-list {
86
- margin-top: 5px;
87
  list-style-type: circle;
88
  list-style-position: inside;
89
  }
90
 
91
  .tools_page_wp-reset .plain-list li {
92
- text-indent: -18px;
93
  padding-left: 23px;
94
  line-height: 23px;
95
  margin: 0;
@@ -192,17 +303,18 @@
192
  margin: -5px 0 0 0;
193
  }
194
 
195
- .tools_page_wp-reset .toggle-card {
196
  text-decoration: none;
197
  color: #444;
 
198
  }
199
 
200
- .tools_page_wp-reset .toggle-card:hover {
201
  color: #00a0d2;
202
  }
203
 
204
  .tools_page_wp-reset .card {
205
- padding: 0 25px 15px 25px;
206
  max-width: 100%;
207
  width: 100%;
208
  margin: 0 0 35px 0;
@@ -223,12 +335,6 @@
223
  padding-bottom: 0;
224
  }
225
 
226
- .tools_page_wp-reset .card.collapsed p,
227
- .tools_page_wp-reset .card.collapsed b,
228
- .tools_page_wp-reset .card.collapsed ul {
229
- display: none;
230
- }
231
-
232
  .tools_page_wp-reset .card.collapsed h4 {
233
  border: none;
234
  }
@@ -246,10 +352,11 @@
246
 
247
  .tools_page_wp-reset table td {
248
  text-align: left;
249
- padding: 5px;
 
250
  }
251
 
252
- .tools_page_wp-reset table tr:nth-child(even) td {
253
  background-color: #f9f9f9;
254
  }
255
 
@@ -740,6 +847,7 @@
740
  }
741
  /* survey */
742
 
 
743
  .webhooks-dialog .ui-dialog-titlebar {
744
  background-color: rgb(241, 88, 42);
745
  color: #fefefe;
@@ -756,7 +864,66 @@
756
  .webhooks-dialog .plain-list li {
757
  text-indent: -21px;
758
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
759
 
760
- #wpfooter {
761
- background-color: #ffffff;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
762
  }
 
 
 
 
 
 
 
17
  padding: 0;
18
  }
19
 
20
+ .tools_page_wp-reset #loading-tabs {
21
+ padding-top: 80px;
22
+ text-align: center;
23
+ }
24
+
25
+ .tools_page_wp-reset #loading-tabs img {
26
+ width: 100px;
27
+ height: auto;
28
+ opacity: 0.4;
29
+ }
30
+
31
+ .tools_page_wp-reset .mb0 {
32
+ margin-bottom: 0;
33
+ }
34
+
35
  #wpbody-content {
36
  background: #f9fbfd;
37
  }
62
  #wpfooter {
63
  padding: 20px;
64
  border-top: 1px solid #e5e5e5;
65
+ background-color: #ffffff;
66
  }
67
 
68
  #wpfooter a {
73
  color: #ffb900;
74
  }
75
 
76
+ .tools_page_wp-reset .dropdown {
77
+ position: relative;
78
+ display: inline-block;
79
+ margin-right: 10px;
80
+ }
81
+
82
+ .tools_page_wp-reset .button.dropdown-toggle::after {
83
+ content: '\f140';
84
+ font-family: dashicons;
85
+ display: inline-block;
86
+ line-height: 1;
87
+ font-weight: 400;
88
+ font-style: normal;
89
+ text-decoration: inherit;
90
+ text-transform: none;
91
+ text-rendering: auto;
92
+ -webkit-font-smoothing: antialiased;
93
+ -moz-osx-font-smoothing: grayscale;
94
+ width: 20px;
95
+ height: 20px;
96
+ font-size: 20px;
97
+ vertical-align: text-top;
98
+ text-align: center;
99
+ }
100
+
101
+ .tools_page_wp-reset .dropdown .dropdown-menu {
102
+ position: absolute;
103
+ top: 100%;
104
+ left: 0;
105
+ z-index: 1000;
106
+ display: none;
107
+ float: left;
108
+ min-width: 10rem;
109
+ padding: 0.5rem 0;
110
+ margin: 0;
111
+ color: #212529;
112
+ text-align: left;
113
+ list-style: none;
114
+ background-color: #fff;
115
+ background-clip: padding-box;
116
+ border: 1px solid rgba(0, 0, 0, 0.15);
117
+ }
118
+
119
+ .tools_page_wp-reset .dropdown .dropdown-menu.show {
120
+ display: block;
121
+ }
122
+
123
+ .tools_page_wp-reset .dropdown-menu .dropdown-item {
124
+ display: block;
125
+ width: 100%;
126
+ padding: 0.25rem 1.5rem;
127
+ clear: both;
128
+ color: #212529;
129
+ text-align: inherit;
130
+ white-space: nowrap;
131
+ background-color: transparent;
132
+ border: 0;
133
+ text-decoration: none;
134
+ box-sizing: border-box;
135
+ }
136
+
137
+ .tools_page_wp-reset .dropdown-menu .dropdown-item:focus,
138
+ .tools_page_wp-reset .dropdown-item:hover {
139
+ color: #16181b;
140
+ text-decoration: none;
141
+ background-color: #f8f9fa;
142
+ }
143
+
144
  .tools_page_wp-reset #wp_reset_confirm {
145
  vertical-align: top;
146
  }
155
  text-align: center;
156
  }
157
 
158
+ .tools_page_wp-reset .half {
159
+ width: 50%;
160
+ display: inline-block;
161
+ vertical-align: top;
162
+ }
163
+
164
  .tools_page_wp-reset .button.disabled {
165
  pointer-events: none;
166
  cursor: not-allowed;
172
  border-radius: 0;
173
  }
174
 
175
+ .tools_page_wp-reset .button,
176
+ .tools_page_wp-reset .button-secondary {
177
+ color: #555;
178
+ border-color: #ccc;
179
+ background: #f7f7f7;
180
+ box-shadow: 0 1px 0 #ccc;
181
+ }
182
+
183
+ .tools_page_wp-reset .button-secondary:focus, .tools_page_wp-reset .button-secondary:hover, .tools_page_wp-reset .button.focus, .tools_page_wp-reset .button.hover, .tools_page_wp-reset .button:focus, .tools_page_wp-reset .button:hover {
184
+ background: #fafafa;
185
+ border-color: #999;
186
+ color: #23282d;
187
+ }
188
+
189
+ .tools_page_wp-reset .button:not(.button-primary):hover {
190
+ background: #f1f1f1;
191
+ }
192
+ .tools_page_wp-reset .button:not(.button-primary) {
193
+ background: #ffffff;
194
+ }
195
+
196
  .tools_page_wp-reset .plain-list {
197
+ margin-top: 10px;
198
  list-style-type: circle;
199
  list-style-position: inside;
200
  }
201
 
202
  .tools_page_wp-reset .plain-list li {
203
+ text-indent: -20px;
204
  padding-left: 23px;
205
  line-height: 23px;
206
  margin: 0;
303
  margin: -5px 0 0 0;
304
  }
305
 
306
+ .tools_page_wp-reset .card-header-right a:not(.button) {
307
  text-decoration: none;
308
  color: #444;
309
+ margin-left: 5px;
310
  }
311
 
312
+ .tools_page_wp-reset .card-header-right a:hover:not(.button) {
313
  color: #00a0d2;
314
  }
315
 
316
  .tools_page_wp-reset .card {
317
+ padding: 0 25px 20px 25px;
318
  max-width: 100%;
319
  width: 100%;
320
  margin: 0 0 35px 0;
335
  padding-bottom: 0;
336
  }
337
 
 
 
 
 
 
 
338
  .tools_page_wp-reset .card.collapsed h4 {
339
  border: none;
340
  }
352
 
353
  .tools_page_wp-reset table td {
354
  text-align: left;
355
+ padding: 20px 10px;
356
+ border-bottom: thin solid #999;
357
  }
358
 
359
+ .tools_page_wp-reset table tr:hover td {
360
  background-color: #f9f9f9;
361
  }
362
 
847
  }
848
  /* survey */
849
 
850
+ /* webhooks dialog */
851
  .webhooks-dialog .ui-dialog-titlebar {
852
  background-color: rgb(241, 88, 42);
853
  color: #fefefe;
864
  .webhooks-dialog .plain-list li {
865
  text-indent: -21px;
866
  }
867
+ /* webhooks dialog */
868
+
869
+ /* Fontello */
870
+ @font-face {
871
+ font-family: 'fontello';
872
+ src: url('./font/fontello.eot?91671913');
873
+ src: url('./font/fontello.eot?91671913#iefix') format('embedded-opentype'),
874
+ url('./font/fontello.woff2?91671913') format('woff2'), url('./font/fontello.woff?91671913') format('woff'),
875
+ url('./font/fontello.ttf?91671913') format('truetype'), url('./font/fontello.svg?91671913#fontello') format('svg');
876
+ font-weight: normal;
877
+ font-style: normal;
878
+ }
879
+ /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
880
+ /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
881
+ /*
882
+ @media screen and (-webkit-min-device-pixel-ratio:0) {
883
+ @font-face {
884
+ font-family: 'fontello';
885
+ src: url('./font/fontello.svg?91671913#fontello') format('svg');
886
+ }
887
+ }
888
+ */
889
 
890
+ [class^='icon-']:before,
891
+ [class*=' icon-']:before {
892
+ font-family: 'fontello';
893
+ font-style: normal;
894
+ font-weight: normal;
895
+ display: inline-block;
896
+ text-decoration: inherit;
897
+ width: 1em;
898
+ margin-right: 0.2em;
899
+ text-align: center;
900
+ /* opacity: .8; */
901
+
902
+ /* For safety - reset parent styles, that can break glyph codes*/
903
+ font-variant: normal;
904
+ text-transform: none;
905
+
906
+ /* fix buttons height, for twitter bootstrap */
907
+ line-height: 1em;
908
+
909
+ /* Animation center compensation - margins should be symmetric */
910
+ /* remove if not needed */
911
+ margin-left: 0.2em;
912
+
913
+ /* you can be more comfortable with increased icons size */
914
+ /* font-size: 120%; */
915
+
916
+ /* Font smoothing. That was taken from TWBS */
917
+ -webkit-font-smoothing: antialiased;
918
+ -moz-osx-font-smoothing: grayscale;
919
+
920
+ /* Uncomment for 3D effect */
921
+ /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
922
  }
923
+
924
+ .icon-doc-text-inv:before {
925
+ content: '\f15c';
926
+ } /* '' */
927
+ .icon-database:before {
928
+ content: '\f1c0';
929
+ } /* '' */
index.php ADDED
@@ -0,0 +1,2 @@
 
 
1
+ <?php
2
+ // Silence is golden
js/wp-reset.js CHANGED
@@ -8,6 +8,9 @@ jQuery(document).ready(function($) {
8
  // init tabs
9
  $('#wp-reset-tabs')
10
  .tabs({
 
 
 
11
  activate: function(event, ui) {
12
  localStorage.setItem('wp-reset-tabs', $('#wp-reset-tabs').tabs('option', 'active'));
13
  },
@@ -27,9 +30,48 @@ jQuery(document).ready(function($) {
27
  $.scrollTo('#' + target, 500, { offset: { top: -50, left: 0 } });
28
  }
29
 
 
30
  return false;
31
  }); // jump to tab/anchor helper
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  // delete transients
34
  $('.tools_page_wp-reset').on('click', '#delete-transients', 'click', function(e) {
35
  e.preventDefault();
@@ -102,53 +144,6 @@ jQuery(document).ready(function($) {
102
  return false;
103
  }); // delete htaccess file
104
 
105
- // create&download backup
106
- $('.tools_page_wp-reset').on('click', '.download-backup', 'click', function(e) {
107
- e.preventDefault();
108
- button = this;
109
-
110
- block_ui(wp_reset.creating_backup);
111
- $.get({
112
- url: ajaxurl,
113
- data: {
114
- action: 'wp_reset_run_tool',
115
- _ajax_nonce: wp_reset.nonce_run_tool,
116
- tool: 'download_backup'
117
- }
118
- })
119
- .always(function(data) {
120
- swal.close();
121
- })
122
- .done(function(data) {
123
- if (data.success) {
124
- $.ajax({
125
- type: 'HEAD',
126
- url: data.data,
127
- success: function() {
128
- window.location = data.data;
129
- },
130
- error: function() {
131
- swal({
132
- type: 'error',
133
- title: wp_reset.backup_not_accessible,
134
- html: wp_reset.backup_not_accessible_details.replace('%url%', data.data)
135
- });
136
- }
137
- });
138
- } else {
139
- swal({
140
- type: 'error',
141
- title: wp_reset.documented_error + ' ' + data.data
142
- });
143
- }
144
- })
145
- .fail(function(data) {
146
- swal({ type: 'error', title: wp_reset.undocumented_error });
147
- });
148
-
149
- return false;
150
- }); // create&download backup
151
-
152
  // compare snapshot
153
  $('#wpr-snapshots').on('click', '.compare-snapshot', 'click', function(e) {
154
  e.preventDefault();
@@ -260,12 +255,14 @@ jQuery(document).ready(function($) {
260
  $('.tools_page_wp-reset').on('click', '.create-new-snapshot', 'click', function(e) {
261
  e.preventDefault();
262
  button = $('#create-new-snapshot-primary');
 
263
 
264
  swal({
265
  title: $(button).data('title'),
266
  type: 'question',
267
  text: $(button).data('text'),
268
  input: 'text',
 
269
  inputPlaceholder: $(button).data('placeholder'),
270
  showCancelButton: true,
271
  focusConfirm: false,
@@ -329,6 +326,8 @@ jQuery(document).ready(function($) {
329
  function run_tool(button, tool_name, extra_data) {
330
  var confirm_title = $(button).data('confirm-title') || wp_reset.confirm_title;
331
 
 
 
332
  confirm_action(
333
  confirm_title,
334
  $(button).data('text-confirm'),
@@ -473,7 +472,7 @@ jQuery(document).ready(function($) {
473
  }); // reset submit
474
 
475
  // collapse / expand card
476
- $('.card').on('click', '.toggle-card', function(e) {
477
  e.preventDefault();
478
 
479
  card = $(this)
@@ -484,6 +483,16 @@ jQuery(document).ready(function($) {
484
  .toggleClass('dashicons-arrow-down-alt2');
485
  $(this).blur();
486
 
 
 
 
 
 
 
 
 
 
 
487
  cards = localStorage.getItem('wp-reset-cards');
488
  if (cards == null) {
489
  cards = new Object();
@@ -491,10 +500,12 @@ jQuery(document).ready(function($) {
491
  cards = JSON.parse(cards);
492
  }
493
 
 
 
494
  if (card.hasClass('collapsed')) {
495
- cards[card.attr('id')] = 'collapsed';
496
  } else {
497
- cards[card.attr('id')] = 'expanded';
498
  }
499
  localStorage.setItem('wp-reset-cards', JSON.stringify(cards));
500
 
@@ -508,7 +519,7 @@ jQuery(document).ready(function($) {
508
  }
509
  $.each(cards, function(card_name, card_value) {
510
  if (card_value == 'collapsed') {
511
- $('a.toggle-card', '#' + card_name).trigger('click');
512
  }
513
  });
514
 
@@ -680,3 +691,8 @@ function wpr_fix_dialog_close(event, ui) {
680
  jQuery('#' + event.target.id).dialog('close');
681
  });
682
  } // wpr_fix_dialog_close
 
 
 
 
 
8
  // init tabs
9
  $('#wp-reset-tabs')
10
  .tabs({
11
+ create: function() {
12
+ $('#loading-tabs').remove();
13
+ },
14
  activate: function(event, ui) {
15
  localStorage.setItem('wp-reset-tabs', $('#wp-reset-tabs').tabs('option', 'active'));
16
  },
30
  $.scrollTo('#' + target, 500, { offset: { top: -50, left: 0 } });
31
  }
32
 
33
+ $(this).blur();
34
  return false;
35
  }); // jump to tab/anchor helper
36
 
37
+ // helper for scrolling to anchor
38
+ $('.tools_page_wp-reset').on('click', '.scrollto', function(e) {
39
+ e.preventDefault();
40
+
41
+ // get the link anchor and scroll to it
42
+ target = this.href.split('#')[1];
43
+ if (target) {
44
+ $.scrollTo('#' + target, 500, { offset: { top: -50, left: 0 } });
45
+ }
46
+
47
+ $(this).blur();
48
+ return false;
49
+ }); // scroll to anchor helper
50
+
51
+ // toggle button dropdown menu
52
+ $('.tools_page_wp-reset').on('click', '.button.dropdown-toggle', function(e) {
53
+ e.preventDefault();
54
+
55
+ parent_dropdown = $(this).parent('.dropdown');
56
+ sibling_menu = $(this).siblings('.dropdown-menu');
57
+
58
+ $('.dropdown')
59
+ .not(parent_dropdown)
60
+ .removeClass('show');
61
+ $('.dropdown-menu')
62
+ .not(sibling_menu)
63
+ .removeClass('show');
64
+
65
+ $(parent_dropdown).toggleClass('show');
66
+ $(sibling_menu).toggleClass('show');
67
+
68
+ return false;
69
+ }); // toggle button dropdown menu
70
+
71
+ $(document).on('click', ':not(.dropdown-toggle), .dropdown-item', function() {
72
+ wpr_close_dropdowns();
73
+ });
74
+
75
  // delete transients
76
  $('.tools_page_wp-reset').on('click', '#delete-transients', 'click', function(e) {
77
  e.preventDefault();
144
  return false;
145
  }); // delete htaccess file
146
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  // compare snapshot
148
  $('#wpr-snapshots').on('click', '.compare-snapshot', 'click', function(e) {
149
  e.preventDefault();
255
  $('.tools_page_wp-reset').on('click', '.create-new-snapshot', 'click', function(e) {
256
  e.preventDefault();
257
  button = $('#create-new-snapshot-primary');
258
+ description = $(this).data('description') || '';
259
 
260
  swal({
261
  title: $(button).data('title'),
262
  type: 'question',
263
  text: $(button).data('text'),
264
  input: 'text',
265
+ inputValue: description,
266
  inputPlaceholder: $(button).data('placeholder'),
267
  showCancelButton: true,
268
  focusConfirm: false,
326
  function run_tool(button, tool_name, extra_data) {
327
  var confirm_title = $(button).data('confirm-title') || wp_reset.confirm_title;
328
 
329
+ wpr_close_dropdowns();
330
+
331
  confirm_action(
332
  confirm_title,
333
  $(button).data('text-confirm'),
472
  }); // reset submit
473
 
474
  // collapse / expand card
475
+ $('.tools_page_wp-reset').on('click', '.toggle-card', function(e, skip_anim) {
476
  e.preventDefault();
477
 
478
  card = $(this)
483
  .toggleClass('dashicons-arrow-down-alt2');
484
  $(this).blur();
485
 
486
+ if (typeof skip_anim != 'undefined' && skip_anim) {
487
+ $(card)
488
+ .find('.card-body')
489
+ .toggle();
490
+ } else {
491
+ $(card)
492
+ .find('.card-body')
493
+ .slideToggle(500);
494
+ }
495
+
496
  cards = localStorage.getItem('wp-reset-cards');
497
  if (cards == null) {
498
  cards = new Object();
500
  cards = JSON.parse(cards);
501
  }
502
 
503
+ card_id = card.attr('id') || $('h4', card).attr('id') || '';
504
+
505
  if (card.hasClass('collapsed')) {
506
+ cards[card_id] = 'collapsed';
507
  } else {
508
+ cards[card_id] = 'expanded';
509
  }
510
  localStorage.setItem('wp-reset-cards', JSON.stringify(cards));
511
 
519
  }
520
  $.each(cards, function(card_name, card_value) {
521
  if (card_value == 'collapsed') {
522
+ $('a.toggle-card', '#' + card_name).trigger('click', true);
523
  }
524
  });
525
 
691
  jQuery('#' + event.target.id).dialog('close');
692
  });
693
  } // wpr_fix_dialog_close
694
+
695
+ function wpr_close_dropdowns() {
696
+ jQuery('.dropdown').removeClass('show');
697
+ jQuery('.dropdown-menu').removeClass('show');
698
+ } // wpr_close_dropdowns
libs/diff.php CHANGED
@@ -8,10 +8,10 @@
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,
@@ -19,20 +19,20 @@
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
@@ -43,7 +43,7 @@
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.
@@ -98,7 +98,7 @@ class Diff
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();
@@ -171,9 +171,9 @@ class Diff
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
- }
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,
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
43
  * @link http://github.com/chrisboulton/php-diff
44
  */
45
 
46
+ class WPR_Diff
47
  {
48
  /**
49
  * @var array The "old" sequence to use as the basis for the comparison.
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(WPR_Diff_Renderer_Html_SideBySide $renderer)
102
  {
103
  $renderer->diff = $this;
104
  return $renderer->render();
171
  return $this->groupedCodes;
172
  }
173
 
174
+ require_once dirname(__FILE__).'/diff/SequenceMatcher.php';
175
+ $sequenceMatcher = new WPR_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 CHANGED
@@ -1,82 +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
  }
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 WPR_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 CHANGED
@@ -1,225 +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 &nbsp; 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 &nbsp;.
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('&nbsp; ', $div).str_repeat('&nbsp;', $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
- }
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 WPR_Diff_Renderer_Html_Array extends WPR_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 &nbsp; 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 &nbsp;.
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('&nbsp; ', $div).str_repeat('&nbsp;', $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 CHANGED
@@ -1,143 +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>&hellip;</th>';
76
- $html .= '<th>&hellip;</th>';
77
- $html .= '<td>&nbsp;</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>&nbsp;</th>';
101
- $html .= '<th>'.$toLine.'</th>';
102
- $html .= '<td class="Right"><ins>'.$line.'</ins>&nbsp;</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>&nbsp;</th>';
113
- $html .= '<td class="Left"><del>'.$line.'</del>&nbsp;</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>&nbsp;</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>&nbsp;</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
- }
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 WPR_Diff_Renderer_Html_Inline extends WPR_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>&hellip;</th>';
76
+ $html .= '<th>&hellip;</th>';
77
+ $html .= '<td>&nbsp;</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>&nbsp;</th>';
101
+ $html .= '<th>'.$toLine.'</th>';
102
+ $html .= '<td class="Right"><ins>'.$line.'</ins>&nbsp;</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>&nbsp;</th>';
113
+ $html .= '<td class="Left"><del>'.$line.'</del>&nbsp;</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>&nbsp;</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>&nbsp;</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 CHANGED
@@ -1,163 +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>&hellip;</th><td>&nbsp;</td>';
73
- $html .= '<th>&hellip;</th><td>&nbsp;</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>&nbsp;</span></td>';
87
- $html .= '<th>'.$toLine.'</th>';
88
- $html .= '<td class="Right"><span>'.$line.'</span>&nbsp;</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>&nbsp;</th>';
98
- $html .= '<td class="Left">&nbsp;</td>';
99
- $html .= '<th>'.$toLine.'</th>';
100
- $html .= '<td class="Right"><ins>'.$line.'</ins>&nbsp;</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>&nbsp;</td>';
111
- $html .= '<th>&nbsp;</th>';
112
- $html .= '<td class="Right">&nbsp;</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>&nbsp;</td>';
124
- if(!isset($change['changed']['lines'][$no])) {
125
- $toLine = '&nbsp;';
126
- $changedLine = '&nbsp;';
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 = '&nbsp;';
141
- $line = '&nbsp;';
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>&nbsp;</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
  }
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 WPR_Diff_Renderer_Html_SideBySide extends WPR_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>&hellip;</th><td>&nbsp;</td>';
73
+ $html .= '<th>&hellip;</th><td>&nbsp;</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>&nbsp;</span></td>';
87
+ $html .= '<th>'.$toLine.'</th>';
88
+ $html .= '<td class="Right"><span>'.$line.'</span>&nbsp;</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>&nbsp;</th>';
98
+ $html .= '<td class="Left">&nbsp;</td>';
99
+ $html .= '<th>'.$toLine.'</th>';
100
+ $html .= '<td class="Right"><ins>'.$line.'</ins>&nbsp;</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>&nbsp;</td>';
111
+ $html .= '<th>&nbsp;</th>';
112
+ $html .= '<td class="Right">&nbsp;</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>&nbsp;</td>';
124
+ if(!isset($change['changed']['lines'][$no])) {
125
+ $toLine = '&nbsp;';
126
+ $changedLine = '&nbsp;';
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 = '&nbsp;';
141
+ $line = '&nbsp;';
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>&nbsp;</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 CHANGED
@@ -1,128 +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
  }
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 WPR_Diff_Renderer_Text_Context extends WPR_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 CHANGED
@@ -1,87 +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
  }
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 WPR_Diff_Renderer_Text_Unified extends WPR_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 CHANGED
@@ -1,742 +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
  }
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 WPR_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 CHANGED
@@ -5,7 +5,7 @@
5
  * (c) 2create Studio, Bulgaria
6
  * http://2create.bg/
7
  */
8
- abstract class Shuttle_Dump_File {
9
  /**
10
  * File Handle
11
  */
@@ -21,16 +21,16 @@ abstract class Shuttle_Dump_File {
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
 
@@ -42,7 +42,7 @@ abstract class Shuttle_Dump_File {
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
  }
@@ -57,7 +57,7 @@ class Shuttle_Dump_File_Plaintext extends Shuttle_Dump_File {
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
  }
@@ -72,7 +72,7 @@ class Shuttle_Dump_File_Gzip extends Shuttle_Dump_File {
72
  /**
73
  * MySQL insert statement builder.
74
  */
75
- class Shuttle_Insert_Statement {
76
  private $rows = array();
77
  private $length = 0;
78
  private $table;
@@ -98,7 +98,7 @@ class Shuttle_Insert_Statement {
98
  }
99
 
100
  return 'INSERT INTO `' . $this->table . '` VALUES ' .
101
- implode(",\n", $this->rows) . '; ';
102
  }
103
 
104
  function get_length() {
@@ -109,19 +109,19 @@ class Shuttle_Insert_Statement {
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
 
@@ -144,7 +144,7 @@ abstract class Shuttle_Dumper {
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
 
@@ -152,9 +152,9 @@ abstract class Shuttle_Dumper {
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'])) {
@@ -167,7 +167,7 @@ abstract class Shuttle_Dumper {
167
  return $dumper;
168
  }
169
 
170
- function __construct(Shuttle_DBConn $db) {
171
  $this->db = $db;
172
  }
173
 
@@ -244,7 +244,7 @@ abstract class Shuttle_Dumper {
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) .
@@ -264,7 +264,7 @@ class Shuttle_Dumper_ShellCommand extends Shuttle_Dumper {
264
 
265
  $command .= ' 2> ' . escapeshellarg($error_file);
266
 
267
- if (Shuttle_Dump_File::is_gzip($export_file_location)) {
268
  $command .= ' | gzip';
269
  }
270
 
@@ -275,19 +275,19 @@ class Shuttle_Dumper_ShellCommand extends Shuttle_Dumper {
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
  global $wp_reset;
288
  $eol = $this->eol;
289
 
290
- $this->dump_file = Shuttle_Dump_File::create($export_file_location);
291
 
292
  $this->dump_file->write("-- Generation time: " . date('r') . $eol);
293
  $this->dump_file->write("-- Host: " . $this->db->host . $eol);
@@ -334,7 +334,7 @@ class Shuttle_Dumper_Native extends Shuttle_Dumper {
334
 
335
  $data = $this->db->query("SELECT * FROM `$table`");
336
 
337
- $insert = new Shuttle_Insert_Statement($table);
338
 
339
  while ($row = $this->db->fetch_row($data)) {
340
  $row_values = array();
@@ -364,7 +364,7 @@ class Shuttle_Dumper_Native extends Shuttle_Dumper {
364
  }
365
  }
366
 
367
- class Shuttle_DBConn {
368
  public $host;
369
  public $username;
370
  public $password;
@@ -384,25 +384,25 @@ class Shuttle_DBConn {
384
 
385
  static function create($options) {
386
  if (class_exists('mysqli')) {
387
- $class_name = "Shuttle_DBConn_Mysqli";
388
  } else {
389
- $class_name = "Shuttle_DBConn_Mysql";
390
  }
391
 
392
  return new $class_name($options);
393
  }
394
  }
395
 
396
- class Shuttle_DBConn_Mysql extends Shuttle_DBConn {
397
  function connect() {
398
  $this->connection = @mysql_connect($this->host, $this->username, $this->password);
399
  if (!$this->connection) {
400
- throw new Shuttle_Exception("Couldn't connect to the database: " . mysql_error());
401
  }
402
 
403
  $select_db_res = mysql_select_db($this->name, $this->connection);
404
  if (!$select_db_res) {
405
- throw new Shuttle_Exception("Couldn't select database: " . mysql_error($this->connection));
406
  }
407
 
408
  return true;
@@ -414,7 +414,7 @@ class Shuttle_DBConn_Mysql extends Shuttle_DBConn {
414
  }
415
  $res = mysql_query($q);
416
  if (!$res) {
417
- throw new Shuttle_Exception("SQL error: " . mysql_error($this->connection));
418
  }
419
  return $res;
420
  }
@@ -455,12 +455,12 @@ class Shuttle_DBConn_Mysql extends Shuttle_DBConn {
455
  }
456
 
457
 
458
- class Shuttle_DBConn_Mysqli extends Shuttle_DBConn {
459
  function connect() {
460
  $this->connection = @new MySQLi($this->host, $this->username, $this->password, $this->name);
461
 
462
  if ($this->connection->connect_error) {
463
- throw new Shuttle_Exception("Couldn't connect to the database: " . $this->connection->connect_error);
464
  }
465
 
466
  return true;
@@ -473,7 +473,7 @@ class Shuttle_DBConn_Mysqli extends Shuttle_DBConn {
473
  $res = $this->connection->query($q);
474
 
475
  if (!$res) {
476
- throw new Shuttle_Exception("SQL error: " . $this->connection->error);
477
  }
478
 
479
  return $res;
@@ -514,4 +514,4 @@ class Shuttle_DBConn_Mysqli extends Shuttle_DBConn {
514
  }
515
  }
516
 
517
- class Shuttle_Exception extends Exception {};
5
  * (c) 2create Studio, Bulgaria
6
  * http://2create.bg/
7
  */
8
+ abstract class WPR_Shuttle_Dump_File {
9
  /**
10
  * File Handle
11
  */
21
 
22
  static function create($filename) {
23
  if (self::is_gzip($filename)) {
24
+ return new WPR_Shuttle_Dump_File_Gzip($filename);
25
  }
26
+ return new WPR_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 WPR_Shuttle_Exception("Couldn't write SQL export file");
34
  }
35
  }
36
 
42
  /**
43
  * Plain text implementation. Uses standard file functions in PHP.
44
  */
45
+ class WPR_Shuttle_Dump_File_Plaintext extends WPR_Shuttle_Dump_File {
46
  function open() {
47
  return fopen($this->file_location, 'w');
48
  }
57
  /**
58
  * Gzip implementation. Uses gz* functions.
59
  */
60
+ class WPR_Shuttle_Dump_File_Gzip extends WPR_Shuttle_Dump_File {
61
  function open() {
62
  return gzopen($this->file_location, 'wb9');
63
  }
72
  /**
73
  * MySQL insert statement builder.
74
  */
75
+ class WPR_Shuttle_Insert_Statement {
76
  private $rows = array();
77
  private $length = 0;
78
  private $table;
98
  }
99
 
100
  return 'INSERT INTO `' . $this->table . '` VALUES ' .
101
+ implode(",\n", $this->rows) . ';';
102
  }
103
 
104
  function get_length() {
109
  /**
110
  * Main facade
111
  */
112
+ abstract class WPR_Shuttle_Dumper {
113
  /**
114
  * Maximum length of single insert statement
115
  */
116
  const INSERT_THRESHOLD = 838860;
117
 
118
  /**
119
+ * @var WPR_Shuttle_DBConn
120
  */
121
  public $db;
122
 
123
  /**
124
+ * @var WPR_Shuttle_Dump_File
125
  */
126
  public $dump_file;
127
 
144
  * Factory method for dumper on current hosts's configuration.
145
  */
146
  static function create($db_options) {
147
+ $db = WPR_Shuttle_DBConn::create($db_options);
148
 
149
  $db->connect();
150
 
152
  && self::is_shell_command_available('mysqldump')
153
  && self::is_shell_command_available('gzip')
154
  ) {
155
+ $dumper = new WPR_Shuttle_Dumper_ShellCommand($db);
156
  } else {
157
+ $dumper = new WPR_Shuttle_Dumper_Native($db);
158
  }
159
 
160
  if (isset($db_options['include_tables'])) {
167
  return $dumper;
168
  }
169
 
170
+ function __construct(WPR_Shuttle_DBConn $db) {
171
  $this->db = $db;
172
  }
173
 
244
  }
245
  }
246
 
247
+ class WPR_Shuttle_Dumper_ShellCommand extends WPR_Shuttle_Dumper {
248
  function dump($export_file_location, $table_prefix='') {
249
  $command = 'mysqldump -h ' . escapeshellarg($this->db->host) .
250
  ' -u ' . escapeshellarg($this->db->username) .
264
 
265
  $command .= ' 2> ' . escapeshellarg($error_file);
266
 
267
+ if (WPR_Shuttle_Dump_File::is_gzip($export_file_location)) {
268
  $command .= ' | gzip';
269
  }
270
 
275
  if ($return_val !== 0) {
276
  $error_text = file_get_contents($error_file);
277
  unlink($error_file);
278
+ throw new WPR_Shuttle_Exception('Couldn\'t export database: ' . $error_text);
279
  }
280
 
281
  unlink($error_file);
282
  }
283
  }
284
 
285
+ class WPR_Shuttle_Dumper_Native extends WPR_Shuttle_Dumper {
286
  public function dump($export_file_location, $table_prefix='') {
287
  global $wp_reset;
288
  $eol = $this->eol;
289
 
290
+ $this->dump_file = WPR_Shuttle_Dump_File::create($export_file_location);
291
 
292
  $this->dump_file->write("-- Generation time: " . date('r') . $eol);
293
  $this->dump_file->write("-- Host: " . $this->db->host . $eol);
334
 
335
  $data = $this->db->query("SELECT * FROM `$table`");
336
 
337
+ $insert = new WPR_Shuttle_Insert_Statement($table);
338
 
339
  while ($row = $this->db->fetch_row($data)) {
340
  $row_values = array();
364
  }
365
  }
366
 
367
+ class WPR_Shuttle_DBConn {
368
  public $host;
369
  public $username;
370
  public $password;
384
 
385
  static function create($options) {
386
  if (class_exists('mysqli')) {
387
+ $class_name = "WPR_Shuttle_DBConn_Mysqli";
388
  } else {
389
+ $class_name = "WPR_Shuttle_DBConn_Mysql";
390
  }
391
 
392
  return new $class_name($options);
393
  }
394
  }
395
 
396
+ class WPR_Shuttle_DBConn_Mysql extends WPR_Shuttle_DBConn {
397
  function connect() {
398
  $this->connection = @mysql_connect($this->host, $this->username, $this->password);
399
  if (!$this->connection) {
400
+ throw new WPR_Shuttle_Exception("Couldn't connect to the database: " . mysql_error());
401
  }
402
 
403
  $select_db_res = mysql_select_db($this->name, $this->connection);
404
  if (!$select_db_res) {
405
+ throw new WPR_Shuttle_Exception("Couldn't select database: " . mysql_error($this->connection));
406
  }
407
 
408
  return true;
414
  }
415
  $res = mysql_query($q);
416
  if (!$res) {
417
+ throw new WPR_Shuttle_Exception("SQL error: " . mysql_error($this->connection));
418
  }
419
  return $res;
420
  }
455
  }
456
 
457
 
458
+ class WPR_Shuttle_DBConn_Mysqli extends WPR_Shuttle_DBConn {
459
  function connect() {
460
  $this->connection = @new MySQLi($this->host, $this->username, $this->password, $this->name);
461
 
462
  if ($this->connection->connect_error) {
463
+ throw new WPR_Shuttle_Exception("Couldn't connect to the database: " . $this->connection->connect_error);
464
  }
465
 
466
  return true;
473
  $res = $this->connection->query($q);
474
 
475
  if (!$res) {
476
+ throw new WPR_Shuttle_Exception("SQL error: " . $this->connection->error);
477
  }
478
 
479
  return $res;
514
  }
515
  }
516
 
517
+ class WPR_Shuttle_Exception extends Exception {};
readme.txt CHANGED
@@ -3,16 +3,16 @@ Tags: wordpress reset, reset database, reset wordpress database, reset, advanced
3
  Contributors: WebFactory, wpreset, googlemapswidget, underconstructionpage
4
  Requires at least: 4.0
5
  Requires PHP: 5.2
6
- Tested up to: 5.2
7
- Stable tag: 1.70
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
11
- WP Reset resets a WordPress site, or just the chosen parts, to the default values. It's safe to use with built-in 1-click backup.
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, or just chosen parts like theme settings. WP Reset is fast and safe to use thanks to the built-in 1-click backup function. 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 & debugging** by providing a quick way to reset settings and re-test code. It's the only WP development tool for non-developers.
16
 
17
  https://youtu.be/qMnkCW2PFoI?rel=0
18
 
@@ -23,7 +23,7 @@ Access WP Reset admin page via the "Tools" menu.
23
  WP Reset is fully integrated with <a href="https://wordpress.org/plugins/wp-webhooks/">WP Webhooks</a> plugin - a secure, universal system that connects WP to any 3rd party systems and enables you to initiate actions both from WordPress (for instance start a MailChimp campaign once a new user registers), and from any other application (create a new user in WP when a purchase is made on a 3rd party system). View more <a href="https://underconstructionpage.com/wp-webhooks-connect-integrate-wordpress/" target="_blank">practical use-cases</a> that save hours of repetitive work.
24
 
25
 
26
- **Please read carefully before proceeding to understand what WP Reset does, and remember to always create a backup**
27
 
28
  #### Resetting will delete:
29
 
@@ -40,20 +40,28 @@ WP Reset is fully integrated with <a href="https://wordpress.org/plugins/wp-webh
40
 
41
  #### What happens when I click the Reset button?
42
 
43
- * remember to always make a backup first
44
  * you will have to confirm the action one more time because there is NO UNDO
45
  * everything will be reset; see bullets above for details
46
  * site title, WordPress address, site address, site language, search engine visibility settings as well as the current user will be restored
47
  * you will be logged out, automatically logged in and taken to the admin dashboard
48
  * WP Reset plugin will be reactivated if that option is chosen in the post-reset options
49
 
50
- #### 1-click Backup Tool
51
 
52
- WP Reset comes with built-in backup functionality. Before deleting any data or running any reset tools make sure you download a fresh backup. Click any of the "download backup" links in the plugin, wait a few seconds till the backup is created and then save it to your computer. WP Reset does not backup any files! So the ZIP you download will only contain the full database; no files are included or saved anywhere.
53
 
54
  #### WP-CLI support
55
 
56
- 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.
 
 
 
 
 
 
 
 
57
 
58
  #### Database Snapshots
59
 
@@ -110,6 +118,15 @@ Or if needed, upload manually;
110
 
111
  == Changelog ==
112
 
 
 
 
 
 
 
 
 
 
113
  = v1.70 =
114
  * 2019/09/27
115
  * bug fixes
3
  Contributors: WebFactory, wpreset, googlemapswidget, underconstructionpage
4
  Requires at least: 4.0
5
  Requires PHP: 5.2
6
+ Tested up to: 5.3
7
+ Stable tag: 1.75
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
11
+ WP Reset resets the whole site, or just the chosen parts, to the default values. It's safe to use with a built-in restore function.
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, or just chosen parts like theme settings. WP Reset is fast and safe to use thanks to the built-in snapshots which provide 1-click restore functionality. 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 & debugging** by providing a quick way to reset settings and re-test code. It's the only WP development tool for non-developers.
16
 
17
  https://youtu.be/qMnkCW2PFoI?rel=0
18
 
23
  WP Reset is fully integrated with <a href="https://wordpress.org/plugins/wp-webhooks/">WP Webhooks</a> plugin - a secure, universal system that connects WP to any 3rd party systems and enables you to initiate actions both from WordPress (for instance start a MailChimp campaign once a new user registers), and from any other application (create a new user in WP when a purchase is made on a 3rd party system). View more <a href="https://underconstructionpage.com/wp-webhooks-connect-integrate-wordpress/" target="_blank">practical use-cases</a> that save hours of repetitive work.
24
 
25
 
26
+ **Please read carefully before proceeding to understand what WP Reset does, and remember to always create a snapshot**
27
 
28
  #### Resetting will delete:
29
 
40
 
41
  #### What happens when I click the Reset button?
42
 
43
+ * remember to always create a snapshot first or a full backup
44
  * you will have to confirm the action one more time because there is NO UNDO
45
  * everything will be reset; see bullets above for details
46
  * site title, WordPress address, site address, site language, search engine visibility settings as well as the current user will be restored
47
  * you will be logged out, automatically logged in and taken to the admin dashboard
48
  * WP Reset plugin will be reactivated if that option is chosen in the post-reset options
49
 
50
+ #### Undoing a reset
51
 
52
+ Before doing a reset, create a snapshot. The button is located right next to the reset button and it takes less than 10 seconds to create a snapshot. After reset is done, if you need to undo it simply restore the snapshot and that's it.
53
 
54
  #### WP-CLI support
55
 
56
+ 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 always make a snapshot or backup first.
57
+
58
+ Currently supported WP-CLI commands:
59
+
60
+ * `wp reset reset`
61
+ * `wp reset version`
62
+ * `wp reset delete`
63
+ * `wp reset snapshots`
64
+
65
 
66
  #### Database Snapshots
67
 
118
 
119
  == Changelog ==
120
 
121
+ = v1.75 =
122
+ * 2019/11/12
123
+ * bug fixes
124
+ * more GUI improvements
125
+ * updates for WP v5.3
126
+ * removed the 1-click backup tool in favor of snapshots - less confusing & same end result
127
+ * two huge bug fixes thanks to @markwill
128
+ * 1,241,470 downloads
129
+
130
  = v1.70 =
131
  * 2019/09/27
132
  * bug fixes
wp-reset-cli.php CHANGED
@@ -321,75 +321,12 @@ class WP_Reset_CLI extends WP_CLI_Command
321
 
322
 
323
  /**
324
- * Manipulate backups.
325
- *
326
- * ## OPTIONS
327
- *
328
- * <create|list>
329
- * : Actions to take with backup(s).
330
- *
331
- * ## EXAMPLES
332
- *
333
- * $ wp reset backup create
334
- * Success: New backup created; /wp-content/wp-reset-backups/wp-reset-backup-site-com-2019-09-25-16-44-37-654a.sql.gz
335
- *
336
- * $ wp reset backup list
337
- * Success: 2 backups found.
338
- * ---
339
- * -
340
- * file: /wp-content/wp-reset-backups/wp-reset-backup-site-com-2019-09-24-15-35-55-6514.sql.gz
341
- * -
342
- * file: /wp-content/wp-reset-backups/wp-reset-backup-site-com-2019-09-24-15-38-09-45ab.sql.gz
343
- *
344
- * @when after_wp_load
345
  */
346
- function backup($args, $assoc_args)
347
  {
348
- global $wp_reset;
349
-
350
- if (empty($args[0])) {
351
- WP_CLI::error('Please choose a subcommand: create or list.');
352
- return;
353
- } elseif (false == in_array($args[0], array('create', 'list'))) {
354
- WP_CLI::error('Unknown subcommand. Please choose from: create or list.');
355
- } else {
356
- $subcommand = $args[0];
357
- }
358
-
359
- switch ($subcommand) {
360
- case 'create':
361
- $filename = $wp_reset->do_create_backup(true);
362
- if (is_wp_error($filename)) {
363
- WP_CLI::error('Unable to create new backup. ' . $filename->get_error_message());
364
- } else {
365
- WP_CLI::success('New backup created: /wp-content/' . $wp_reset->backups_folder . '/' . $filename);
366
- }
367
- break;
368
- case 'list':
369
- $out = array();
370
- $files = @scandir(trailingslashit(WP_CONTENT_DIR) . $wp_reset->backups_folder . '/');
371
- if (false === $files || !is_array($files)) {
372
- WP_CLI::error('Unable to open backups folder.');
373
- } else {
374
- foreach ($files as $file) {
375
- if (substr($file, -3) == '.gz') {
376
- $out[] = array('file' => '/wp-content/' . $wp_reset->backups_folder . '/' . $file);
377
- }
378
- } // foreach
379
- if ($out) {
380
- WP_CLI::success(sizeof($out) . ' backup' . (sizeof($out) == 1 ? '' : 's') . ' found.');
381
- WP_CLI\Utils\format_items('yaml', $out, array('file'));
382
- } else {
383
- WP_CLI::error('No backups found.');
384
- }
385
- }
386
- break;
387
- default:
388
- // should never come to this but can't hurt
389
- WP_CLI::error('Unknown subcommand. Please choose from: create or list.');
390
- return;
391
- }
392
- } // backup
393
  } // WP_Reset_CLI
394
 
395
  WP_CLI::add_command('reset', 'WP_Reset_CLI');
321
 
322
 
323
  /**
324
+ * This command is no longer available. Please use "wp reset snapshots create" instead.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325
  */
326
+ function backups($args, $assoc_args)
327
  {
328
+ WP_CLI::error('This command is no longer available. Please use: wp reset snapshots create');
329
+ } // backups
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
  } // WP_Reset_CLI
331
 
332
  WP_CLI::add_command('reset', 'WP_Reset_CLI');
wp-reset.php CHANGED
@@ -2,8 +2,8 @@
2
  /*
3
  Plugin Name: WP Reset
4
  Plugin URI: https://wpreset.com/
5
- Description: Reset the entire site or selected parts to default installation values without having to edit any files.
6
- Version: 1.70
7
  Author: WebFactory Ltd
8
  Author URI: https://www.webfactoryltd.com/
9
  Text Domain: wp-reset
@@ -43,7 +43,6 @@ class WP_Reset
43
  public $plugin_url = '';
44
  public $plugin_dir = '';
45
  public $snapshots_folder = 'wp-reset-snapshots-export';
46
- public $backups_folder = 'wp-reset-backups';
47
  protected $options = array();
48
  private $delete_count = 0;
49
  private $licensing_servers = array('https://license1.wpreset.com/', 'https://license2.wpreset.com/');
@@ -134,14 +133,6 @@ class WP_Reset
134
  $options['last_run'] = array();
135
  $change = true;
136
  }
137
- if (!isset($options['last_backup_filename'])) {
138
- $options['last_backup_filename'] = '';
139
- $change = true;
140
- }
141
- if (!isset($options['last_backup_timestamp'])) {
142
- $options['last_backup_timestamp'] = 0;
143
- $change = true;
144
- }
145
  if (!isset($options['options'])) {
146
  $options['options'] = array();
147
  $change = true;
@@ -341,15 +332,12 @@ class WP_Reset
341
  'cancel_button' => __('Cancel', 'wp-reset'),
342
  'open_survey' => $survey,
343
  'ok_button' => __('OK', 'wp-reset'),
344
- 'creating_backup' => __('Creating backup. Please wait.', 'wp-reset'),
345
  'confirm_button' => __('Reset WordPress', 'wp-reset'),
346
  'confirm_title' => __('Are you sure you want to proceed?', 'wp-reset'),
347
  'confirm_title_reset' => __('Are you sure you want to reset the site?', 'wp-reset'),
348
- 'confirm1' => __('Clicking "Reset WordPress" will reset your site to default values. All content will be lost. There is NO UNDO. Always <a href="#" class="download-backup">create a backup</a>.</b>', 'wp-reset'),
349
  'confirm2' => __('Click "Cancel" to abort.', 'wp-reset'),
350
  'doing_reset' => __('Resetting in progress. Please wait.', 'wp-reset'),
351
- 'backup_not_accessible' => __('Backup was created but it\'s not accessible.', 'wp-reset'),
352
- 'backup_not_accessible_details' => __('Please check your folder access rights. File should be accessible on <a href="%url%" target="_blank">this URL</a>.', 'wp-reset'),
353
  'nonce_dismiss_notice' => wp_create_nonce('wp-reset_dismiss_notice'),
354
  'nonce_run_tool' => wp_create_nonce('wp-reset_run_tool'),
355
  'nonce_do_reset' => wp_create_nonce('wp-reset_do_reset'),
@@ -960,14 +948,6 @@ class WP_Reset
960
  $url = content_url() . '/' . $this->snapshots_folder . '/' . $res;
961
  wp_send_json_success($url);
962
  }
963
- } elseif ($tool == 'download_backup') {
964
- $res = $this->do_create_backup();
965
- if (is_wp_error($res)) {
966
- wp_send_json_error($res->get_error_message());
967
- } else {
968
- $url = content_url() . '/' . $this->backups_folder . '/' . $res;
969
- wp_send_json_success($url);
970
- }
971
  } elseif ($tool == 'restore_snapshot') {
972
  $res = $this->do_restore_snapshot($extra_data);
973
  if (is_wp_error($res)) {
@@ -1266,17 +1246,87 @@ class WP_Reset
1266
 
1267
 
1268
  /**
1269
- * Generates a button that initiates backup creation and download
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1270
  *
1271
  * @return string
1272
  */
1273
- function get_backup_button($tool_id = '')
1274
  {
1275
  $out = '';
1276
- $out .= '<a data-tool-id="' . $tool_id . '" class="button download-backup" href="#">Create &amp; download backup</a>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1277
 
1278
  return $out;
1279
- } // get_backup_button
1280
 
1281
 
1282
  /**
@@ -1297,19 +1347,20 @@ class WP_Reset
1297
  echo '<header>';
1298
  echo '<div class="wpr-container">';
1299
  echo '<img id="logo-icon" src="' . $this->plugin_url . 'img/wp-reset-logo.png" title="' . __('WP Reset', 'wp-reset') . '" alt="' . __('WP Reset', 'wp-reset') . '">';
1300
- echo '<a href="' . $this->generate_web_link('header', '/pro-version-launch/') . '" target="_blank" class="button button-primary alignright">Grab the limited LTD offer!</a>';
1301
  echo '</div>';
1302
  echo '</header>';
1303
 
1304
- echo '<div id="wp-reset-tabs" class="ui-tabs">';
 
 
1305
 
1306
  echo '<nav>';
1307
  echo '<div class="wpr-container">';
1308
  echo '<ul class="wpr-main-tab">';
1309
  echo '<li><a href="#tab-reset">' . __('Reset', 'wp-reset') . '</a></li>';
1310
  echo '<li><a href="#tab-tools">' . __('Tools', 'wp-reset') . '</a></li>';
1311
- echo '<li><a href="#tab-snapshots">' . __('DB Snapshots', 'wp-reset') . '</a></li>';
1312
- echo '<li><a href="#tab-collections">' . __('Plugins &amp; Themes Collections', 'wp-reset') . '</a></li>';
1313
  echo '<li><a href="#tab-support">' . __('Support', 'wp-reset') . '</a></li>';
1314
  echo '</ul>';
1315
  echo '</div>'; // container
@@ -1361,7 +1412,7 @@ class WP_Reset
1361
  $questions[] = '<div class="question-wrapper" data-value="backup" title="Click to select/unselect answer">' .
1362
  '<span class="dashicons dashicons-yes"></span>' .
1363
  '<div class="question"><b>Off-site backups</b><br>' .
1364
- '<i>Backup the site to Dropbox, FTP or Google Drive before running any tools</i></div>' .
1365
  '</div>';
1366
 
1367
  $questions[] = '<div class="question-wrapper" data-value="wpmu" title="Click to select/unselect answer">' .
@@ -1452,7 +1503,7 @@ class WP_Reset
1452
  }
1453
 
1454
  // ask for review
1455
- if ((!empty($meta['reset_count']) || !empty($snapshots) || current_time('timestamp', true) - $meta['first_install'] > WEEK_IN_SECONDS)
1456
  && false === $notice_shown
1457
  && false == $this->get_dismissed_notices('rate')
1458
  ) {
@@ -1477,15 +1528,16 @@ class WP_Reset
1477
 
1478
  echo '<div class="card" id="card-description">';
1479
  echo '<h4>';
1480
- echo __('Please read carefully before proceeding. There is NO UNDO!', 'wp-reset');
1481
  echo '<div class="card-header-right"><a class="toggle-card" href="#" title="' . __('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a></div>';
1482
  echo '</h4>';
 
1483
  echo '<p><b class="red">' . __('Resetting will delete:', 'wp-reset') . '</b></p>';
1484
  echo '<ul class="plain-list">';
1485
  echo '<li>' . __('all posts, pages, custom post types, comments, media entries, users', 'wp-reset') . '</li>';
1486
  echo '<li>' . __('all default WP database tables', 'wp-reset') . '</li>';
1487
  echo '<li>' . sprintf(__('all custom database tables that have the same prefix "%s" as default tables in this installation', 'wp-reset'), $wpdb->prefix) . '</li>';
1488
- echo '<li>' . __('always <a href="#" class="download-backup">make a backup</a> first', 'wp-reset') . '</li>';
1489
  echo '</ul>';
1490
 
1491
  echo '<p><b class="green">' . __('Resetting will not delete:', 'wp-reset') . '</b></p>';
@@ -1500,8 +1552,8 @@ class WP_Reset
1500
 
1501
  echo '<p><b>' . __('What happens when I click the Reset button?', 'wp-reset') . '</b></p>';
1502
  echo '<ul class="plain-list">';
1503
- echo '<li>' . __('remember, always <a href="#" class="download-backup">make a backup</a> first', 'wp-reset') . '</li>';
1504
- echo '<li>' . __('you will have to confirm the action one more time because there is NO UNDO', 'wp-reset') . '</li>';
1505
  echo '<li>' . __('everything will be reset; see bullets above for details', 'wp-reset') . '</li>';
1506
  echo '<li>' . __('site title, WordPress address, site address, site language, search engine visibility and current user will be restored', 'wp-reset') . '</li>';
1507
  echo '<li>' . __('you will be logged out, automatically logged in and taken to the admin dashboard', 'wp-reset') . '</li>';
@@ -1519,12 +1571,15 @@ class WP_Reset
1519
  } else {
1520
  echo '<a href="#" class="open-webhooks-dialog">Install WP Webhooks &amp; WPR addon</a> to automate your workflow, develop faster and connect WordPress to any web app or 3rd party system.';
1521
  }
1522
- echo '</p></div>'; // card description
1523
 
1524
  $theme = wp_get_theme();
1525
 
1526
  echo '<div class="card" id="card-reset">';
1527
- echo '<h4>' . __('Reset', 'wp-reset') . '</h4>';
 
 
 
1528
  echo '<p><label for="reactivate-theme"><input name="wpr-post-reset[reactivate_theme]" type="checkbox" id="reactivate-theme" value="1"> ' . __('Reactivate current theme', 'wp-reset') . ' - ' . $theme->get('Name') . '</label></p>';
1529
  echo '<p><label for="reactivate-wpreset"><input name="wpr-post-reset[reactivate_wpreset]" type="checkbox" id="reactivate-wpreset" value="1" checked> ' . __('Reactivate WP Reset plugin', 'wp-reset') . '</label></p>';
1530
  if ($this->is_webhooks_active()) {
@@ -1536,11 +1591,12 @@ class WP_Reset
1536
  } else {
1537
  echo '<p>To run additional actions after reset or automate a complex workflow <a href="#" class="open-webhooks-dialog">install WP Webhooks &amp; WPR addon</a>. It\'s a standard, platform-independent way of connecting WordPress to any web app. This <a href="https://www.youtube.com/watch?v=m8XDFXCNP9g" target="_blank">short video</a> explains it well.</p>';
1538
  }
1539
- echo '<p><br>' . __('Type <b>reset</b> in the confirmation field to confirm the reset and then click the "Reset WordPress" button.<br><b>There is NO UNDO.</b> Always <a href="#" class="download-backup">create a backup</a> before deleting anything.', 'wp-reset') . '</p>';
1540
 
1541
  wp_nonce_field('wp-reset');
1542
  echo '<p><input id="wp_reset_confirm" type="text" name="wp_reset_confirm" placeholder="' . esc_attr__('Type in "reset"', 'wp-reset') . '" value="" autocomplete="off"> &nbsp;';
1543
- echo '<a id="wp_reset_submit" class="button button-delete">' . __('Reset WordPress', 'wp-reset') . '</a>' . $this->get_backup_button('reset-wordpress') . '</p>';
 
1544
  echo '</div>';
1545
  } // tab_reset
1546
 
@@ -1552,51 +1608,111 @@ class WP_Reset
1552
  */
1553
  private function tab_tools()
1554
  {
 
 
 
 
 
 
 
 
 
 
 
 
1555
  echo '<div class="card">';
1556
- echo '<h4 id="tool-delete-transients">' . __('Delete Transients', 'wp-reset') . '</h4>';
1557
- echo '<p>All transient related database entries will be deleted. Including expired and non-expired transients, and orphaned transient timeout entries.<br><b>There is NO UNDO.</b> Always <a href="#" class="download-backup">create a backup</a> before deleting anything.</p>';
1558
- echo '<p><a data-confirm-title="Are you sure you want to delete all transients?" data-btn-confirm="Delete all transients" data-text-wait="Deleting transients. Please wait." data-text-confirm="All database entries related to transients will be deleted. There is NO UNDO. Always ' . esc_attr('<a href="#" class="download-backup">create a backup</a>') . '." data-text-done="%n transient database entries have been deleted." data-text-done-singular="One transient database entry has been deleted." class="button button-delete" href="#" id="delete-transients">Delete all transients</a>' . $this->get_backup_button('delete-transients') . '</p>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1559
  echo '</div>';
1560
 
1561
  $upload_dir = wp_upload_dir(date('Y/m'), true);
1562
  $upload_dir['basedir'] = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $upload_dir['basedir']);
1563
 
1564
  echo '<div class="card">';
1565
- echo '<h4 id="tool-delete-uploads">' . __('Clean Uploads Folder', 'wp-reset') . '</h4>';
 
1566
  echo '<p>' . __('All files in <code>' . $upload_dir['basedir'] . '</code> folder will be deleted. Including folders and subfolders, and files in subfolders. Files associated with <a href="' . admin_url('upload.php') . '">media</a> entries will be deleted too.<br><b>There is NO UNDO. WP Reset does not make any file backups.</b>', 'wp-reset') . '</p>';
 
 
 
1567
  if (false != $upload_dir['error']) {
1568
- echo '<p><span style="color:#dd3036;"><b>Tool is not available.</b></span> Folder is not writeable by WordPress. Please check file and folder access rights.</p>';
1569
  } else {
1570
- echo '<p><a data-confirm-title="Are you sure you want to delete all files &amp; folders in uploads folder?" data-btn-confirm="Delete everything in uploads folder" data-text-wait="Deleting uploads. Please wait." data-text-confirm="All files and folders in uploads will be deleted. There is NO UNDO. WP Reset does not make any file backups." data-text-done="%n files &amp; folders have been deleted." data-text-done-singular="One file or folder has been deleted." class="button button-delete" href="#" id="delete-uploads">Delete all files &amp; folders in uploads folder</a></p>';
1571
  }
1572
  echo '</div>';
 
1573
 
1574
  echo '<div class="card">';
1575
- echo '<h4 id="tool-reset-theme-options">' . __('Reset Theme Options', 'wp-reset') . '</h4>';
1576
- echo '<p>' . __('All options (mods) for all themes will be reset; not just for the active theme. The tool works only for themes that use the <a href="https://codex.wordpress.org/Theme_Modification_API" target="_blank">WordPress theme modification API</a>. If options are saved in some other, custom way they won\'t be reset.<br><b>There is NO UNDO.</b> Always <a href="#" class="download-backup">create a backup</a> before deleting anything.', 'wp-reset') . '</p>';
1577
- echo '<p><a data-confirm-title="Are you sure you want to reset all theme options?" data-btn-confirm="Reset theme options" data-text-wait="Resetting theme options. Please wait." data-text-confirm="All options (mods) for all themes will be reset. There is NO UNDO. Always ' . esc_attr('<a href="#" class="download-backup">create a backup</a>') . '." data-text-done="Options for %n themes have been reset." data-text-done-singular="Options for one theme have been reset." class="button button-delete" href="#" id="reset-theme-options">Reset theme options</a>' . $this->get_backup_button('reset-theme-options') . '</p>';
 
 
 
1578
  echo '</div>';
1579
 
1580
  $theme = wp_get_theme();
1581
 
1582
  echo '<div class="card">';
1583
- echo '<h4 id="tool-delete-themes">' . __('Delete Themes', 'wp-reset') . '</h4>';
 
1584
  echo '<p>' . __('All themes will be deleted. Including the currently active theme - ' . $theme->get('Name') . '.<br><b>There is NO UNDO. WP Reset does not make any file backups.</b>', 'wp-reset') . '</p>';
1585
- echo '<p><a data-confirm-title="Are you sure you want to delete all themes?" data-btn-confirm="Delete all themes" data-text-wait="Deleting all themes. Please wait." data-text-confirm="All themes will be deleted. There is NO UNDO. WP Reset does not make any file backups." data-text-done="%n themes have been deleted." data-text-done-singular="One theme has been deleted." class="button button-delete" href="#" id="delete-themes">Delete all themes</a></p>';
 
 
 
 
1586
  echo '</div>';
1587
 
1588
  echo '<div class="card">';
1589
- echo '<h4 id="tool-delete-plugins">' . __('Delete Plugins', 'wp-reset') . '</h4>';
 
1590
  echo '<p>' . __('All plugins will be deleted except for WP Reset which will remain active.<br><b>There is NO UNDO. WP Reset does not make any file backups.</b>', 'wp-reset') . '</p>';
1591
- echo '<p><a data-confirm-title="Are you sure you want to delete all plugins?" data-btn-confirm="Delete plugins" data-text-wait="Deleting plugins. Please wait." data-text-confirm="All plugins except WP Reset will be deleted. There is NO UNDO. WP Reset does not make any file backups." data-text-done="%n plugins have been deleted." data-text-done-singular="One plugin has been deleted." class="button button-delete" href="#" id="delete-plugins">Delete plugins</a></p>';
 
 
 
 
1592
  echo '</div>';
1593
 
1594
- global $wpdb;
1595
  $custom_tables = $this->get_custom_tables();
1596
 
1597
  echo '<div class="card">';
1598
- echo '<h4 id="tool-empty-delete-custom-tables">' . __('Empty or Delete Custom Tables', 'wp-reset') . '</h4>';
1599
- echo '<p>' . __('This action affects only custom tables with <code>' . $wpdb->prefix . '</code> prefix. Core WP tables and other tables in the database that do not have that prefix will not be deleted/emptied. Deleting (dropping) tables completely removes them from the database. Emptying (truncating) removes all content from them, but keeps the structure intact.<br><b>There is NO UNDO.</b> Always <a href="#" class="download-backup">create a backup</a> before deleting anything.</p>', 'wp-reset');
 
1600
  if ($custom_tables) {
1601
  echo '<p>' . __('The following ' . sizeof($custom_tables) . ' custom tables are affected by this tool: ');
1602
  foreach ($custom_tables as $tbl) {
@@ -1611,19 +1727,26 @@ class WP_Reset
1611
  echo '<p>' . __('There are no custom tables. There\'s nothing for this tool to empty or delete.', 'wp-reset') . '</p>';
1612
  $custom_tables_btns = ' disabled';
1613
  }
1614
- echo '<p><a data-confirm-title="Are you sure you want to empty all custom tables?" data-btn-confirm="Empty custom tables" data-text-wait="Emptying custom tables. Please wait." data-text-confirm="All custom tables with prefix <code>' . $wpdb->prefix . '</code> will be emptied. There is NO UNDO. Always ' . esc_attr('<a href="#" class="download-backup">create a backup</a>') . '." data-text-done="%n custom tables have been emptied." data-text-done-singular="One custom table has been emptied." class="button button-delete' . $custom_tables_btns . '" href="#" id="truncate-custom-tables">Empty (truncate) custom tables</a>';
1615
- echo '<a data-confirm-title="Are you sure you want to delete all custom tables?" data-btn-confirm="Delete custom tables" data-text-wait="Deleting custom tables. Please wait." data-text-confirm="All custom tables with prefix <code>' . $wpdb->prefix . '</code> will be deleted. There is NO UNDO. Always ' . esc_attr('<a href="#" class="download-backup">create a backup</a>') . '." data-text-done="%n custom tables have been deleted." data-text-done-singular="One custom table has been deleted." class="button button-delete' . $custom_tables_btns . '" href="#" id="drop-custom-tables">Delete (drop) custom tables</a>' . $this->get_backup_button('drop-custom-tables') . '</p>';
1616
 
 
 
 
 
 
1617
  echo '</div>';
1618
 
1619
  echo '<div class="card">';
1620
- echo '<h4 id="tool-delete-htaccess">' . __('Delete .htaccess File', 'wp-reset') . '</h4>';
 
1621
  echo '<p>' . __('This action deletes the .htaccess file located in <code>' . $this->get_htaccess_path() . '</code><br><b>There is NO UNDO. WP Reset does not make any file backups.</b></p>', 'wp-reset');
1622
 
1623
  echo '<p>If you need to edit .htaccess, install our free <a href="' . admin_url('plugin-install.php?tab=plugin-information&plugin=wp-htaccess-editor&TB_iframe=true&width=600&height=550') . '" class="thickbox open-plugin-details-modal">WP Htaccess Editor</a> plugin. It automatically creates backups when you edit .htaccess as well as checks for syntax errors. To create the default .htaccess file open <a href="' . admin_url('options-permalink.php') . '">Settings - Permalinks</a> and re-save settings. WordPress will recreate the file.</p>';
1624
 
1625
- echo '<a data-confirm-title="Are you sure you want to delete the .htaccess file?" data-btn-confirm="Delete .htaccess file" data-text-wait="Deleting .htaccess file. Please wait." data-text-confirm="Htaccess file will be deleted. There is NO UNDO. WP Reset does not make any file backups." data-text-done="Htaccess file has been deleted." data-text-done-singular="Htaccess file has been deleted." class="button button-delete" href="#" id="delete-htaccess">Delete .htaccess file</a></p>';
1626
 
 
 
 
1627
  echo '</div>';
1628
  } // tab_tools
1629
 
@@ -1638,7 +1761,7 @@ class WP_Reset
1638
  echo '<div class="card">';
1639
  echo '<h4>' . __('What are Plugin &amp; Theme Collections?', 'wp-reset') . '</h4>';
1640
  echo '<p>' . __('A tool that\'s coming with WP Reset PRO that will <b>save hours &amp; hours of your precious time</b>! Have a set of plugins (and themes) that you install and activate after every reset? Or on every fresh WP installation? Well, no more clicking install &amp; active for ten minutes! Build the collection once and install it with one click as many times as needed.<br>WP Reset stores collections in the cloud so they\'re accessible on every site you build. You can use free plugins and themes from the official repo, and PRO ones by uploading a ZIP file. We\'ll safely store your license keys too, so you have everything in one place.', 'wp-reset') . '</p>';
1641
- echo '<p class="textcenter"><br><a href="' . $this->generate_web_link('collections-tab', '/pro-version-launch/') . '" target="_blank" class="button button-primary">Sign up for the private LTD launch of WP Reset PRO in early November</a></p>';
1642
  echo '</div>';
1643
  } // tab_collections
1644
 
@@ -1660,11 +1783,6 @@ class WP_Reset
1660
  echo '<p>' . __('We are very active on the <a href="https://wordpress.org/support/plugin/wp-reset" target="_blank">official WP Reset support forum</a>. If you found a bug, have a feature idea or just want to say hi - please drop by. We love to hear back from our users.', 'wp-reset') . '</p>';
1661
  echo '</div>';
1662
 
1663
- echo '<div class="card">';
1664
- echo '<h4>' . __('Private contact', 'wp-reset') . '</h4>';
1665
- 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>';
1666
- echo '</div>';
1667
-
1668
  echo '<div class="card">';
1669
  echo '<h4>' . __('Care to help out?', 'wp-reset') . '</h4>';
1670
  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. A public mention <a href="https://twitter.com/webfactoryltd" target="_blank">@webfactoryltd</a> also does wonders. Thank you!', 'wp-reset') . '</p>';
@@ -1684,11 +1802,12 @@ class WP_Reset
1684
 
1685
  echo '<div class="card" id="card-snapshots">';
1686
  echo '<h4>';
1687
- echo __('Database Snapshots', 'wp-reset');
1688
  echo '<div class="card-header-right"><a class="toggle-card" href="#" title="' . __('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a></div>';
1689
  echo '</h4>';
 
1690
  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. <a href="https://www.youtube.com/watch?v=xBfMmS12vMY" target="_blank">Watch a short video</a> overview and tutorial about Snapshots.</p>';
1691
- echo '<p>Snapshots are primarily a development tool. Although they can be used for backups (and downloaded), when using various reset tools we advise using our 1-click backup tool available in every tool\'s confirmation dialog. If you need a full backup that includes files, use something like <a href="' . admin_url('plugin-install.php?tab=plugin-information&plugin=updraftplus&TB_iframe=true&width=600&height=550') . '" class="thickbox open-plugin-details-modal">UpdraftPlus</a>.</p>';
1692
  echo '<p>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. Restoring a snapshot does not affect other snapshots, or WP Reset settings.</p>';
1693
 
1694
  $table_status = $wpdb->get_results('SHOW TABLE STATUS');
@@ -1721,26 +1840,40 @@ class WP_Reset
1721
 
1722
  echo '';
1723
  echo '</div>';
 
1724
 
1725
- echo '<div class="card no-padding-bottom">';
1726
  echo '<h4>';
1727
- echo __('Saved Snapshots', 'wp-reset');
1728
- echo '<div class="card-header-right"><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', 'wp-reset') . '</a></div>';
1729
  echo '</h4>';
1730
 
1731
  if ($snapshots = $this->get_snapshots()) {
 
1732
  echo '<table id="wpr-snapshots">';
1733
- echo '<tr><th>Name</th><th>Info &amp; Size</th><th class="ss-actions">Actions</th></tr>';
1734
  foreach ($snapshots as $ss) {
1735
  echo '<tr id="wpr-ss-' . $ss['uid'] . '">';
1736
  if (!empty($ss['name'])) {
1737
- echo '<td title="Created on ' . date(get_option('date_format'), strtotime($ss['timestamp'])) . ' @ ' . date(get_option('time_format'), strtotime($ss['timestamp'])) . '">' . $ss['name'] . '</td>';
1738
  $name = $ss['name'];
1739
  } else {
1740
- 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>';
1741
  $name = 'created on ' . date(get_option('date_format'), strtotime($ss['timestamp'])) . ' @ ' . date(get_option('time_format'), strtotime($ss['timestamp']));
1742
  }
1743
- echo '<td>' . $ss['tbl_core'] . ' standard &amp; ';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1744
  if ($ss['tbl_custom']) {
1745
  echo $ss['tbl_custom'] . ' custom table' . ($ss['tbl_custom'] == 1 ? '' : 's');
1746
  } else {
@@ -1748,10 +1881,13 @@ class WP_Reset
1748
  }
1749
  echo ' totaling ' . $this->format_size($ss['tbl_size']) . ' in ' . number_format($ss['tbl_rows']) . ' rows</td>';
1750
  echo '<td>';
1751
- 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>';
1752
- 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>';
1753
- 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>';
1754
- 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>';
 
 
 
1755
  echo '</tr>';
1756
  } // foreach
1757
  echo '</table>';
@@ -1986,7 +2122,7 @@ class WP_Reset
1986
  require_once $this->plugin_dir . 'libs/dumper.php';
1987
 
1988
  try {
1989
- $world_dumper = Shuttle_Dumper::create(array(
1990
  'host' => DB_HOST,
1991
  'username' => DB_USER,
1992
  'password' => DB_PASSWORD,
@@ -2017,68 +2153,6 @@ class WP_Reset
2017
  } // export_snapshot
2018
 
2019
 
2020
-
2021
- /**
2022
- * Exports DB tables as SQL dump; saved in gzipped file in WP_CONTENT folder.
2023
- *
2024
- * @return string|WP_Error Export base filename, or error object on fail.
2025
- */
2026
- function do_create_backup($skip_timestamp_check = false)
2027
- {
2028
- require_once $this->plugin_dir . 'libs/dumper.php';
2029
- global $wpdb;
2030
-
2031
- if (
2032
- false == $skip_timestamp_check
2033
- && !empty($this->options['last_backup_timestamp'])
2034
- && !empty($this->options['last_backup_filename'])
2035
- && time() - $this->options['last_backup_timestamp'] < 30
2036
- ) {
2037
- return $this->options['last_backup_filename'];
2038
- }
2039
-
2040
- try {
2041
- $world_dumper = Shuttle_Dumper::create(array(
2042
- 'host' => DB_HOST,
2043
- 'username' => DB_USER,
2044
- 'password' => DB_PASSWORD,
2045
- 'db_name' => DB_NAME,
2046
- ));
2047
-
2048
- $folder = wp_mkdir_p(trailingslashit(WP_CONTENT_DIR) . $this->backups_folder);
2049
- if (!$folder) {
2050
- return new WP_Error(1, 'Unable to create wp-content/' . $this->backups_folder . '/ folder.');
2051
- }
2052
-
2053
- $htaccess_content = 'AddType application/octet-stream .gz' . PHP_EOL;
2054
- $htaccess_content .= 'Options -Indexes' . PHP_EOL;
2055
- $htaccess_file = @fopen(trailingslashit(WP_CONTENT_DIR) . $this->backups_folder . '/.htaccess', 'w');
2056
- if ($htaccess_file) {
2057
- fputs($htaccess_file, $htaccess_content);
2058
- fclose($htaccess_file);
2059
- }
2060
-
2061
- $rand_id = sprintf('%04x', mt_rand(0, 0xFFFF));
2062
- $filename = '';
2063
- $filename .= 'wp-reset-backup-' . str_replace(array('http://', 'https://', '.'), array('', '', '-'), home_url());
2064
- $filename .= '-' . date('Y-m-d-H-i-s', current_time('timestamp')) . '-' . $rand_id;
2065
- $filename = sanitize_file_name($filename) . '.sql.gz';
2066
- $filename = apply_filters('wp_reset_backup_filename', $filename);
2067
-
2068
- $world_dumper->dump(trailingslashit(WP_CONTENT_DIR) . $this->backups_folder . '/' . $filename, $wpdb->prefix);
2069
- } catch (Shuttle_Exception $e) {
2070
- return new WP_Error(1, 'Couldn\'t create backup: ' . $e->getMessage());
2071
- }
2072
-
2073
- do_action('wp_reset_create_backup', $filename);
2074
-
2075
- $this->update_options('last_backup_timestamp', time());
2076
- $this->update_options('last_backup_filename', $filename);
2077
-
2078
- return $filename;
2079
- } // create_backup
2080
-
2081
-
2082
  /**
2083
  * Replace current tables with ones in snapshot.
2084
  *
@@ -2314,8 +2388,8 @@ class WP_Reset
2314
  } elseif ($schema1 != $schema2) {
2315
  require_once $this->plugin_dir . 'libs/diff.php';
2316
  require_once $this->plugin_dir . 'libs/diff/Renderer/Html/SideBySide.php';
2317
- $diff = new Diff(explode("\n", $tbl_current['schema']), explode("\n", $tbl_snapshot['schema']), array('ignoreWhitespace' => false));
2318
- $renderer = new Diff_Renderer_Html_SideBySide;
2319
 
2320
  $out2 .= '<div class="wpr-table-container" data-table="' . $tbl_current['basename'] . '">';
2321
  $out2 .= '<table>';
@@ -2476,7 +2550,7 @@ class WP_Reset
2476
  }
2477
  }
2478
 
2479
- if ($plugin_info) {
2480
  array_unshift($res->plugins, $plugin_info);
2481
  }
2482
 
@@ -2497,7 +2571,6 @@ class WP_Reset
2497
  $res = $this->add_plugin_featured('wp-force-ssl', $res);
2498
  $res = $this->add_plugin_featured('eps-301-redirects', $res);
2499
 
2500
-
2501
  return $res;
2502
  } // plugins_api_result
2503
 
2
  /*
3
  Plugin Name: WP Reset
4
  Plugin URI: https://wpreset.com/
5
+ Description: Reset the entire site or just selected parts while reserving the option to undo by using snapshots.
6
+ Version: 1.75
7
  Author: WebFactory Ltd
8
  Author URI: https://www.webfactoryltd.com/
9
  Text Domain: wp-reset
43
  public $plugin_url = '';
44
  public $plugin_dir = '';
45
  public $snapshots_folder = 'wp-reset-snapshots-export';
 
46
  protected $options = array();
47
  private $delete_count = 0;
48
  private $licensing_servers = array('https://license1.wpreset.com/', 'https://license2.wpreset.com/');
133
  $options['last_run'] = array();
134
  $change = true;
135
  }
 
 
 
 
 
 
 
 
136
  if (!isset($options['options'])) {
137
  $options['options'] = array();
138
  $change = true;
332
  'cancel_button' => __('Cancel', 'wp-reset'),
333
  'open_survey' => $survey,
334
  'ok_button' => __('OK', 'wp-reset'),
 
335
  'confirm_button' => __('Reset WordPress', 'wp-reset'),
336
  'confirm_title' => __('Are you sure you want to proceed?', 'wp-reset'),
337
  'confirm_title_reset' => __('Are you sure you want to reset the site?', 'wp-reset'),
338
+ 'confirm1' => __('Clicking "Reset WordPress" will reset your site to default values. All content will be lost. Always <a href="#" class="create-new-snapshot" data-description="Before resetting the site">create a snapshot</a> if you want to be able to undo.</b>', 'wp-reset'),
339
  'confirm2' => __('Click "Cancel" to abort.', 'wp-reset'),
340
  'doing_reset' => __('Resetting in progress. Please wait.', 'wp-reset'),
 
 
341
  'nonce_dismiss_notice' => wp_create_nonce('wp-reset_dismiss_notice'),
342
  'nonce_run_tool' => wp_create_nonce('wp-reset_run_tool'),
343
  'nonce_do_reset' => wp_create_nonce('wp-reset_do_reset'),
948
  $url = content_url() . '/' . $this->snapshots_folder . '/' . $res;
949
  wp_send_json_success($url);
950
  }
 
 
 
 
 
 
 
 
951
  } elseif ($tool == 'restore_snapshot') {
952
  $res = $this->do_restore_snapshot($extra_data);
953
  if (is_wp_error($res)) {
1246
 
1247
 
1248
  /**
1249
+ * Generate a button that initiates snapshot creation
1250
+ *
1251
+ * @param string $tool_id Tool ID.
1252
+ * @param string $description Snapshot description.
1253
+ *
1254
+ * @return string
1255
+ */
1256
+ function get_snapshot_button($tool_id = '', $description = '')
1257
+ {
1258
+ $out = '';
1259
+ $out .= '<a data-tool-id="' . $tool_id . '" data-description="' . esc_attr($description) . '" class="button create-new-snapshot" href="#">Create snapshot</a>';
1260
+
1261
+ return $out;
1262
+ } // get_snapshot_button
1263
+
1264
+
1265
+ /**
1266
+ * Generate card header including title and action buttons
1267
+ *
1268
+ * @param string $title Card title.
1269
+ * @param string $card_id Card ID.
1270
+ * @param bool $collapse_button Show collapse button.
1271
+ * @param bool $iot_button Show index of tools button.
1272
+ *
1273
+ * @return string
1274
+ */
1275
+ function get_card_header($title, $card_id, $collapse_button = false, $iot_button = false)
1276
+ {
1277
+ $out = '';
1278
+ $out .= '<h4 id="' . $card_id . '">' . $title;
1279
+ $out .= '<div class="card-header-right">';
1280
+ if ($iot_button) {
1281
+ $out .= '<a class="scrollto" href="#iot" title="Jump to Index of Tools"><span class="dashicons dashicons-screenoptions"></span></a>';
1282
+ }
1283
+ if ($collapse_button) {
1284
+ $out .= '<a class="toggle-card" href="#" title="' . __('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a>';
1285
+ }
1286
+ $out .= '</div></h4>';
1287
+
1288
+ return $out;
1289
+ } // get_card_header
1290
+
1291
+
1292
+ /**
1293
+ * Generate tool icons and description detailing what it modifies
1294
+ *
1295
+ * @param bool $modify_files Does the tool modify files?
1296
+ * @param bool $modify_db Does the tool modify the database?
1297
+ * @param bool $plural Is there more than one tool in the set?
1298
  *
1299
  * @return string
1300
  */
1301
+ function get_tool_icons($modify_files = false, $modify_db = false, $plural = false)
1302
  {
1303
  $out = '';
1304
+
1305
+ $out .= '<p class="tool-icons">';
1306
+ $out .= '<i class="icon-doc-text-inv' . ($modify_files ? ' red' : '') . '"></i> ';
1307
+ $out .= '<i class="icon-database' . ($modify_db ? ' red' : '') . '"></i> ';
1308
+
1309
+ if ($plural) {
1310
+ if ($modify_files && $modify_db) {
1311
+ $out .= 'these tools <b>modify files &amp; the database</b>';
1312
+ } elseif (!$modify_files && $modify_db) {
1313
+ $out .= 'these tools <b>modify the database</b> but they don\'t modify any files</b>';
1314
+ } elseif ($modify_files && !$modify_db) {
1315
+ $out .= 'these tools <b>modify files</b> but they don\'t modify the database</b>';
1316
+ }
1317
+ } else {
1318
+ if ($modify_files && $modify_db) {
1319
+ $out .= 'this tool <b>modifies files &amp; the database</b>';
1320
+ } elseif (!$modify_files && $modify_db) {
1321
+ $out .= 'this tool <b>modifies the database</b> but it doesn\'t modify any files</b>';
1322
+ } elseif ($modify_files && !$modify_db) {
1323
+ $out .= 'this tool <b>modifies files</b> but it doesn\'t modify the database</b>';
1324
+ }
1325
+ }
1326
+ $out .= '</p>';
1327
 
1328
  return $out;
1329
+ } // get_tool_icons
1330
 
1331
 
1332
  /**
1347
  echo '<header>';
1348
  echo '<div class="wpr-container">';
1349
  echo '<img id="logo-icon" src="' . $this->plugin_url . 'img/wp-reset-logo.png" title="' . __('WP Reset', 'wp-reset') . '" alt="' . __('WP Reset', 'wp-reset') . '">';
 
1350
  echo '</div>';
1351
  echo '</header>';
1352
 
1353
+ echo '<div id="loading-tabs"><img class="rotating" src="' . $this->plugin_url . 'img/wp-reset-icon.png' . '" alt="Loading. Please wait." title="Loading. Please wait."></div>';
1354
+
1355
+ echo '<div id="wp-reset-tabs" class="ui-tabs" style="display: none;">';
1356
 
1357
  echo '<nav>';
1358
  echo '<div class="wpr-container">';
1359
  echo '<ul class="wpr-main-tab">';
1360
  echo '<li><a href="#tab-reset">' . __('Reset', 'wp-reset') . '</a></li>';
1361
  echo '<li><a href="#tab-tools">' . __('Tools', 'wp-reset') . '</a></li>';
1362
+ echo '<li><a href="#tab-snapshots">' . __('Snapshots', 'wp-reset') . '</a></li>';
1363
+ echo '<li><a href="#tab-collections">' . __('Collections', 'wp-reset') . '</a></li>';
1364
  echo '<li><a href="#tab-support">' . __('Support', 'wp-reset') . '</a></li>';
1365
  echo '</ul>';
1366
  echo '</div>'; // container
1412
  $questions[] = '<div class="question-wrapper" data-value="backup" title="Click to select/unselect answer">' .
1413
  '<span class="dashicons dashicons-yes"></span>' .
1414
  '<div class="question"><b>Off-site backups</b><br>' .
1415
+ '<i>Backup the site to Dropbox, FTP or Google Drive before using any tools</i></div>' .
1416
  '</div>';
1417
 
1418
  $questions[] = '<div class="question-wrapper" data-value="wpmu" title="Click to select/unselect answer">' .
1503
  }
1504
 
1505
  // ask for review
1506
+ if ((!empty($meta['reset_count']) || !empty($snapshots) || current_time('timestamp', true) - $meta['first_install'] > DAY_IN_SECONDS)
1507
  && false === $notice_shown
1508
  && false == $this->get_dismissed_notices('rate')
1509
  ) {
1528
 
1529
  echo '<div class="card" id="card-description">';
1530
  echo '<h4>';
1531
+ echo __('Please read carefully before proceeding', 'wp-reset');
1532
  echo '<div class="card-header-right"><a class="toggle-card" href="#" title="' . __('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a></div>';
1533
  echo '</h4>';
1534
+ echo '<div class="card-body">';
1535
  echo '<p><b class="red">' . __('Resetting will delete:', 'wp-reset') . '</b></p>';
1536
  echo '<ul class="plain-list">';
1537
  echo '<li>' . __('all posts, pages, custom post types, comments, media entries, users', 'wp-reset') . '</li>';
1538
  echo '<li>' . __('all default WP database tables', 'wp-reset') . '</li>';
1539
  echo '<li>' . sprintf(__('all custom database tables that have the same prefix "%s" as default tables in this installation', 'wp-reset'), $wpdb->prefix) . '</li>';
1540
+ echo '<li>' . __('always <a href="#" class="create-new-snapshot">create a snapshot</a> or a full backup, so you can restore it later', 'wp-reset') . '</li>';
1541
  echo '</ul>';
1542
 
1543
  echo '<p><b class="green">' . __('Resetting will not delete:', 'wp-reset') . '</b></p>';
1552
 
1553
  echo '<p><b>' . __('What happens when I click the Reset button?', 'wp-reset') . '</b></p>';
1554
  echo '<ul class="plain-list">';
1555
+ echo '<li>' . __('remember, always <a href="#" class="create-new-snapshot">make a snapshot</a> or a full backup first', 'wp-reset') . '</li>';
1556
+ echo '<li>' . __('you will have to confirm the action one more time', 'wp-reset') . '</li>';
1557
  echo '<li>' . __('everything will be reset; see bullets above for details', 'wp-reset') . '</li>';
1558
  echo '<li>' . __('site title, WordPress address, site address, site language, search engine visibility and current user will be restored', 'wp-reset') . '</li>';
1559
  echo '<li>' . __('you will be logged out, automatically logged in and taken to the admin dashboard', 'wp-reset') . '</li>';
1571
  } else {
1572
  echo '<a href="#" class="open-webhooks-dialog">Install WP Webhooks &amp; WPR addon</a> to automate your workflow, develop faster and connect WordPress to any web app or 3rd party system.';
1573
  }
1574
+ echo '</p></div></div>'; // card description
1575
 
1576
  $theme = wp_get_theme();
1577
 
1578
  echo '<div class="card" id="card-reset">';
1579
+ echo '<h4>' . __('Site Reset', 'wp-reset');
1580
+ echo '<div class="card-header-right"><a class="toggle-card" href="#" title="' . __('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a></div>';
1581
+ echo '</h4>';
1582
+ echo '<div class="card-body">';
1583
  echo '<p><label for="reactivate-theme"><input name="wpr-post-reset[reactivate_theme]" type="checkbox" id="reactivate-theme" value="1"> ' . __('Reactivate current theme', 'wp-reset') . ' - ' . $theme->get('Name') . '</label></p>';
1584
  echo '<p><label for="reactivate-wpreset"><input name="wpr-post-reset[reactivate_wpreset]" type="checkbox" id="reactivate-wpreset" value="1" checked> ' . __('Reactivate WP Reset plugin', 'wp-reset') . '</label></p>';
1585
  if ($this->is_webhooks_active()) {
1591
  } else {
1592
  echo '<p>To run additional actions after reset or automate a complex workflow <a href="#" class="open-webhooks-dialog">install WP Webhooks &amp; WPR addon</a>. It\'s a standard, platform-independent way of connecting WordPress to any web app. This <a href="https://www.youtube.com/watch?v=m8XDFXCNP9g" target="_blank">short video</a> explains it well.</p>';
1593
  }
1594
+ echo '<p><br>' . __('Type <b>reset</b> in the confirmation field to confirm the reset and then click the "Reset WordPress" button.<br>Always <a href="#" class="create-new-snapshot" data-description="Before resetting the site">create a snapshot</a> before resetting if you want to be able to undo.', 'wp-reset') . '</p>';
1595
 
1596
  wp_nonce_field('wp-reset');
1597
  echo '<p><input id="wp_reset_confirm" type="text" name="wp_reset_confirm" placeholder="' . esc_attr__('Type in "reset"', 'wp-reset') . '" value="" autocomplete="off"> &nbsp;';
1598
+ echo '<a id="wp_reset_submit" class="button button-delete">' . __('Reset Site', 'wp-reset') . '</a>' . $this->get_snapshot_button('reset-wordpress', 'Before resetting the site') . '</p>';
1599
+ echo '</div>';
1600
  echo '</div>';
1601
  } // tab_reset
1602
 
1608
  */
1609
  private function tab_tools()
1610
  {
1611
+ global $wpdb;
1612
+
1613
+ $tools = array(
1614
+ 'tool-delete-transients' => 'Delete Transients',
1615
+ 'tool-delete-uploads' => 'Clean Uploads Folder',
1616
+ 'tool-reset-theme-options' => 'Reset Theme Options',
1617
+ 'tool-delete-themes' => 'Delete Themes',
1618
+ 'tool-delete-plugins' => 'Delete Plugins',
1619
+ 'tool-empty-delete-custom-tables' => 'Empty or Delete Custom Tables',
1620
+ 'tool-delete-htaccess' => 'Delete .htaccess File'
1621
+ );
1622
+
1623
  echo '<div class="card">';
1624
+ echo $this->get_card_header(__('Index of Tools', 'wp-reset'), 'iot', true, false);
1625
+ echo '<div class="card-body">';
1626
+ $i = 0;
1627
+ $tools_nb = sizeof($tools);
1628
+ foreach ($tools as $tool_id => $tool_name) {
1629
+ if ($i == 0) {
1630
+ echo '<div class="half">';
1631
+ echo '<ul class="mb0 plain-list">';
1632
+ }
1633
+ if ($i == ceil($tools_nb / 2)) {
1634
+ echo '</div>';
1635
+ echo '<div class="half">';
1636
+ echo '<ul class="mb0 plain-list">';
1637
+ }
1638
+
1639
+ echo '<li><a title="Jump to ' . $tool_name . ' tool" class="scrollto" href="#' . $tool_id . '">' . $tool_name . '</a></li>';
1640
+
1641
+ if ($i == $tools_nb - 1) {
1642
+ echo '</ul>';
1643
+ echo '</div>'; // half
1644
+ }
1645
+ $i++;
1646
+ } // foreach tools
1647
+ echo '</div>';
1648
+ echo '</div>';
1649
+
1650
+ echo '<div class="card">';
1651
+ echo $this->get_card_header(__('Delete Transients', 'wp-reset'), 'tool-delete-transients', true, true);
1652
+ echo '<div class="card-body">';
1653
+ echo '<p>All transient related database entries will be deleted. Including expired and non-expired transients, and orphaned transient timeout entries.<br>Always <a href="#" data-description="Before deleting transients" class="create-new-snapshot">create a snapshot</a> before using this tool if you want to be able to undo its actions.</p>';
1654
+ echo $this->get_tool_icons(false, true);
1655
+ echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete all transients?" data-btn-confirm="Delete all transients" data-text-wait="Deleting transients. Please wait." data-text-confirm="All database entries related to transients will be deleted. Always ' . esc_attr('<a data-description="Before deleting transients" href="#" class="create-new-snapshot">create a snapshot</a> if you want to be able to undo') . '." data-text-done="%n transient database entries have been deleted." data-text-done-singular="One transient database entry has been deleted." class="button button-delete" href="#" id="delete-transients">Delete all transients</a>' . $this->get_snapshot_button('delete-transients', 'Before deleting transients') . '</p>';
1656
+ echo '</div>';
1657
  echo '</div>';
1658
 
1659
  $upload_dir = wp_upload_dir(date('Y/m'), true);
1660
  $upload_dir['basedir'] = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $upload_dir['basedir']);
1661
 
1662
  echo '<div class="card">';
1663
+ echo $this->get_card_header(__('Clean Uploads Folder', 'wp-reset'), 'tool-delete-uploads', true, true);
1664
+ echo '<div class="card-body">';
1665
  echo '<p>' . __('All files in <code>' . $upload_dir['basedir'] . '</code> folder will be deleted. Including folders and subfolders, and files in subfolders. Files associated with <a href="' . admin_url('upload.php') . '">media</a> entries will be deleted too.<br><b>There is NO UNDO. WP Reset does not make any file backups.</b>', 'wp-reset') . '</p>';
1666
+
1667
+ echo $this->get_tool_icons(true, false);
1668
+
1669
  if (false != $upload_dir['error']) {
1670
+ echo '<p class="mb0"><span style="color:#dd3036;"><b>Tool is not available.</b></span> Folder is not writeable by WordPress. Please check file and folder access rights.</p>';
1671
  } else {
1672
+ echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete all files &amp; folders in uploads folder?" data-btn-confirm="Delete everything in uploads folder" data-text-wait="Deleting uploads. Please wait." data-text-confirm="All files and folders in uploads will be deleted. There is NO UNDO. WP Reset does not make any file backups." data-text-done="%n files &amp; folders have been deleted." data-text-done-singular="One file or folder has been deleted." class="button button-delete" href="#" id="delete-uploads">Delete all files &amp; folders in uploads folder</a></p>';
1673
  }
1674
  echo '</div>';
1675
+ echo '</div>';
1676
 
1677
  echo '<div class="card">';
1678
+ echo $this->get_card_header(__('Reset Theme Options', 'wp-reset'), 'tool-reset-theme-options', true, true);
1679
+ echo '<div class="card-body">';
1680
+ echo '<p>' . __('All options (mods) for all themes will be reset; not just for the active theme. The tool works only for themes that use the <a href="https://codex.wordpress.org/Theme_Modification_API" target="_blank">WordPress theme modification API</a>. If options are saved in some other, custom way they won\'t be reset.<br> Always <a href="#" class="create-new-snapshot" data-description="Before resetting theme options">create a snapshot</a> before using this tool if you want to be able to undo its actions.', 'wp-reset') . '</p>';
1681
+ echo $this->get_tool_icons(false, true);
1682
+ echo '<p class="mb0"><a data-confirm-title="Are you sure you want to reset all theme options?" data-btn-confirm="Reset theme options" data-text-wait="Resetting theme options. Please wait." data-text-confirm="All options (mods) for all themes will be reset. Always ' . esc_attr('<a data-description="Before resetting theme options" href="#" class="create-new-snapshot">create a snapshot</a> if you want to be able to undo') . '." data-text-done="Options for %n themes have been reset." data-text-done-singular="Options for one theme have been reset." class="button button-delete" href="#" id="reset-theme-options">Reset theme options</a>' . $this->get_snapshot_button('reset-theme-options', 'Before resetting theme options') . '</p>';
1683
+ echo '</div>';
1684
  echo '</div>';
1685
 
1686
  $theme = wp_get_theme();
1687
 
1688
  echo '<div class="card">';
1689
+ echo $this->get_card_header(__('Delete Themes', 'wp-reset'), 'tool-delete-themes', true, true);
1690
+ echo '<div class="card-body">';
1691
  echo '<p>' . __('All themes will be deleted. Including the currently active theme - ' . $theme->get('Name') . '.<br><b>There is NO UNDO. WP Reset does not make any file backups.</b>', 'wp-reset') . '</p>';
1692
+
1693
+ echo $this->get_tool_icons(true, true);
1694
+
1695
+ echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete all themes?" data-btn-confirm="Delete all themes" data-text-wait="Deleting all themes. Please wait." data-text-confirm="All themes will be deleted. There is NO UNDO. WP Reset does not make any file backups." data-text-done="%n themes have been deleted." data-text-done-singular="One theme has been deleted." class="button button-delete" href="#" id="delete-themes">Delete all themes</a></p>';
1696
+ echo '</div>';
1697
  echo '</div>';
1698
 
1699
  echo '<div class="card">';
1700
+ echo $this->get_card_header(__('Delete Plugins', 'wp-reset'), 'tool-delete-plugins', true, true);
1701
+ echo '<div class="card-body">';
1702
  echo '<p>' . __('All plugins will be deleted except for WP Reset which will remain active.<br><b>There is NO UNDO. WP Reset does not make any file backups.</b>', 'wp-reset') . '</p>';
1703
+
1704
+ echo $this->get_tool_icons(true, true);
1705
+
1706
+ echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete all plugins?" data-btn-confirm="Delete plugins" data-text-wait="Deleting plugins. Please wait." data-text-confirm="All plugins except WP Reset will be deleted. There is NO UNDO. WP Reset does not make any file backups." data-text-done="%n plugins have been deleted." data-text-done-singular="One plugin has been deleted." class="button button-delete" href="#" id="delete-plugins">Delete plugins</a></p>';
1707
+ echo '</div>';
1708
  echo '</div>';
1709
 
 
1710
  $custom_tables = $this->get_custom_tables();
1711
 
1712
  echo '<div class="card">';
1713
+ echo $this->get_card_header(__('Empty or Delete Custom Tables', 'wp-reset'), 'tool-empty-delete-custom-tables', true, true);
1714
+ echo '<div class="card-body">';
1715
+ echo '<p>' . __('This action affects only custom tables with <code>' . $wpdb->prefix . '</code> prefix. Core WP tables and other tables in the database that do not have that prefix will not be deleted/emptied. Deleting (dropping) tables completely removes them from the database. Emptying (truncating) removes all content from them, but keeps the structure intact.<br>Always <a href="#" class="create-new-snapshot" data-description="Before deleting custom tables">create a snapshot</a> before using this tool if you want to be able to undo its actions.</p>', 'wp-reset');
1716
  if ($custom_tables) {
1717
  echo '<p>' . __('The following ' . sizeof($custom_tables) . ' custom tables are affected by this tool: ');
1718
  foreach ($custom_tables as $tbl) {
1727
  echo '<p>' . __('There are no custom tables. There\'s nothing for this tool to empty or delete.', 'wp-reset') . '</p>';
1728
  $custom_tables_btns = ' disabled';
1729
  }
 
 
1730
 
1731
+ echo $this->get_tool_icons(false, true, true);
1732
+
1733
+ echo '<p class="mb0"><a data-confirm-title="Are you sure you want to empty all custom tables?" data-btn-confirm="Empty custom tables" data-text-wait="Emptying custom tables. Please wait." data-text-confirm="All custom tables with prefix <code>' . $wpdb->prefix . '</code> will be emptied. Always ' . esc_attr('<a href="#" class="create-new-snapshot" data-description="Before emptying custom tables">create a snapshot</a> if you want to be able to undo') . '." data-text-done="%n custom tables have been emptied." data-text-done-singular="One custom table has been emptied." class="button button-delete' . $custom_tables_btns . '" href="#" id="truncate-custom-tables">Empty (truncate) custom tables</a>';
1734
+ echo '<a data-confirm-title="Are you sure you want to delete all custom tables?" data-btn-confirm="Delete custom tables" data-text-wait="Deleting custom tables. Please wait." data-text-confirm="All custom tables with prefix <code>' . $wpdb->prefix . '</code> will be deleted. Always ' . esc_attr('<a href="#" class="create-new-snapshot" data-description="Before deleting custom tables">create a snapshot</a> if you want to be able to undo') . '." data-text-done="%n custom tables have been deleted." data-text-done-singular="One custom table has been deleted." class="button button-delete' . $custom_tables_btns . '" href="#" id="drop-custom-tables">Delete (drop) custom tables</a>' . $this->get_snapshot_button('drop-custom-tables', 'Before deleting custom tables') . '</p>';
1735
+ echo '</div>';
1736
  echo '</div>';
1737
 
1738
  echo '<div class="card">';
1739
+ echo $this->get_card_header(__('Delete .htaccess File', 'wp-reset'), 'tool-delete-htaccess', true, true);
1740
+ echo '<div class="card-body">';
1741
  echo '<p>' . __('This action deletes the .htaccess file located in <code>' . $this->get_htaccess_path() . '</code><br><b>There is NO UNDO. WP Reset does not make any file backups.</b></p>', 'wp-reset');
1742
 
1743
  echo '<p>If you need to edit .htaccess, install our free <a href="' . admin_url('plugin-install.php?tab=plugin-information&plugin=wp-htaccess-editor&TB_iframe=true&width=600&height=550') . '" class="thickbox open-plugin-details-modal">WP Htaccess Editor</a> plugin. It automatically creates backups when you edit .htaccess as well as checks for syntax errors. To create the default .htaccess file open <a href="' . admin_url('options-permalink.php') . '">Settings - Permalinks</a> and re-save settings. WordPress will recreate the file.</p>';
1744
 
1745
+ echo $this->get_tool_icons(true, false);
1746
 
1747
+ echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete the .htaccess file?" data-btn-confirm="Delete .htaccess file" data-text-wait="Deleting .htaccess file. Please wait." data-text-confirm="Htaccess file will be deleted. There is NO UNDO. WP Reset does not make any file backups." data-text-done="Htaccess file has been deleted." data-text-done-singular="Htaccess file has been deleted." class="button button-delete" href="#" id="delete-htaccess">Delete .htaccess file</a></p>';
1748
+
1749
+ echo '</div>';
1750
  echo '</div>';
1751
  } // tab_tools
1752
 
1761
  echo '<div class="card">';
1762
  echo '<h4>' . __('What are Plugin &amp; Theme Collections?', 'wp-reset') . '</h4>';
1763
  echo '<p>' . __('A tool that\'s coming with WP Reset PRO that will <b>save hours &amp; hours of your precious time</b>! Have a set of plugins (and themes) that you install and activate after every reset? Or on every fresh WP installation? Well, no more clicking install &amp; active for ten minutes! Build the collection once and install it with one click as many times as needed.<br>WP Reset stores collections in the cloud so they\'re accessible on every site you build. You can use free plugins and themes from the official repo, and PRO ones by uploading a ZIP file. We\'ll safely store your license keys too, so you have everything in one place.', 'wp-reset') . '</p>';
1764
+ echo '<p><b>Interested in collections and other PRO features?</b> Ping us <a href="https://twitter.com/webfactoryltd" target="_blank">@webfactoryltd</a> and be among the first users who will get PRO in early January 2020.</p>';
1765
  echo '</div>';
1766
  } // tab_collections
1767
 
1783
  echo '<p>' . __('We are very active on the <a href="https://wordpress.org/support/plugin/wp-reset" target="_blank">official WP Reset support forum</a>. If you found a bug, have a feature idea or just want to say hi - please drop by. We love to hear back from our users.', 'wp-reset') . '</p>';
1784
  echo '</div>';
1785
 
 
 
 
 
 
1786
  echo '<div class="card">';
1787
  echo '<h4>' . __('Care to help out?', 'wp-reset') . '</h4>';
1788
  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. A public mention <a href="https://twitter.com/webfactoryltd" target="_blank">@webfactoryltd</a> also does wonders. Thank you!', 'wp-reset') . '</p>';
1802
 
1803
  echo '<div class="card" id="card-snapshots">';
1804
  echo '<h4>';
1805
+ echo __('Snapshots', 'wp-reset');
1806
  echo '<div class="card-header-right"><a class="toggle-card" href="#" title="' . __('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a></div>';
1807
  echo '</h4>';
1808
+ echo '<div class="card-body">';
1809
  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. <a href="https://www.youtube.com/watch?v=xBfMmS12vMY" target="_blank">Watch a short video</a> overview and tutorial about Snapshots.</p>';
1810
+ echo '<p>Snapshots are primarily a development tool. When using various reset tools that alter the database be sure to create a snapshot. Shortcuts are available in the confirmation dialog. If you need a full backup that includes files, use a dedicated <a target="_blank" href="' . admin_url('plugin-install.php?s=backup&tab=search&type=term') . '">backup plugin</a>.</p>';
1811
  echo '<p>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. Restoring a snapshot does not affect other snapshots, or WP Reset settings.</p>';
1812
 
1813
  $table_status = $wpdb->get_results('SHOW TABLE STATUS');
1840
 
1841
  echo '';
1842
  echo '</div>';
1843
+ echo '</div>';
1844
 
1845
+ echo '<div class="card">';
1846
  echo '<h4>';
1847
+ echo __('Snapshots', 'wp-reset');
1848
+ echo '<div class="card-header-right"><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 Snapshot', 'wp-reset') . '</a></div>';
1849
  echo '</h4>';
1850
 
1851
  if ($snapshots = $this->get_snapshots()) {
1852
+ $snapshots = array_reverse($snapshots);
1853
  echo '<table id="wpr-snapshots">';
1854
+ echo '<tr><th>Date</th><th>Description</th><th class="ss-actions">&nbsp;</th></tr>';
1855
  foreach ($snapshots as $ss) {
1856
  echo '<tr id="wpr-ss-' . $ss['uid'] . '">';
1857
  if (!empty($ss['name'])) {
 
1858
  $name = $ss['name'];
1859
  } else {
 
1860
  $name = 'created on ' . date(get_option('date_format'), strtotime($ss['timestamp'])) . ' @ ' . date(get_option('time_format'), strtotime($ss['timestamp']));
1861
  }
1862
+
1863
+ echo '<td>';
1864
+ if (current_time('timestamp') - strtotime($ss['timestamp']) > 12 * HOUR_IN_SECONDS) {
1865
+ echo date(get_option('date_format'), strtotime($ss['timestamp'])) . '<br>@ ' . date(get_option('time_format'), strtotime($ss['timestamp']));
1866
+ } else {
1867
+ echo human_time_diff(strtotime($ss['timestamp']), current_time('timestamp')) . ' ago';
1868
+ }
1869
+ echo '</td>';
1870
+ //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>';
1871
+
1872
+ echo '<td>';
1873
+ if (!empty($ss['name'])) {
1874
+ echo '<b>' . $ss['name'] . '</b><br>';
1875
+ }
1876
+ echo $ss['tbl_core'] . ' standard &amp; ';
1877
  if ($ss['tbl_custom']) {
1878
  echo $ss['tbl_custom'] . ' custom table' . ($ss['tbl_custom'] == 1 ? '' : 's');
1879
  } else {
1881
  }
1882
  echo ' totaling ' . $this->format_size($ss['tbl_size']) . ' in ' . number_format($ss['tbl_rows']) . ' rows</td>';
1883
  echo '<td>';
1884
+ echo '<div class="dropdown">
1885
+ <a class="button dropdown-toggle" href="#">Actions</a>
1886
+ <div class="dropdown-menu">';
1887
+ 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 dropdown-item" data-ss-uid="' . $ss['uid'] . '">Compare snapshot to current data</a>';
1888
+ 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 dropdown-item" data-ss-uid="' . $ss['uid'] . '">Restore snapshot</a>';
1889
+ 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 dropdown-item" data-ss-uid="' . $ss['uid'] . '">Download snapshot</a>';
1890
+ 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 dropdown-item" data-ss-uid="' . $ss['uid'] . '">Delete snapshot</a></div></div></td>';
1891
  echo '</tr>';
1892
  } // foreach
1893
  echo '</table>';
2122
  require_once $this->plugin_dir . 'libs/dumper.php';
2123
 
2124
  try {
2125
+ $world_dumper = WPR_Shuttle_Dumper::create(array(
2126
  'host' => DB_HOST,
2127
  'username' => DB_USER,
2128
  'password' => DB_PASSWORD,
2153
  } // export_snapshot
2154
 
2155
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2156
  /**
2157
  * Replace current tables with ones in snapshot.
2158
  *
2388
  } elseif ($schema1 != $schema2) {
2389
  require_once $this->plugin_dir . 'libs/diff.php';
2390
  require_once $this->plugin_dir . 'libs/diff/Renderer/Html/SideBySide.php';
2391
+ $diff = new WPR_Diff(explode("\n", $tbl_current['schema']), explode("\n", $tbl_snapshot['schema']), array('ignoreWhitespace' => false));
2392
+ $renderer = new WPR_Diff_Renderer_Html_SideBySide;
2393
 
2394
  $out2 .= '<div class="wpr-table-container" data-table="' . $tbl_current['basename'] . '">';
2395
  $out2 .= '<table>';
2550
  }
2551
  }
2552
 
2553
+ if ($plugin_info && is_array($plugin_info)) {
2554
  array_unshift($res->plugins, $plugin_info);
2555
  }
2556
 
2571
  $res = $this->add_plugin_featured('wp-force-ssl', $res);
2572
  $res = $this->add_plugin_featured('eps-301-redirects', $res);
2573
 
 
2574
  return $res;
2575
  } // plugins_api_result
2576