Duplicator – WordPress Migration Plugin - Version 1.5.0

Version Description

Download this release

Release Info

Developer cory@lamle.org
Plugin Icon 128x128 Duplicator – WordPress Migration Plugin
Version 1.5.0
Comparing to
See all releases

Code changes from version 1.4.7.2 to 1.5.0

Files changed (70) hide show
  1. assets/css/images/index.php +2 -1
  2. assets/css/index.php +2 -1
  3. assets/css/style.css +35 -5
  4. assets/img/index.php +2 -1
  5. assets/js/duplicator-tooltip.js +122 -0
  6. assets/js/index.php +2 -1
  7. assets/js/javascript.php +223 -223
  8. assets/js/jquery.qtip/index.php +2 -1
  9. assets/js/popper/popper.js +1961 -0
  10. assets/js/popper/popper.min.js +5 -0
  11. assets/js/tippy/dup-pro-tippy.css +91 -0
  12. assets/js/tippy/index.php +3 -0
  13. assets/js/tippy/tippy-bundle.umd.js +2509 -0
  14. assets/js/tippy/tippy-bundle.umd.min.js +1 -0
  15. assets/js/tippy/tippy.css +1 -0
  16. assets/webfonts/index.php +2 -1
  17. classes/class.archive.config.php +44 -48
  18. classes/class.constants.php +1 -0
  19. classes/class.db.php +316 -261
  20. classes/class.io.php +18 -121
  21. classes/class.logging.php +254 -238
  22. classes/class.password.php +235 -229
  23. classes/class.plugin.upgrade.php +8 -8
  24. classes/class.server.php +326 -323
  25. classes/class.settings.php +254 -268
  26. classes/host/class.custom.host.manager.php +13 -12
  27. classes/host/class.flywheel.host.php +2 -3
  28. classes/host/class.godaddy.host.php +7 -4
  29. classes/host/class.liquidweb.host.php +10 -3
  30. classes/host/class.pantheon.host.php +9 -4
  31. classes/host/class.wordpresscom.host.php +9 -4
  32. classes/host/class.wpengine.host.php +8 -5
  33. classes/host/interface.host.php +3 -2
  34. classes/index.php +2 -1
  35. classes/package/class.pack.archive.file.list.php +202 -0
  36. classes/package/class.pack.archive.filters.php +96 -96
  37. classes/package/class.pack.archive.php +1014 -805
  38. classes/package/class.pack.archive.zip.php +61 -48
  39. classes/package/class.pack.database.php +787 -779
  40. classes/package/class.pack.installer.php +1043 -551
  41. classes/package/class.pack.php +1858 -1805
  42. classes/package/duparchive/class.pack.archive.duparchive.php +268 -313
  43. classes/package/duparchive/class.pack.archive.duparchive.state.create.php +66 -80
  44. classes/package/duparchive/class.pack.archive.duparchive.state.expand.php +88 -133
  45. classes/package/duparchive/index.php +2 -1
  46. classes/package/index.php +2 -1
  47. classes/ui/class.ui.dialog.php +219 -235
  48. classes/ui/class.ui.messages.php +111 -117
  49. classes/ui/class.ui.notice.php +136 -82
  50. classes/ui/class.ui.screen.base.php +23 -20
  51. classes/ui/class.ui.viewstate.php +85 -83
  52. classes/ui/index.php +2 -1
  53. classes/utilities/class.u.json.php +166 -167
  54. classes/utilities/class.u.migration.php +0 -137
  55. classes/utilities/class.u.multisite.php +264 -39
  56. classes/utilities/class.u.patch.php +35 -4
  57. classes/utilities/class.u.php +133 -144
  58. classes/utilities/class.u.scancheck.php +169 -181
  59. classes/utilities/class.u.shell.php +49 -45
  60. classes/utilities/class.u.string.php +96 -99
  61. classes/utilities/class.u.validator.php +220 -231
  62. classes/utilities/class.u.zip.php +160 -154
  63. ctrls/class.web.services.php +19 -22
  64. ctrls/ctrl.base.php +152 -153
  65. ctrls/ctrl.package.php +430 -479
  66. ctrls/ctrl.tools.php +183 -132
  67. ctrls/ctrl.ui.php +51 -52
  68. ctrls/index.php +2 -1
  69. deactivation.php +434 -440
  70. define.php +70 -78
assets/css/images/index.php CHANGED
@@ -1,2 +1,3 @@
1
  <?php
2
- //silent
 
1
  <?php
2
+
3
+ //silent
assets/css/index.php CHANGED
@@ -1,2 +1,3 @@
1
  <?php
2
- //silent
 
1
  <?php
2
+
3
+ //silent
assets/css/style.css CHANGED
@@ -22,14 +22,16 @@ i.grey-icon {color:#777}
22
  .no-decoration {text-decoration:none;}
23
  p.description {padding-top:3px}
24
  .dup-guide-txt-color {color:#b0b0b0;}
 
 
25
 
26
  /*TABS*/
27
  ul.category-tabs li {cursor:pointer;user-select: none;}
28
 
29
  /*BOXES:Expandable sections */
30
  div.dup-box {padding:0px; display:block; background-color:#fff; border:1px solid #e5e5e5; box-shadow:0 1px 1px rgba(0,0,0,.04);}
31
- div.dup-box-title {font-size:20px; padding:12px 0 3px 12px; font-weight:bold; cursor:pointer; height:30px; margin:0; color:#000; }
32
- div.dup-box-title:hover {color:#555;}
33
  div.dup-box-arrow {text-decoration:none!important; float:right; width:27px; height:30px; font-size:16px; cursor:pointer; padding:1px 0 0 0; white-space:nowrap}
34
  div.dup-box-panel {padding:10px 15px 10px 15px; border-top:1px solid #EEEEEE; margin:-1px 0 0 0;}
35
  div.dup-redirect {font-size:16px; font-weight:bold; padding:10px}
@@ -66,6 +68,34 @@ div.dup-global-error-reserved-files p.pass-lnks {line-height:24px; margin:-7px 0
66
  div.dup-global-error-reserved-files div.pass-msg {padding:5px 0 0 10px; font-size:11px; color:#999; font-style:italic}
67
  div.dup-wpnotice-box {display:none;}
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  /*================================================
70
  PARSLEY:Overrides*/
71
  input.parsley-error, textarea.parsley-error {
@@ -210,6 +240,8 @@ div.dup-screen-hlp-info {line-height:26px; padding:10px 0 10px 0}
210
  }
211
 
212
  /** Settings **/
 
 
213
  #installer-name-mode-option {
214
  line-height:25px;
215
  }
@@ -221,9 +253,7 @@ div.dup-screen-hlp-info {line-height:26px; padding:10px 0 10px 0}
221
  line-height:18px;
222
  }
223
 
224
- #dup-lite-inst-mode-details p {
225
- margin:1em 0;
226
- }
227
 
228
  .storage_pos_fixed_label {
229
  display:inline-block;
22
  .no-decoration {text-decoration:none;}
23
  p.description {padding-top:3px}
24
  .dup-guide-txt-color {color:#b0b0b0;}
25
+ i.shield-on {color:#337114}
26
+ i.shield-off {color:maroon}
27
 
28
  /*TABS*/
29
  ul.category-tabs li {cursor:pointer;user-select: none;}
30
 
31
  /*BOXES:Expandable sections */
32
  div.dup-box {padding:0px; display:block; background-color:#fff; border:1px solid #e5e5e5; box-shadow:0 1px 1px rgba(0,0,0,.04);}
33
+ div.dup-box-title {font-size:20px; padding:12px 0 3px 12px; font-weight:bold; cursor:pointer; height:30px; margin:0; color:#000; user-select:none; }
34
+ div.dup-box-title:hover {background-color: #f9f9f9;}
35
  div.dup-box-arrow {text-decoration:none!important; float:right; width:27px; height:30px; font-size:16px; cursor:pointer; padding:1px 0 0 0; white-space:nowrap}
36
  div.dup-box-panel {padding:10px 15px 10px 15px; border-top:1px solid #EEEEEE; margin:-1px 0 0 0;}
37
  div.dup-redirect {font-size:16px; font-weight:bold; padding:10px}
68
  div.dup-global-error-reserved-files div.pass-msg {padding:5px 0 0 10px; font-size:11px; color:#999; font-style:italic}
69
  div.dup-wpnotice-box {display:none;}
70
 
71
+ .dup-migration-pass-wrapper p {
72
+ font-size: 14px;
73
+ }
74
+
75
+ .dup-migration-pass-title {
76
+ color: green;
77
+ font-size: 20px;
78
+ margin: 10px 0;
79
+ font-weight: bold;
80
+ }
81
+
82
+ .dup-stored-minstallation-files {
83
+ margin-left: 20px;
84
+ line-height: 1;
85
+ font-style: italic;
86
+ margin-top: 0;
87
+ font-size: 12px;
88
+ }
89
+
90
+ .dup-migration-pass-wrapper .sub-note {
91
+ font-size: 12px;
92
+ }
93
+
94
+ label.dup-store-lbl:hover {
95
+ color:#000;
96
+ font-weight:500;
97
+ }
98
+
99
  /*================================================
100
  PARSLEY:Overrides*/
101
  input.parsley-error, textarea.parsley-error {
240
  }
241
 
242
  /** Settings **/
243
+ .dup-settings-pages p.description {max-width:700px; font-size:13px; color:#666; padding-left:15px; text-align:justify; }
244
+ #dup-lite-inst-mode-details p { margin:1em 0; max-width:700px; font-size:13px; color:#666; padding-left:15px; text-align:justify;}
245
  #installer-name-mode-option {
246
  line-height:25px;
247
  }
253
  line-height:18px;
254
  }
255
 
256
+
 
 
257
 
258
  .storage_pos_fixed_label {
259
  display:inline-block;
assets/img/index.php CHANGED
@@ -1,2 +1,3 @@
1
  <?php
2
- //silent
 
1
  <?php
2
+
3
+ //silent
assets/js/duplicator-tooltip.js ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*! dup tooltip */
2
+ (function ($) {
3
+ DuplicatorTooltip = {
4
+ initialized: false,
5
+ messages: {
6
+ 'copy': 'Copy to clipboard',
7
+ 'copied': 'copied to clipboard',
8
+ 'copyUnable': 'Unable to copy'
9
+ },
10
+ load: function () {
11
+ if (this.initialized) {
12
+ return;
13
+ }
14
+
15
+ this.loadSelector('[data-tooltip]');
16
+ this.loadCopySelector('[data-dup-copy-value]');
17
+
18
+ this.initialized = true;
19
+ },
20
+ loadSelector: function (selector) {
21
+ $(selector).each(function () {
22
+ if (this._tippy) {
23
+ // already init
24
+ return;
25
+ }
26
+
27
+ tippy(this, {
28
+ content: function (ref) {
29
+ var header = ref.dataset.tooltipTitle;
30
+ var body = ref.dataset.tooltip;
31
+ var res = header !== undefined ? '<h3>' + header + '</h3>' : '';
32
+ res += '<div class="dup-tippy-content">' + body + '</div>';
33
+ return res;
34
+ },
35
+ allowHTML: true,
36
+ interactive: true,
37
+ placement: this.dataset.tooltipPlacement ? this.dataset.tooltipPlacement : 'bottom-start',
38
+ theme: 'duplicator',
39
+ zIndex: 900000,
40
+ appendTo: document.body
41
+ });
42
+ $(this).data('dup-tooltip-loaded', true);
43
+ });
44
+ },
45
+ loadCopySelector: function (selector) {
46
+ $(selector).each(function () {
47
+ if (this._tippy) {
48
+ // already init
49
+ return;
50
+ }
51
+
52
+ var element = $(this);
53
+ if (element.hasClass('disabled')) {
54
+ return;
55
+ }
56
+
57
+ var tippyElement = tippy(this, {
58
+ allowHTML: true,
59
+ placement: this.dataset.tooltipPlacement ? this.dataset.tooltipPlacement : 'bottom-start',
60
+ theme: 'duplicator',
61
+ zIndex: 900000,
62
+ hideOnClick: false,
63
+ trigger: 'manual'
64
+ });
65
+
66
+ var copyTitle = element.is('[data-dup-copy-title]') ? element.data('dup-copy-title') : DuplicatorTooltip.messages.copy;
67
+ tippyElement.setContent('<div class="dup-tippy-content">' + copyTitle + '</div>');
68
+
69
+ //Have to set manually otherwise might hide on click.
70
+ element.mouseover(function () {
71
+ tippyElement.show();
72
+ }).mouseout(function () {
73
+ tippyElement.hide();
74
+ });
75
+
76
+ element.click(function () {
77
+ var valueToCopy = element.data('dup-copy-value');
78
+ var copiedTitle = element.is('[data-dup-copied-title]') ? element.data('dup-copied-title') : valueToCopy + ' ' + DuplicatorTooltip.messages.copied;
79
+ var message = DuplicatorTooltip.messages.copyUnable;
80
+ var tmpArea = jQuery("<textarea></textarea>").css({
81
+ position: 'absolute',
82
+ top: '-10000px'
83
+ }).text(valueToCopy).appendTo("body");
84
+ tmpArea.select();
85
+
86
+ try {
87
+ message = document.execCommand('copy') ? copiedTitle : 'Unable to copy';
88
+ } catch (err) {
89
+ console.log(err);
90
+ }
91
+
92
+ tippyElement.setContent('<div class="dup-tippy-content">' + message + '</div>');
93
+ tippyElement.setProps({ theme: 'duplicator-filled' });
94
+
95
+ setTimeout(function () {
96
+ tippyElement.setContent('<div class="dup-tippy-content">' + copyTitle + '</div>');
97
+ tippyElement.setProps({ theme: 'duplicator' });
98
+ }, 2000);
99
+ });
100
+ });
101
+ },
102
+ updateElementContent: function (selector, content) {
103
+ if ($(selector).get(0)) {
104
+ $(selector).get(0)._tippy.setContent('<div class="dup-tippy-content">' + content + '</div>');
105
+ }
106
+ },
107
+ unload: function () {
108
+ var tooltips = document.querySelectorAll('[data-tooltip], [data-dup-copy-value]');
109
+ tooltips.forEach(function (element) {
110
+ if (element._tippy) {
111
+ element._tippy.destroy();
112
+ element._tippy = null;
113
+ }
114
+ });
115
+ this.initialized = false;
116
+ },
117
+ reload: function () {
118
+ this.unload();
119
+ this.load();
120
+ }
121
+ }
122
+ })(jQuery);
assets/js/index.php CHANGED
@@ -1,2 +1,3 @@
1
  <?php
2
- //silent
 
1
  <?php
2
+
3
+ //silent
assets/js/javascript.php CHANGED
@@ -3,15 +3,15 @@ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
  ?>
4
  <script>
5
  /* DESCRIPTION: Methods and Objects in this file are global and common in
6
- * nature use this file to place all shared methods and varibles */
7
 
8
  //UNIQUE NAMESPACE
9
- Duplicator = new Object();
10
- Duplicator.UI = new Object();
11
- Duplicator.Pack = new Object();
12
  Duplicator.Settings = new Object();
13
- Duplicator.Tools = new Object();
14
- Duplicator.Debug = new Object();
15
 
16
  //GLOBAL CONSTANTS
17
  Duplicator.DEBUG_AJAX_RESPONSE = false;
@@ -19,193 +19,193 @@ Duplicator.AJAX_TIMER = null;
19
 
20
  Duplicator.parseJSON = function(mixData) {
21
  try {
22
- var parsed = JSON.parse(mixData);
23
- return parsed;
24
- } catch (e) {
25
- console.log("JSON parse failed - 1");
26
- console.log(mixData);
27
- }
28
-
29
- if (mixData.indexOf('[') > -1 && mixData.indexOf('{') > -1) {
30
- if (mixData.indexOf('{') < mixData.indexOf('[')) {
31
- var startBracket = '{';
32
- var endBracket = '}';
33
- } else {
34
- var startBracket = '[';
35
- var endBracket = ']';
36
- }
37
- } else if (mixData.indexOf('[') > -1 && mixData.indexOf('{') === -1) {
38
- var startBracket = '[';
39
- var endBracket = ']';
40
- } else {
41
- var startBracket = '{';
42
- var endBracket = '}';
43
- }
44
-
45
- var jsonStartPos = mixData.indexOf(startBracket);
46
- var jsonLastPos = mixData.lastIndexOf(endBracket);
47
- if (jsonStartPos > -1 && jsonLastPos > -1) {
48
- var expectedJsonStr = mixData.slice(jsonStartPos, jsonLastPos + 1);
49
- try {
50
- var parsed = JSON.parse(expectedJsonStr);
51
- return parsed;
52
- } catch (e) {
53
- console.log("JSON parse failed - 2");
54
- console.log(mixData);
55
- throw e;
56
  return false;
57
- }
58
- }
59
- throw "could not parse the JSON";
60
  return false;
61
  }
62
 
63
 
64
  /* ============================================================================
65
  * BASE NAMESPACE: All methods at the top of the Duplicator Namespace
66
- * ============================================================================ */
67
 
68
- /* Starts a timer for Ajax calls */
69
  Duplicator.StartAjaxTimer = function()
70
  {
71
- Duplicator.AJAX_TIMER = new Date();
72
  };
73
 
74
- /* Ends a timer for Ajax calls */
75
  Duplicator.EndAjaxTimer = function()
76
  {
77
- var endTime = new Date();
78
- Duplicator.AJAX_TIMER = (endTime.getTime() - Duplicator.AJAX_TIMER) /1000;
79
  };
80
 
81
- /* Reloads the current window
82
- * @param data An xhr object */
83
  Duplicator.ReloadWindow = function(data)
84
  {
85
- if (Duplicator.DEBUG_AJAX_RESPONSE) {
86
- Duplicator.Pack.ShowError('debug on', data);
87
- } else {
88
- window.location.reload(true);
89
- }
90
  };
91
 
92
  //Basic Util Methods here:
93
  Duplicator.OpenLogWindow = function(target)
94
  {
95
- var target = "log-win" || null;
96
- if (target != null) {
97
- window.open('?page=duplicator-tools&tab=diagnostics&section=log', 'log-win');
98
- } else {
99
- window.open('<?php echo esc_js(DUP_Settings::getSsdirUrl()); ?>' + '/' + log)
100
- }
101
  };
102
 
103
 
104
  /* ============================================================================
105
  * UI NAMESPACE: All methods at the top of the Duplicator Namespace
106
- * ============================================================================ */
107
 
108
- /* Saves the state of a UI element */
109
  Duplicator.UI.SaveViewState = function (key, value)
110
  {
111
- if (key != undefined && value != undefined ) {
112
- jQuery.ajax({
113
- type: "POST",
114
- url: ajaxurl,
115
- dataType: "text",
116
- data: {
117
- action : 'DUP_CTRL_UI_SaveViewState',
118
- key: key,
119
- value: value,
120
- nonce: '<?php echo wp_create_nonce('DUP_CTRL_UI_SaveViewState'); ?>'
121
- },
122
- success: function(respData) {
123
- try {
124
- var data = Duplicator.parseJSON(respData);
125
- } catch(err) {
126
- console.error(err);
127
- console.error('JSON parse failed for response data: ' + respData);
128
- return false;
129
- }
130
- },
131
- error: function(data) {}
132
- });
133
- }
134
  }
135
 
136
- /* Saves multiple states of a UI element */
137
  Duplicator.UI.SaveMulViewStates = function (states)
138
  {
139
- jQuery.ajax({
140
- type: "POST",
141
- url: ajaxurl,
142
- dataType: "text",
143
- data: {
144
- action : 'DUP_CTRL_UI_SaveViewState',
145
- states: states,
146
- nonce: '<?php echo wp_create_nonce('DUP_CTRL_UI_SaveViewState'); ?>'
147
- },
148
- success: function(respData) {
149
- try {
150
- var data = Duplicator.parseJSON(respData);
151
- } catch(err) {
152
- console.error(err);
153
- console.error('JSON parse failed for response data: ' + respData);
154
- return false;
155
- }
156
- },
157
- error: function(data) {}
158
- });
159
  }
160
 
161
  /* Animates the progress bar */
162
  Duplicator.UI.AnimateProgressBar = function(id)
163
  {
164
- //Create Progress Bar
165
- var $mainbar = jQuery("#" + id);
166
- $mainbar.progressbar({ value: 100 });
167
- $mainbar.height(25);
168
- runAnimation($mainbar);
169
-
170
- function runAnimation($pb) {
171
- $pb.css({ "padding-left": "0%", "padding-right": "90%" });
172
- $pb.progressbar("option", "value", 100);
173
- $pb.animate({ paddingLeft: "90%", paddingRight: "0%" }, 3000, "linear", function () { runAnimation($pb); });
174
- }
175
  }
176
 
177
  Duplicator.UI.IsSaveViewState = true;
178
  /* Toggle MetaBoxes */
179
  Duplicator.UI.ToggleMetaBox = function()
180
  {
181
- var $title = jQuery(this);
182
- var $panel = $title.parent().find('.dup-box-panel');
183
- var $arrow = $title.parent().find('.dup-box-arrow i');
184
- var key = $panel.attr('id');
185
- var value = $panel.is(":visible") ? 0 : 1;
186
- $panel.toggle();
187
- if (Duplicator.UI.IsSaveViewState)
188
- Duplicator.UI.SaveViewState(key, value);
189
- (value)
190
- ? $arrow.removeClass().addClass('fa fa-caret-up')
191
- : $arrow.removeClass().addClass('fa fa-caret-down');
192
-
193
  }
194
 
195
  Duplicator.UI.readonly = function(item)
196
  {
197
- jQuery(item).attr('readonly', 'true').css({color:'#999'});
198
  }
199
 
200
  Duplicator.UI.disable = function(item)
201
  {
202
- jQuery(item).attr('disabled', 'true').css({color:'#999'});
203
  }
204
 
205
  Duplicator.UI.enable = function(item)
206
  {
207
- jQuery(item).removeAttr('disabled').css({color:'#000'});
208
- jQuery(item).removeAttr('readonly').css({color:'#000'});
209
  }
210
 
211
  //Init
@@ -290,108 +290,108 @@ jQuery(document).ready(function($)
290
  });
291
  };
292
 
293
- //INIT: Tabs
294
- $("div[data-dup-tabs='true']").each(function () {
295
-
296
- //Load Tab Setup
297
- var $root = $(this);
298
- var $lblRoot = $root.find('ul:first-child')
299
- var $lblKids = $lblRoot.children('li');
300
- var $pnls = $root.children('div');
301
-
302
- //Apply Styles
303
- $root.addClass('categorydiv');
304
- $lblRoot.addClass('category-tabs');
305
- $pnls.addClass('tabs-panel').css('display', 'none');
306
- $lblKids.eq(0).addClass('tabs').css('font-weight', 'bold');
307
- $pnls.eq(0).show();
308
-
309
- //Attach Events
310
- $lblKids.click(function(evt)
311
- {
312
- var $lbls = $(evt.target).parent().children('li');
313
- var $pnls = $(evt.target).parent().parent().children('div');
314
- var index = ($(evt.target).index());
315
-
316
- $lbls.removeClass('tabs').css('font-weight', 'normal');
317
- $lbls.eq(index).addClass('tabs').css('font-weight', 'bold');
318
- $pnls.hide();
319
- $pnls.eq(index).show();
320
- });
321
- });
322
-
323
- //Init: Toggle MetaBoxes
324
- $('div.dup-box div.dup-box-title').each(function() {
325
- var $title = $(this);
326
- var $panel = $title.parent().find('.dup-box-panel');
327
- var $arrow = $title.find('.dup-box-arrow');
328
- $title.click(Duplicator.UI.ToggleMetaBox);
329
- ($panel.is(":visible"))
330
- ? $arrow.html('<i class="fa fa-caret-up"></i>')
331
- : $arrow.html('<i class="fa fa-caret-down"></i>');
332
- });
333
 
334
 
335
  Duplicator.UI.loadQtip();
336
  Duplicator.UI.loadSimpeQtip();
337
  Duplicator.UI.Copytext();
338
 
339
- //HANDLEBARS HELPERS
340
- if (typeof(Handlebars) != "undefined"){
341
-
342
- function _handleBarscheckCondition(v1, operator, v2) {
343
- switch(operator) {
344
- case '==':
345
- return (v1 == v2);
346
- case '===':
347
- return (v1 === v2);
348
- case '!==':
349
- return (v1 !== v2);
350
- case '<':
351
- return (v1 < v2);
352
- case '<=':
353
- return (v1 <= v2);
354
- case '>':
355
- return (v1 > v2);
356
- case '>=':
357
- return (v1 >= v2);
358
- case '&&':
359
- return (v1 && v2);
360
- case '||':
361
- return (v1 || v2);
362
- case 'obj||':
363
- v1 = typeof(v1) == 'object' ? v1.length : v1;
364
- v2 = typeof(v2) == 'object' ? v2.length : v2;
365
- return (v1 !=0 || v2 != 0);
366
- default:
367
- return false;
368
- }
369
- }
370
-
371
- Handlebars.registerHelper('ifCond', function (v1, operator, v2, options) {
372
- return _handleBarscheckCondition(v1, operator, v2)
373
- ? options.fn(this)
374
- : options.inverse(this);
375
- });
376
-
377
- Handlebars.registerHelper('if_eq', function(a, b, opts) { return (a == b) ? opts.fn(this) : opts.inverse(this);});
378
- Handlebars.registerHelper('if_neq', function(a, b, opts) { return (a != b) ? opts.fn(this) : opts.inverse(this);});
379
- }
380
-
381
- //Prevent notice boxes from flashing as its re-positioned in DOM
382
- $('div.dup-wpnotice-box').show(300);
383
 
384
  });
385
 
386
  jQuery(document).ready(function($) {
387
  $('.duplicator-message .notice-dismiss, .duplicator-message .duplicator-notice-dismiss, .duplicator-message .duplicator-notice-rate-now').on('click', function (event) {
388
- if ('button button-primary duplicator-notice-rate-now' !== $(event.target).attr('class')) {
389
- event.preventDefault();
390
- }
391
  $.post(ajaxurl, {
392
  action: 'duplicator_set_admin_notice_viewed',
393
  notice_id: $(this).closest('.duplicator-message-dismissed').data('notice_id'),
394
- nonce: '<?php echo wp_create_nonce('duplicator_set_admin_notice_viewed'); ?>'
395
  });
396
  var $wrapperElm = $(this).closest('.duplicator-message-dismissed');
397
  $wrapperElm.fadeTo(100, 0, function () {
3
  ?>
4
  <script>
5
  /* DESCRIPTION: Methods and Objects in this file are global and common in
6
+ * nature use this file to place all shared methods and varibles */
7
 
8
  //UNIQUE NAMESPACE
9
+ Duplicator = new Object();
10
+ Duplicator.UI = new Object();
11
+ Duplicator.Pack = new Object();
12
  Duplicator.Settings = new Object();
13
+ Duplicator.Tools = new Object();
14
+ Duplicator.Debug = new Object();
15
 
16
  //GLOBAL CONSTANTS
17
  Duplicator.DEBUG_AJAX_RESPONSE = false;
19
 
20
  Duplicator.parseJSON = function(mixData) {
21
  try {
22
+ var parsed = JSON.parse(mixData);
23
+ return parsed;
24
+ } catch (e) {
25
+ console.log("JSON parse failed - 1");
26
+ console.log(mixData);
27
+ }
28
+
29
+ if (mixData.indexOf('[') > -1 && mixData.indexOf('{') > -1) {
30
+ if (mixData.indexOf('{') < mixData.indexOf('[')) {
31
+ var startBracket = '{';
32
+ var endBracket = '}';
33
+ } else {
34
+ var startBracket = '[';
35
+ var endBracket = ']';
36
+ }
37
+ } else if (mixData.indexOf('[') > -1 && mixData.indexOf('{') === -1) {
38
+ var startBracket = '[';
39
+ var endBracket = ']';
40
+ } else {
41
+ var startBracket = '{';
42
+ var endBracket = '}';
43
+ }
44
+
45
+ var jsonStartPos = mixData.indexOf(startBracket);
46
+ var jsonLastPos = mixData.lastIndexOf(endBracket);
47
+ if (jsonStartPos > -1 && jsonLastPos > -1) {
48
+ var expectedJsonStr = mixData.slice(jsonStartPos, jsonLastPos + 1);
49
+ try {
50
+ var parsed = JSON.parse(expectedJsonStr);
51
+ return parsed;
52
+ } catch (e) {
53
+ console.log("JSON parse failed - 2");
54
+ console.log(mixData);
55
+ throw e;
56
  return false;
57
+ }
58
+ }
59
+ throw "could not parse the JSON";
60
  return false;
61
  }
62
 
63
 
64
  /* ============================================================================
65
  * BASE NAMESPACE: All methods at the top of the Duplicator Namespace
66
+ * ============================================================================ */
67
 
68
+ /* Starts a timer for Ajax calls */
69
  Duplicator.StartAjaxTimer = function()
70
  {
71
+ Duplicator.AJAX_TIMER = new Date();
72
  };
73
 
74
+ /* Ends a timer for Ajax calls */
75
  Duplicator.EndAjaxTimer = function()
76
  {
77
+ var endTime = new Date();
78
+ Duplicator.AJAX_TIMER = (endTime.getTime() - Duplicator.AJAX_TIMER) /1000;
79
  };
80
 
81
+ /* Reloads the current window
82
+ * @param data An xhr object */
83
  Duplicator.ReloadWindow = function(data)
84
  {
85
+ if (Duplicator.DEBUG_AJAX_RESPONSE) {
86
+ Duplicator.Pack.ShowError('debug on', data);
87
+ } else {
88
+ window.location.reload(true);
89
+ }
90
  };
91
 
92
  //Basic Util Methods here:
93
  Duplicator.OpenLogWindow = function(target)
94
  {
95
+ var target = "log-win" || null;
96
+ if (target != null) {
97
+ window.open('?page=duplicator-tools&tab=diagnostics&section=log', 'log-win');
98
+ } else {
99
+ window.open('<?php echo esc_js(DUP_Settings::getSsdirUrl()); ?>' + '/' + log)
100
+ }
101
  };
102
 
103
 
104
  /* ============================================================================
105
  * UI NAMESPACE: All methods at the top of the Duplicator Namespace
106
+ * ============================================================================ */
107
 
108
+ /* Saves the state of a UI element */
109
  Duplicator.UI.SaveViewState = function (key, value)
110
  {
111
+ if (key != undefined && value != undefined ) {
112
+ jQuery.ajax({
113
+ type: "POST",
114
+ url: ajaxurl,
115
+ dataType: "text",
116
+ data: {
117
+ action : 'DUP_CTRL_UI_SaveViewState',
118
+ key: key,
119
+ value: value,
120
+ nonce: '<?php echo wp_create_nonce('DUP_CTRL_UI_SaveViewState'); ?>'
121
+ },
122
+ success: function(respData) {
123
+ try {
124
+ var data = Duplicator.parseJSON(respData);
125
+ } catch(err) {
126
+ console.error(err);
127
+ console.error('JSON parse failed for response data: ' + respData);
128
+ return false;
129
+ }
130
+ },
131
+ error: function(data) {}
132
+ });
133
+ }
134
  }
135
 
136
+ /* Saves multiple states of a UI element */
137
  Duplicator.UI.SaveMulViewStates = function (states)
138
  {
139
+ jQuery.ajax({
140
+ type: "POST",
141
+ url: ajaxurl,
142
+ dataType: "text",
143
+ data: {
144
+ action : 'DUP_CTRL_UI_SaveViewState',
145
+ states: states,
146
+ nonce: '<?php echo wp_create_nonce('DUP_CTRL_UI_SaveViewState'); ?>'
147
+ },
148
+ success: function(respData) {
149
+ try {
150
+ var data = Duplicator.parseJSON(respData);
151
+ } catch(err) {
152
+ console.error(err);
153
+ console.error('JSON parse failed for response data: ' + respData);
154
+ return false;
155
+ }
156
+ },
157
+ error: function(data) {}
158
+ });
159
  }
160
 
161
  /* Animates the progress bar */
162
  Duplicator.UI.AnimateProgressBar = function(id)
163
  {
164
+ //Create Progress Bar
165
+ var $mainbar = jQuery("#" + id);
166
+ $mainbar.progressbar({ value: 100 });
167
+ $mainbar.height(25);
168
+ runAnimation($mainbar);
169
+
170
+ function runAnimation($pb) {
171
+ $pb.css({ "padding-left": "0%", "padding-right": "90%" });
172
+ $pb.progressbar("option", "value", 100);
173
+ $pb.animate({ paddingLeft: "90%", paddingRight: "0%" }, 3000, "linear", function () { runAnimation($pb); });
174
+ }
175
  }
176
 
177
  Duplicator.UI.IsSaveViewState = true;
178
  /* Toggle MetaBoxes */
179
  Duplicator.UI.ToggleMetaBox = function()
180
  {
181
+ var $title = jQuery(this);
182
+ var $panel = $title.parent().find('.dup-box-panel');
183
+ var $arrow = $title.parent().find('.dup-box-arrow i');
184
+ var key = $panel.attr('id');
185
+ var value = $panel.is(":visible") ? 0 : 1;
186
+ $panel.toggle();
187
+ if (Duplicator.UI.IsSaveViewState)
188
+ Duplicator.UI.SaveViewState(key, value);
189
+ (value)
190
+ ? $arrow.removeClass().addClass('fa fa-caret-up')
191
+ : $arrow.removeClass().addClass('fa fa-caret-down');
192
+
193
  }
194
 
195
  Duplicator.UI.readonly = function(item)
196
  {
197
+ jQuery(item).attr('readonly', 'true').css({color:'#999'});
198
  }
199
 
200
  Duplicator.UI.disable = function(item)
201
  {
202
+ jQuery(item).attr('disabled', 'true').css({color:'#999'});
203
  }
204
 
205
  Duplicator.UI.enable = function(item)
206
  {
207
+ jQuery(item).removeAttr('disabled').css({color:'#000'});
208
+ jQuery(item).removeAttr('readonly').css({color:'#000'});
209
  }
210
 
211
  //Init
290
  });
291
  };
292
 
293
+ //INIT: Tabs
294
+ $("div[data-dup-tabs='true']").each(function () {
295
+
296
+ //Load Tab Setup
297
+ var $root = $(this);
298
+ var $lblRoot = $root.find('ul:first-child')
299
+ var $lblKids = $lblRoot.children('li');
300
+ var $pnls = $root.children('div');
301
+
302
+ //Apply Styles
303
+ $root.addClass('categorydiv');
304
+ $lblRoot.addClass('category-tabs');
305
+ $pnls.addClass('tabs-panel').css('display', 'none');
306
+ $lblKids.eq(0).addClass('tabs').css('font-weight', 'bold');
307
+ $pnls.eq(0).show();
308
+
309
+ //Attach Events
310
+ $lblKids.click(function(evt)
311
+ {
312
+ var $lbls = $(evt.target).parent().children('li');
313
+ var $pnls = $(evt.target).parent().parent().children('div');
314
+ var index = ($(evt.target).index());
315
+
316
+ $lbls.removeClass('tabs').css('font-weight', 'normal');
317
+ $lbls.eq(index).addClass('tabs').css('font-weight', 'bold');
318
+ $pnls.hide();
319
+ $pnls.eq(index).show();
320
+ });
321
+ });
322
+
323
+ //Init: Toggle MetaBoxes
324
+ $('div.dup-box div.dup-box-title').each(function() {
325
+ var $title = $(this);
326
+ var $panel = $title.parent().find('.dup-box-panel');
327
+ var $arrow = $title.find('.dup-box-arrow');
328
+ $title.click(Duplicator.UI.ToggleMetaBox);
329
+ ($panel.is(":visible"))
330
+ ? $arrow.html('<i class="fa fa-caret-up"></i>')
331
+ : $arrow.html('<i class="fa fa-caret-down"></i>');
332
+ });
333
 
334
 
335
  Duplicator.UI.loadQtip();
336
  Duplicator.UI.loadSimpeQtip();
337
  Duplicator.UI.Copytext();
338
 
339
+ //HANDLEBARS HELPERS
340
+ if (typeof(Handlebars) != "undefined"){
341
+
342
+ function _handleBarscheckCondition(v1, operator, v2) {
343
+ switch(operator) {
344
+ case '==':
345
+ return (v1 == v2);
346
+ case '===':
347
+ return (v1 === v2);
348
+ case '!==':
349
+ return (v1 !== v2);
350
+ case '<':
351
+ return (v1 < v2);
352
+ case '<=':
353
+ return (v1 <= v2);
354
+ case '>':
355
+ return (v1 > v2);
356
+ case '>=':
357
+ return (v1 >= v2);
358
+ case '&&':
359
+ return (v1 && v2);
360
+ case '||':
361
+ return (v1 || v2);
362
+ case 'obj||':
363
+ v1 = typeof(v1) == 'object' ? v1.length : v1;
364
+ v2 = typeof(v2) == 'object' ? v2.length : v2;
365
+ return (v1 !=0 || v2 != 0);
366
+ default:
367
+ return false;
368
+ }
369
+ }
370
+
371
+ Handlebars.registerHelper('ifCond', function (v1, operator, v2, options) {
372
+ return _handleBarscheckCondition(v1, operator, v2)
373
+ ? options.fn(this)
374
+ : options.inverse(this);
375
+ });
376
+
377
+ Handlebars.registerHelper('if_eq', function(a, b, opts) { return (a == b) ? opts.fn(this) : opts.inverse(this);});
378
+ Handlebars.registerHelper('if_neq', function(a, b, opts) { return (a != b) ? opts.fn(this) : opts.inverse(this);});
379
+ }
380
+
381
+ //Prevent notice boxes from flashing as its re-positioned in DOM
382
+ $('div.dup-wpnotice-box').show(300);
383
 
384
  });
385
 
386
  jQuery(document).ready(function($) {
387
  $('.duplicator-message .notice-dismiss, .duplicator-message .duplicator-notice-dismiss, .duplicator-message .duplicator-notice-rate-now').on('click', function (event) {
388
+ if ('button button-primary duplicator-notice-rate-now' !== $(event.target).attr('class')) {
389
+ event.preventDefault();
390
+ }
391
  $.post(ajaxurl, {
392
  action: 'duplicator_set_admin_notice_viewed',
393
  notice_id: $(this).closest('.duplicator-message-dismissed').data('notice_id'),
394
+ nonce: '<?php echo wp_create_nonce('duplicator_set_admin_notice_viewed'); ?>'
395
  });
396
  var $wrapperElm = $(this).closest('.duplicator-message-dismissed');
397
  $wrapperElm.fadeTo(100, 0, function () {
assets/js/jquery.qtip/index.php CHANGED
@@ -1,2 +1,3 @@
1
  <?php
2
- //silent
 
1
  <?php
2
+
3
+ //silent
assets/js/popper/popper.js ADDED
@@ -0,0 +1,1961 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * @popperjs/core v2.6.0 - MIT License
3
+ */
4
+
5
+ (function (global, factory) {
6
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
7
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
8
+ (global = global || self, factory(global.Popper = {}));
9
+ }(this, (function (exports) {
10
+ 'use strict';
11
+
12
+ function getBoundingClientRect(element)
13
+ {
14
+ var rect = element.getBoundingClientRect();
15
+ return {
16
+ width: rect.width,
17
+ height: rect.height,
18
+ top: rect.top,
19
+ right: rect.right,
20
+ bottom: rect.bottom,
21
+ left: rect.left,
22
+ x: rect.left,
23
+ y: rect.top
24
+ };
25
+ }
26
+
27
+ /*:: import type { Window } from '../types'; */
28
+
29
+ /*:: declare function getWindow(node: Node | Window): Window; */
30
+ function getWindow(node)
31
+ {
32
+ if (node.toString() !== '[object Window]') {
33
+ var ownerDocument = node.ownerDocument;
34
+ return ownerDocument ? ownerDocument.defaultView || window : window;
35
+ }
36
+
37
+ return node;
38
+ }
39
+
40
+ function getWindowScroll(node)
41
+ {
42
+ var win = getWindow(node);
43
+ var scrollLeft = win.pageXOffset;
44
+ var scrollTop = win.pageYOffset;
45
+ return {
46
+ scrollLeft: scrollLeft,
47
+ scrollTop: scrollTop
48
+ };
49
+ }
50
+
51
+ /*:: declare function isElement(node: mixed): boolean %checks(node instanceof
52
+ Element); */
53
+
54
+ function isElement(node)
55
+ {
56
+ var OwnElement = getWindow(node).Element;
57
+ return node instanceof OwnElement || node instanceof Element;
58
+ }
59
+ /*:: declare function isHTMLElement(node: mixed): boolean %checks(node instanceof
60
+ HTMLElement); */
61
+
62
+
63
+ function isHTMLElement(node)
64
+ {
65
+ var OwnElement = getWindow(node).HTMLElement;
66
+ return node instanceof OwnElement || node instanceof HTMLElement;
67
+ }
68
+ /*:: declare function isShadowRoot(node: mixed): boolean %checks(node instanceof
69
+ ShadowRoot); */
70
+
71
+
72
+ function isShadowRoot(node)
73
+ {
74
+ var OwnElement = getWindow(node).ShadowRoot;
75
+ return node instanceof OwnElement || node instanceof ShadowRoot;
76
+ }
77
+
78
+ function getHTMLElementScroll(element)
79
+ {
80
+ return {
81
+ scrollLeft: element.scrollLeft,
82
+ scrollTop: element.scrollTop
83
+ };
84
+ }
85
+
86
+ function getNodeScroll(node)
87
+ {
88
+ if (node === getWindow(node) || !isHTMLElement(node)) {
89
+ return getWindowScroll(node);
90
+ } else {
91
+ return getHTMLElementScroll(node);
92
+ }
93
+ }
94
+
95
+ function getNodeName(element)
96
+ {
97
+ return element ? (element.nodeName || '').toLowerCase() : null;
98
+ }
99
+
100
+ function getDocumentElement(element)
101
+ {
102
+ // $FlowFixMe[incompatible-return]: assume body is always available
103
+ return ((isElement(element) ? element.ownerDocument : // $FlowFixMe[prop-missing]
104
+ element.document) || window.document).documentElement;
105
+ }
106
+
107
+ function getWindowScrollBarX(element)
108
+ {
109
+ // If <html> has a CSS width greater than the viewport, then this will be
110
+ // incorrect for RTL.
111
+ // Popper 1 is broken in this case and never had a bug report so let's assume
112
+ // it's not an issue. I don't think anyone ever specifies width on <html>
113
+ // anyway.
114
+ // Browsers where the left scrollbar doesn't cause an issue report `0` for
115
+ // this (e.g. Edge 2019, IE11, Safari)
116
+ return getBoundingClientRect(getDocumentElement(element)).left + getWindowScroll(element).scrollLeft;
117
+ }
118
+
119
+ function getComputedStyle(element)
120
+ {
121
+ return getWindow(element).getComputedStyle(element);
122
+ }
123
+
124
+ function isScrollParent(element)
125
+ {
126
+ // Firefox wants us to check `-x` and `-y` variations as well
127
+ var _getComputedStyle = getComputedStyle(element),
128
+ overflow = _getComputedStyle.overflow,
129
+ overflowX = _getComputedStyle.overflowX,
130
+ overflowY = _getComputedStyle.overflowY;
131
+
132
+ return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX);
133
+ }
134
+
135
+ // Composite means it takes into account transforms as well as layout.
136
+
137
+ function getCompositeRect(elementOrVirtualElement, offsetParent, isFixed)
138
+ {
139
+ if (isFixed === void 0) {
140
+ isFixed = false;
141
+ }
142
+
143
+ var documentElement = getDocumentElement(offsetParent);
144
+ var rect = getBoundingClientRect(elementOrVirtualElement);
145
+ var isOffsetParentAnElement = isHTMLElement(offsetParent);
146
+ var scroll = {
147
+ scrollLeft: 0,
148
+ scrollTop: 0
149
+ };
150
+ var offsets = {
151
+ x: 0,
152
+ y: 0
153
+ };
154
+
155
+ if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {
156
+ if (getNodeName(offsetParent) !== 'body' || // https://github.com/popperjs/popper-core/issues/1078
157
+ isScrollParent(documentElement)) {
158
+ scroll = getNodeScroll(offsetParent);
159
+ }
160
+
161
+ if (isHTMLElement(offsetParent)) {
162
+ offsets = getBoundingClientRect(offsetParent);
163
+ offsets.x += offsetParent.clientLeft;
164
+ offsets.y += offsetParent.clientTop;
165
+ } else if (documentElement) {
166
+ offsets.x = getWindowScrollBarX(documentElement);
167
+ }
168
+ }
169
+
170
+ return {
171
+ x: rect.left + scroll.scrollLeft - offsets.x,
172
+ y: rect.top + scroll.scrollTop - offsets.y,
173
+ width: rect.width,
174
+ height: rect.height
175
+ };
176
+ }
177
+
178
+ // Returns the layout rect of an element relative to its offsetParent. Layout
179
+ // means it doesn't take into account transforms.
180
+ function getLayoutRect(element)
181
+ {
182
+ return {
183
+ x: element.offsetLeft,
184
+ y: element.offsetTop,
185
+ width: element.offsetWidth,
186
+ height: element.offsetHeight
187
+ };
188
+ }
189
+
190
+ function getParentNode(element)
191
+ {
192
+ if (getNodeName(element) === 'html') {
193
+ return element;
194
+ }
195
+
196
+ return ( // this is a quicker (but less type safe) way to save quite some bytes from the bundle
197
+ // $FlowFixMe[incompatible-return]
198
+ // $FlowFixMe[prop-missing]
199
+ element.assignedSlot || // step into the shadow DOM of the parent of a slotted node
200
+ element.parentNode || // DOM Element detected
201
+ // $FlowFixMe[incompatible-return]: need a better way to handle this...
202
+ element.host || // ShadowRoot detected
203
+ // $FlowFixMe[incompatible-call]: HTMLElement is a Node
204
+ getDocumentElement(element) // fallback
205
+
206
+ );
207
+ }
208
+
209
+ function getScrollParent(node)
210
+ {
211
+ if (['html', 'body', '#document'].indexOf(getNodeName(node)) >= 0) {
212
+ // $FlowFixMe[incompatible-return]: assume body is always available
213
+ return node.ownerDocument.body;
214
+ }
215
+
216
+ if (isHTMLElement(node) && isScrollParent(node)) {
217
+ return node;
218
+ }
219
+
220
+ return getScrollParent(getParentNode(node));
221
+ }
222
+
223
+ /*
224
+ given a DOM element, return the list of all scroll parents, up the list of ancesors
225
+ until we get to the top window object. This list is what we attach scroll listeners
226
+ to, because if any of these parent elements scroll, we'll need to re-calculate the
227
+ reference element's position.
228
+ */
229
+
230
+ function listScrollParents(element, list)
231
+ {
232
+ if (list === void 0) {
233
+ list = [];
234
+ }
235
+
236
+ var scrollParent = getScrollParent(element);
237
+ var isBody = getNodeName(scrollParent) === 'body';
238
+ var win = getWindow(scrollParent);
239
+ var target = isBody ? [win].concat(win.visualViewport || [], isScrollParent(scrollParent) ? scrollParent : []) : scrollParent;
240
+ var updatedList = list.concat(target);
241
+ return isBody ? updatedList : // $FlowFixMe[incompatible-call]: isBody tells us target will be an HTMLElement here
242
+ updatedList.concat(listScrollParents(getParentNode(target)));
243
+ }
244
+
245
+ function isTableElement(element)
246
+ {
247
+ return ['table', 'td', 'th'].indexOf(getNodeName(element)) >= 0;
248
+ }
249
+
250
+ function getTrueOffsetParent(element)
251
+ {
252
+ if (!isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837
253
+ getComputedStyle(element).position === 'fixed') {
254
+ return null;
255
+ }
256
+
257
+ var offsetParent = element.offsetParent;
258
+
259
+ if (offsetParent) {
260
+ var html = getDocumentElement(offsetParent);
261
+
262
+ if (getNodeName(offsetParent) === 'body' && getComputedStyle(offsetParent).position === 'static' && getComputedStyle(html).position !== 'static') {
263
+ return html;
264
+ }
265
+ }
266
+
267
+ return offsetParent;
268
+ } // `.offsetParent` reports `null` for fixed elements, while absolute elements
269
+ // return the containing block
270
+
271
+
272
+ function getContainingBlock(element)
273
+ {
274
+ var currentNode = getParentNode(element);
275
+
276
+ while (isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0) {
277
+ var css = getComputedStyle(currentNode); // This is non-exhaustive but covers the most common CSS properties that
278
+ // create a containing block.
279
+
280
+ if (css.transform !== 'none' || css.perspective !== 'none' || css.willChange && css.willChange !== 'auto') {
281
+ return currentNode;
282
+ } else {
283
+ currentNode = currentNode.parentNode;
284
+ }
285
+ }
286
+
287
+ return null;
288
+ } // Gets the closest ancestor positioned element. Handles some edge cases,
289
+ // such as table ancestors and cross browser bugs.
290
+
291
+
292
+ function getOffsetParent(element)
293
+ {
294
+ var window = getWindow(element);
295
+ var offsetParent = getTrueOffsetParent(element);
296
+
297
+ while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === 'static') {
298
+ offsetParent = getTrueOffsetParent(offsetParent);
299
+ }
300
+
301
+ if (offsetParent && getNodeName(offsetParent) === 'body' && getComputedStyle(offsetParent).position === 'static') {
302
+ return window;
303
+ }
304
+
305
+ return offsetParent || getContainingBlock(element) || window;
306
+ }
307
+
308
+ var top = 'top';
309
+ var bottom = 'bottom';
310
+ var right = 'right';
311
+ var left = 'left';
312
+ var auto = 'auto';
313
+ var basePlacements = [top, bottom, right, left];
314
+ var start = 'start';
315
+ var end = 'end';
316
+ var clippingParents = 'clippingParents';
317
+ var viewport = 'viewport';
318
+ var popper = 'popper';
319
+ var reference = 'reference';
320
+ var variationPlacements = /*#__PURE__*/ basePlacements.reduce(function (acc, placement) {
321
+ return acc.concat([placement + "-" + start, placement + "-" + end]);
322
+ }, []);
323
+ var placements = /*#__PURE__*/ [].concat(basePlacements, [auto]).reduce(function (acc, placement) {
324
+ return acc.concat([placement, placement + "-" + start, placement + "-" + end]);
325
+ }, []); // modifiers that need to read the DOM
326
+
327
+ var beforeRead = 'beforeRead';
328
+ var read = 'read';
329
+ var afterRead = 'afterRead'; // pure-logic modifiers
330
+
331
+ var beforeMain = 'beforeMain';
332
+ var main = 'main';
333
+ var afterMain = 'afterMain'; // modifier with the purpose to write to the DOM (or write into a framework state)
334
+
335
+ var beforeWrite = 'beforeWrite';
336
+ var write = 'write';
337
+ var afterWrite = 'afterWrite';
338
+ var modifierPhases = [beforeRead, read, afterRead, beforeMain, main, afterMain, beforeWrite, write, afterWrite];
339
+
340
+ function order(modifiers)
341
+ {
342
+ var map = new Map();
343
+ var visited = new Set();
344
+ var result = [];
345
+ modifiers.forEach(function (modifier) {
346
+ map.set(modifier.name, modifier);
347
+ }); // On visiting object, check for its dependencies and visit them recursively
348
+
349
+ function sort(modifier)
350
+ {
351
+ visited.add(modifier.name);
352
+ var requires = [].concat(modifier.requires || [], modifier.requiresIfExists || []);
353
+ requires.forEach(function (dep) {
354
+ if (!visited.has(dep)) {
355
+ var depModifier = map.get(dep);
356
+
357
+ if (depModifier) {
358
+ sort(depModifier);
359
+ }
360
+ }
361
+ });
362
+ result.push(modifier);
363
+ }
364
+
365
+ modifiers.forEach(function (modifier) {
366
+ if (!visited.has(modifier.name)) {
367
+ // check for visited object
368
+ sort(modifier);
369
+ }
370
+ });
371
+ return result;
372
+ }
373
+
374
+ function orderModifiers(modifiers)
375
+ {
376
+ // order based on dependencies
377
+ var orderedModifiers = order(modifiers); // order based on phase
378
+
379
+ return modifierPhases.reduce(function (acc, phase) {
380
+ return acc.concat(orderedModifiers.filter(function (modifier) {
381
+ return modifier.phase === phase;
382
+ }));
383
+ }, []);
384
+ }
385
+
386
+ function debounce(fn)
387
+ {
388
+ var pending;
389
+ return function () {
390
+ if (!pending) {
391
+ pending = new Promise(function (resolve) {
392
+ Promise.resolve().then(function () {
393
+ pending = undefined;
394
+ resolve(fn());
395
+ });
396
+ });
397
+ }
398
+
399
+ return pending;
400
+ };
401
+ }
402
+
403
+ function format(str)
404
+ {
405
+ for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
406
+ args[_key - 1] = arguments[_key];
407
+ }
408
+
409
+ return [].concat(args).reduce(function (p, c) {
410
+ return p.replace(/%s/, c);
411
+ }, str);
412
+ }
413
+
414
+ var INVALID_MODIFIER_ERROR = 'Popper: modifier "%s" provided an invalid %s property, expected %s but got %s';
415
+ var MISSING_DEPENDENCY_ERROR = 'Popper: modifier "%s" requires "%s", but "%s" modifier is not available';
416
+ var VALID_PROPERTIES = ['name', 'enabled', 'phase', 'fn', 'effect', 'requires', 'options'];
417
+
418
+ function validateModifiers(modifiers)
419
+ {
420
+ modifiers.forEach(function (modifier) {
421
+ Object.keys(modifier).forEach(function (key) {
422
+ switch (key) {
423
+ case 'name':
424
+ if (typeof modifier.name !== 'string') {
425
+ console.error(format(INVALID_MODIFIER_ERROR, String(modifier.name), '"name"', '"string"', "\"" + String(modifier.name) + "\""));
426
+ }
427
+
428
+ break;
429
+
430
+ case 'enabled':
431
+ if (typeof modifier.enabled !== 'boolean') {
432
+ console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"enabled"', '"boolean"', "\"" + String(modifier.enabled) + "\""));
433
+ }
434
+
435
+ case 'phase':
436
+ if (modifierPhases.indexOf(modifier.phase) < 0) {
437
+ console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"phase"', "either " + modifierPhases.join(', '), "\"" + String(modifier.phase) + "\""));
438
+ }
439
+
440
+ break;
441
+
442
+ case 'fn':
443
+ if (typeof modifier.fn !== 'function') {
444
+ console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"fn"', '"function"', "\"" + String(modifier.fn) + "\""));
445
+ }
446
+
447
+ break;
448
+
449
+ case 'effect':
450
+ if (typeof modifier.effect !== 'function') {
451
+ console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"effect"', '"function"', "\"" + String(modifier.fn) + "\""));
452
+ }
453
+
454
+ break;
455
+
456
+ case 'requires':
457
+ if (!Array.isArray(modifier.requires)) {
458
+ console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"requires"', '"array"', "\"" + String(modifier.requires) + "\""));
459
+ }
460
+
461
+ break;
462
+
463
+ case 'requiresIfExists':
464
+ if (!Array.isArray(modifier.requiresIfExists)) {
465
+ console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"requiresIfExists"', '"array"', "\"" + String(modifier.requiresIfExists) + "\""));
466
+ }
467
+
468
+ break;
469
+
470
+ case 'options':
471
+ case 'data':
472
+ break;
473
+
474
+ default:
475
+ console.error("PopperJS: an invalid property has been provided to the \"" + modifier.name + "\" modifier, valid properties are " + VALID_PROPERTIES.map(function (s) {
476
+ return "\"" + s + "\"";
477
+ }).join(', ') + "; but \"" + key + "\" was provided.");
478
+ }
479
+
480
+ modifier.requires && modifier.requires.forEach(function (requirement) {
481
+ if (modifiers.find(function (mod) {
482
+ return mod.name === requirement;
483
+ }) == null) {
484
+ console.error(format(MISSING_DEPENDENCY_ERROR, String(modifier.name), requirement, requirement));
485
+ }
486
+ });
487
+ });
488
+ });
489
+ }
490
+
491
+ function uniqueBy(arr, fn)
492
+ {
493
+ var identifiers = new Set();
494
+ return arr.filter(function (item) {
495
+ var identifier = fn(item);
496
+
497
+ if (!identifiers.has(identifier)) {
498
+ identifiers.add(identifier);
499
+ return true;
500
+ }
501
+ });
502
+ }
503
+
504
+ function getBasePlacement(placement)
505
+ {
506
+ return placement.split('-')[0];
507
+ }
508
+
509
+ function mergeByName(modifiers)
510
+ {
511
+ var merged = modifiers.reduce(function (merged, current) {
512
+ var existing = merged[current.name];
513
+ merged[current.name] = existing ? Object.assign(Object.assign(Object.assign({}, existing), current), {}, {
514
+ options: Object.assign(Object.assign({}, existing.options), current.options),
515
+ data: Object.assign(Object.assign({}, existing.data), current.data)
516
+ }) : current;
517
+ return merged;
518
+ }, {}); // IE11 does not support Object.values
519
+
520
+ return Object.keys(merged).map(function (key) {
521
+ return merged[key];
522
+ });
523
+ }
524
+
525
+ function getViewportRect(element)
526
+ {
527
+ var win = getWindow(element);
528
+ var html = getDocumentElement(element);
529
+ var visualViewport = win.visualViewport;
530
+ var width = html.clientWidth;
531
+ var height = html.clientHeight;
532
+ var x = 0;
533
+ var y = 0; // NB: This isn't supported on iOS <= 12. If the keyboard is open, the popper
534
+ // can be obscured underneath it.
535
+ // Also, `html.clientHeight` adds the bottom bar height in Safari iOS, even
536
+ // if it isn't open, so if this isn't available, the popper will be detected
537
+ // to overflow the bottom of the screen too early.
538
+
539
+ if (visualViewport) {
540
+ width = visualViewport.width;
541
+ height = visualViewport.height; // Uses Layout Viewport (like Chrome; Safari does not currently)
542
+ // In Chrome, it returns a value very close to 0 (+/-) but contains rounding
543
+ // errors due to floating point numbers, so we need to check precision.
544
+ // Safari returns a number <= 0, usually < -1 when pinch-zoomed
545
+ // Feature detection fails in mobile emulation mode in Chrome.
546
+ // Math.abs(win.innerWidth / visualViewport.scale - visualViewport.width) <
547
+ // 0.001
548
+ // Fallback here: "Not Safari" userAgent
549
+
550
+ if (!/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {
551
+ x = visualViewport.offsetLeft;
552
+ y = visualViewport.offsetTop;
553
+ }
554
+ }
555
+
556
+ return {
557
+ width: width,
558
+ height: height,
559
+ x: x + getWindowScrollBarX(element),
560
+ y: y
561
+ };
562
+ }
563
+
564
+ // of the `<html>` and `<body>` rect bounds if horizontally scrollable
565
+
566
+ function getDocumentRect(element)
567
+ {
568
+ var html = getDocumentElement(element);
569
+ var winScroll = getWindowScroll(element);
570
+ var body = element.ownerDocument.body;
571
+ var width = Math.max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0);
572
+ var height = Math.max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0);
573
+ var x = -winScroll.scrollLeft + getWindowScrollBarX(element);
574
+ var y = -winScroll.scrollTop;
575
+
576
+ if (getComputedStyle(body || html).direction === 'rtl') {
577
+ x += Math.max(html.clientWidth, body ? body.clientWidth : 0) - width;
578
+ }
579
+
580
+ return {
581
+ width: width,
582
+ height: height,
583
+ x: x,
584
+ y: y
585
+ };
586
+ }
587
+
588
+ function contains(parent, child)
589
+ {
590
+ var rootNode = child.getRootNode && child.getRootNode(); // First, attempt with faster native method
591
+ // then fallback to custom implementation with Shadow DOM support
592
+ if (parent.contains(child)) {
593
+ return true;
594
+ } else if (rootNode && isShadowRoot(rootNode)) {
595
+ var next = child;
596
+
597
+ do {
598
+ if (next && parent.isSameNode(next)) {
599
+ return true;
600
+ } // $FlowFixMe[prop-missing]: need a better way to handle this...
601
+
602
+
603
+ next = next.parentNode || next.host;
604
+ } while (next);
605
+ } // Give up, the result is false
606
+
607
+
608
+ return false;
609
+ }
610
+
611
+ function rectToClientRect(rect)
612
+ {
613
+ return Object.assign(Object.assign({}, rect), {}, {
614
+ left: rect.x,
615
+ top: rect.y,
616
+ right: rect.x + rect.width,
617
+ bottom: rect.y + rect.height
618
+ });
619
+ }
620
+
621
+ function getInnerBoundingClientRect(element)
622
+ {
623
+ var rect = getBoundingClientRect(element);
624
+ rect.top = rect.top + element.clientTop;
625
+ rect.left = rect.left + element.clientLeft;
626
+ rect.bottom = rect.top + element.clientHeight;
627
+ rect.right = rect.left + element.clientWidth;
628
+ rect.width = element.clientWidth;
629
+ rect.height = element.clientHeight;
630
+ rect.x = rect.left;
631
+ rect.y = rect.top;
632
+ return rect;
633
+ }
634
+
635
+ function getClientRectFromMixedType(element, clippingParent)
636
+ {
637
+ return clippingParent === viewport ? rectToClientRect(getViewportRect(element)) : isHTMLElement(clippingParent) ? getInnerBoundingClientRect(clippingParent) : rectToClientRect(getDocumentRect(getDocumentElement(element)));
638
+ } // A "clipping parent" is an overflowable container with the characteristic of
639
+ // clipping (or hiding) overflowing elements with a position different from
640
+ // `initial`
641
+
642
+
643
+ function getClippingParents(element)
644
+ {
645
+ var clippingParents = listScrollParents(getParentNode(element));
646
+ var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle(element).position) >= 0;
647
+ var clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element;
648
+
649
+ if (!isElement(clipperElement)) {
650
+ return [];
651
+ } // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414
652
+
653
+
654
+ return clippingParents.filter(function (clippingParent) {
655
+ return isElement(clippingParent) && contains(clippingParent, clipperElement) && getNodeName(clippingParent) !== 'body';
656
+ });
657
+ } // Gets the maximum area that the element is visible in due to any number of
658
+ // clipping parents
659
+
660
+
661
+ function getClippingRect(element, boundary, rootBoundary)
662
+ {
663
+ var mainClippingParents = boundary === 'clippingParents' ? getClippingParents(element) : [].concat(boundary);
664
+ var clippingParents = [].concat(mainClippingParents, [rootBoundary]);
665
+ var firstClippingParent = clippingParents[0];
666
+ var clippingRect = clippingParents.reduce(function (accRect, clippingParent) {
667
+ var rect = getClientRectFromMixedType(element, clippingParent);
668
+ accRect.top = Math.max(rect.top, accRect.top);
669
+ accRect.right = Math.min(rect.right, accRect.right);
670
+ accRect.bottom = Math.min(rect.bottom, accRect.bottom);
671
+ accRect.left = Math.max(rect.left, accRect.left);
672
+ return accRect;
673
+ }, getClientRectFromMixedType(element, firstClippingParent));
674
+ clippingRect.width = clippingRect.right - clippingRect.left;
675
+ clippingRect.height = clippingRect.bottom - clippingRect.top;
676
+ clippingRect.x = clippingRect.left;
677
+ clippingRect.y = clippingRect.top;
678
+ return clippingRect;
679
+ }
680
+
681
+ function getVariation(placement)
682
+ {
683
+ return placement.split('-')[1];
684
+ }
685
+
686
+ function getMainAxisFromPlacement(placement)
687
+ {
688
+ return ['top', 'bottom'].indexOf(placement) >= 0 ? 'x' : 'y';
689
+ }
690
+
691
+ function computeOffsets(_ref)
692
+ {
693
+ var reference = _ref.reference,
694
+ element = _ref.element,
695
+ placement = _ref.placement;
696
+ var basePlacement = placement ? getBasePlacement(placement) : null;
697
+ var variation = placement ? getVariation(placement) : null;
698
+ var commonX = reference.x + reference.width / 2 - element.width / 2;
699
+ var commonY = reference.y + reference.height / 2 - element.height / 2;
700
+ var offsets;
701
+
702
+ switch (basePlacement) {
703
+ case top:
704
+ offsets = {
705
+ x: commonX,
706
+ y: reference.y - element.height
707
+ };
708
+ break;
709
+
710
+ case bottom:
711
+ offsets = {
712
+ x: commonX,
713
+ y: reference.y + reference.height
714
+ };
715
+ break;
716
+
717
+ case right:
718
+ offsets = {
719
+ x: reference.x + reference.width,
720
+ y: commonY
721
+ };
722
+ break;
723
+
724
+ case left:
725
+ offsets = {
726
+ x: reference.x - element.width,
727
+ y: commonY
728
+ };
729
+ break;
730
+
731
+ default:
732
+ offsets = {
733
+ x: reference.x,
734
+ y: reference.y
735
+ };
736
+ }
737
+
738
+ var mainAxis = basePlacement ? getMainAxisFromPlacement(basePlacement) : null;
739
+
740
+ if (mainAxis != null) {
741
+ var len = mainAxis === 'y' ? 'height' : 'width';
742
+
743
+ switch (variation) {
744
+ case start:
745
+ offsets[mainAxis] = offsets[mainAxis] - (reference[len] / 2 - element[len] / 2);
746
+ break;
747
+
748
+ case end:
749
+ offsets[mainAxis] = offsets[mainAxis] + (reference[len] / 2 - element[len] / 2);
750
+ break;
751
+ }
752
+ }
753
+
754
+ return offsets;
755
+ }
756
+
757
+ function getFreshSideObject()
758
+ {
759
+ return {
760
+ top: 0,
761
+ right: 0,
762
+ bottom: 0,
763
+ left: 0
764
+ };
765
+ }
766
+
767
+ function mergePaddingObject(paddingObject)
768
+ {
769
+ return Object.assign(Object.assign({}, getFreshSideObject()), paddingObject);
770
+ }
771
+
772
+ function expandToHashMap(value, keys)
773
+ {
774
+ return keys.reduce(function (hashMap, key) {
775
+ hashMap[key] = value;
776
+ return hashMap;
777
+ }, {});
778
+ }
779
+
780
+ function detectOverflow(state, options)
781
+ {
782
+ if (options === void 0) {
783
+ options = {};
784
+ }
785
+
786
+ var _options = options,
787
+ _options$placement = _options.placement,
788
+ placement = _options$placement === void 0 ? state.placement : _options$placement,
789
+ _options$boundary = _options.boundary,
790
+ boundary = _options$boundary === void 0 ? clippingParents : _options$boundary,
791
+ _options$rootBoundary = _options.rootBoundary,
792
+ rootBoundary = _options$rootBoundary === void 0 ? viewport : _options$rootBoundary,
793
+ _options$elementConte = _options.elementContext,
794
+ elementContext = _options$elementConte === void 0 ? popper : _options$elementConte,
795
+ _options$altBoundary = _options.altBoundary,
796
+ altBoundary = _options$altBoundary === void 0 ? false : _options$altBoundary,
797
+ _options$padding = _options.padding,
798
+ padding = _options$padding === void 0 ? 0 : _options$padding;
799
+ var paddingObject = mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));
800
+ var altContext = elementContext === popper ? reference : popper;
801
+ var referenceElement = state.elements.reference;
802
+ var popperRect = state.rects.popper;
803
+ var element = state.elements[altBoundary ? altContext : elementContext];
804
+ var clippingClientRect = getClippingRect(isElement(element) ? element : element.contextElement || getDocumentElement(state.elements.popper), boundary, rootBoundary);
805
+ var referenceClientRect = getBoundingClientRect(referenceElement);
806
+ var popperOffsets = computeOffsets({
807
+ reference: referenceClientRect,
808
+ element: popperRect,
809
+ strategy: 'absolute',
810
+ placement: placement
811
+ });
812
+ var popperClientRect = rectToClientRect(Object.assign(Object.assign({}, popperRect), popperOffsets));
813
+ var elementClientRect = elementContext === popper ? popperClientRect : referenceClientRect; // positive = overflowing the clipping rect
814
+ // 0 or negative = within the clipping rect
815
+
816
+ var overflowOffsets = {
817
+ top: clippingClientRect.top - elementClientRect.top + paddingObject.top,
818
+ bottom: elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom,
819
+ left: clippingClientRect.left - elementClientRect.left + paddingObject.left,
820
+ right: elementClientRect.right - clippingClientRect.right + paddingObject.right
821
+ };
822
+ var offsetData = state.modifiersData.offset; // Offsets can be applied only to the popper element
823
+
824
+ if (elementContext === popper && offsetData) {
825
+ var offset = offsetData[placement];
826
+ Object.keys(overflowOffsets).forEach(function (key) {
827
+ var multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1;
828
+ var axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x';
829
+ overflowOffsets[key] += offset[axis] * multiply;
830
+ });
831
+ }
832
+
833
+ return overflowOffsets;
834
+ }
835
+
836
+ var INVALID_ELEMENT_ERROR = 'Popper: Invalid reference or popper argument provided. They must be either a DOM element or virtual element.';
837
+ var INFINITE_LOOP_ERROR = 'Popper: An infinite loop in the modifiers cycle has been detected! The cycle has been interrupted to prevent a browser crash.';
838
+ var DEFAULT_OPTIONS = {
839
+ placement: 'bottom',
840
+ modifiers: [],
841
+ strategy: 'absolute'
842
+ };
843
+
844
+ function areValidElements()
845
+ {
846
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
847
+ args[_key] = arguments[_key];
848
+ }
849
+
850
+ return !args.some(function (element) {
851
+ return !(element && typeof element.getBoundingClientRect === 'function');
852
+ });
853
+ }
854
+
855
+ function popperGenerator(generatorOptions)
856
+ {
857
+ if (generatorOptions === void 0) {
858
+ generatorOptions = {};
859
+ }
860
+
861
+ var _generatorOptions = generatorOptions,
862
+ _generatorOptions$def = _generatorOptions.defaultModifiers,
863
+ defaultModifiers = _generatorOptions$def === void 0 ? [] : _generatorOptions$def,
864
+ _generatorOptions$def2 = _generatorOptions.defaultOptions,
865
+ defaultOptions = _generatorOptions$def2 === void 0 ? DEFAULT_OPTIONS : _generatorOptions$def2;
866
+ return function createPopper(reference, popper, options)
867
+ {
868
+ if (options === void 0) {
869
+ options = defaultOptions;
870
+ }
871
+
872
+ var state = {
873
+ placement: 'bottom',
874
+ orderedModifiers: [],
875
+ options: Object.assign(Object.assign({}, DEFAULT_OPTIONS), defaultOptions),
876
+ modifiersData: {},
877
+ elements: {
878
+ reference: reference,
879
+ popper: popper
880
+ },
881
+ attributes: {},
882
+ styles: {}
883
+ };
884
+ var effectCleanupFns = [];
885
+ var isDestroyed = false;
886
+ var instance = {
887
+ state: state,
888
+ setOptions: function setOptions(options)
889
+ {
890
+ cleanupModifierEffects();
891
+ state.options = Object.assign(Object.assign(Object.assign({}, defaultOptions), state.options), options);
892
+ state.scrollParents = {
893
+ reference: isElement(reference) ? listScrollParents(reference) : reference.contextElement ? listScrollParents(reference.contextElement) : [],
894
+ popper: listScrollParents(popper)
895
+ }; // Orders the modifiers based on their dependencies and `phase`
896
+ // properties
897
+
898
+ var orderedModifiers = orderModifiers(mergeByName([].concat(defaultModifiers, state.options.modifiers))); // Strip out disabled modifiers
899
+
900
+ state.orderedModifiers = orderedModifiers.filter(function (m) {
901
+ return m.enabled;
902
+ }); // Validate the provided modifiers so that the consumer will get warned
903
+ // if one of the modifiers is invalid for any reason
904
+
905
+ {
906
+ var modifiers = uniqueBy([].concat(orderedModifiers, state.options.modifiers), function (_ref) {
907
+ var name = _ref.name;
908
+ return name;
909
+ });
910
+ validateModifiers(modifiers);
911
+
912
+ if (getBasePlacement(state.options.placement) === auto) {
913
+ var flipModifier = state.orderedModifiers.find(function (_ref2) {
914
+ var name = _ref2.name;
915
+ return name === 'flip';
916
+ });
917
+
918
+ if (!flipModifier) {
919
+ console.error(['Popper: "auto" placements require the "flip" modifier be', 'present and enabled to work.'].join(' '));
920
+ }
921
+ }
922
+
923
+ var _getComputedStyle = getComputedStyle(popper),
924
+ marginTop = _getComputedStyle.marginTop,
925
+ marginRight = _getComputedStyle.marginRight,
926
+ marginBottom = _getComputedStyle.marginBottom,
927
+ marginLeft = _getComputedStyle.marginLeft; // We no longer take into account `margins` on the popper, and it can
928
+ // cause bugs with positioning, so we'll warn the consumer
929
+
930
+
931
+ if ([marginTop, marginRight, marginBottom, marginLeft].some(function (margin) {
932
+ return parseFloat(margin);
933
+ })) {
934
+ console.warn(['Popper: CSS "margin" styles cannot be used to apply padding', 'between the popper and its reference element or boundary.', 'To replicate margin, use the `offset` modifier, as well as', 'the `padding` option in the `preventOverflow` and `flip`', 'modifiers.'].join(' '));
935
+ }
936
+ }
937
+
938
+ runModifierEffects();
939
+ return instance.update();
940
+ },
941
+ // Sync update – it will always be executed, even if not necessary. This
942
+ // is useful for low frequency updates where sync behavior simplifies the
943
+ // logic.
944
+ // For high frequency updates (e.g. `resize` and `scroll` events), always
945
+ // prefer the async Popper#update method
946
+ forceUpdate: function forceUpdate()
947
+ {
948
+ if (isDestroyed) {
949
+ return;
950
+ }
951
+
952
+ var _state$elements = state.elements,
953
+ reference = _state$elements.reference,
954
+ popper = _state$elements.popper; // Don't proceed if `reference` or `popper` are not valid elements
955
+ // anymore
956
+
957
+ if (!areValidElements(reference, popper)) {
958
+ {
959
+ console.error(INVALID_ELEMENT_ERROR);
960
+ }
961
+
962
+ return;
963
+ } // Store the reference and popper rects to be read by modifiers
964
+
965
+
966
+ state.rects = {
967
+ reference: getCompositeRect(reference, getOffsetParent(popper), state.options.strategy === 'fixed'),
968
+ popper: getLayoutRect(popper)
969
+ }; // Modifiers have the ability to reset the current update cycle. The
970
+ // most common use case for this is the `flip` modifier changing the
971
+ // placement, which then needs to re-run all the modifiers, because the
972
+ // logic was previously ran for the previous placement and is therefore
973
+ // stale/incorrect
974
+
975
+ state.reset = false;
976
+ state.placement = state.options.placement; // On each update cycle, the `modifiersData` property for each modifier
977
+ // is filled with the initial data specified by the modifier. This means
978
+ // it doesn't persist and is fresh on each update.
979
+ // To ensure persistent data, use `${name}#persistent`
980
+
981
+ state.orderedModifiers.forEach(function (modifier) {
982
+ return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);
983
+ });
984
+ var __debug_loops__ = 0;
985
+
986
+ for (var index = 0; index < state.orderedModifiers.length; index++) {
987
+ {
988
+ __debug_loops__ += 1;
989
+
990
+ if (__debug_loops__ > 100) {
991
+ console.error(INFINITE_LOOP_ERROR);
992
+ break;
993
+ }
994
+ }
995
+
996
+ if (state.reset === true) {
997
+ state.reset = false;
998
+ index = -1;
999
+ continue;
1000
+ }
1001
+
1002
+ var _state$orderedModifie = state.orderedModifiers[index],
1003
+ fn = _state$orderedModifie.fn,
1004
+ _state$orderedModifie2 = _state$orderedModifie.options,
1005
+ _options = _state$orderedModifie2 === void 0 ? {} : _state$orderedModifie2,
1006
+ name = _state$orderedModifie.name;
1007
+
1008
+ if (typeof fn === 'function') {
1009
+ state = fn({
1010
+ state: state,
1011
+ options: _options,
1012
+ name: name,
1013
+ instance: instance
1014
+ }) || state;
1015
+ }
1016
+ }
1017
+ },
1018
+ // Async and optimistically optimized update – it will not be executed if
1019
+ // not necessary (debounced to run at most once-per-tick)
1020
+ update: debounce(function () {
1021
+ return new Promise(function (resolve) {
1022
+ instance.forceUpdate();
1023
+ resolve(state);
1024
+ });
1025
+ }),
1026
+ destroy: function destroy()
1027
+ {
1028
+ cleanupModifierEffects();
1029
+ isDestroyed = true;
1030
+ }
1031
+ };
1032
+
1033
+ if (!areValidElements(reference, popper)) {
1034
+ {
1035
+ console.error(INVALID_ELEMENT_ERROR);
1036
+ }
1037
+
1038
+ return instance;
1039
+ }
1040
+
1041
+ instance.setOptions(options).then(function (state) {
1042
+ if (!isDestroyed && options.onFirstUpdate) {
1043
+ options.onFirstUpdate(state);
1044
+ }
1045
+ }); // Modifiers have the ability to execute arbitrary code before the first
1046
+ // update cycle runs. They will be executed in the same order as the update
1047
+ // cycle. This is useful when a modifier adds some persistent data that
1048
+ // other modifiers need to use, but the modifier is run after the dependent
1049
+ // one.
1050
+
1051
+ function runModifierEffects()
1052
+ {
1053
+ state.orderedModifiers.forEach(function (_ref3) {
1054
+ var name = _ref3.name,
1055
+ _ref3$options = _ref3.options,
1056
+ options = _ref3$options === void 0 ? {} : _ref3$options,
1057
+ effect = _ref3.effect;
1058
+
1059
+ if (typeof effect === 'function') {
1060
+ var cleanupFn = effect({
1061
+ state: state,
1062
+ name: name,
1063
+ instance: instance,
1064
+ options: options
1065
+ });
1066
+
1067
+ var noopFn = function noopFn()
1068
+ {};
1069
+
1070
+ effectCleanupFns.push(cleanupFn || noopFn);
1071
+ }
1072
+ });
1073
+ }
1074
+
1075
+ function cleanupModifierEffects()
1076
+ {
1077
+ effectCleanupFns.forEach(function (fn) {
1078
+ return fn();
1079
+ });
1080
+ effectCleanupFns = [];
1081
+ }
1082
+
1083
+ return instance;
1084
+ };
1085
+ }
1086
+
1087
+ var passive = {
1088
+ passive: true
1089
+ };
1090
+
1091
+ function effect(_ref)
1092
+ {
1093
+ var state = _ref.state,
1094
+ instance = _ref.instance,
1095
+ options = _ref.options;
1096
+ var _options$scroll = options.scroll,
1097
+ scroll = _options$scroll === void 0 ? true : _options$scroll,
1098
+ _options$resize = options.resize,
1099
+ resize = _options$resize === void 0 ? true : _options$resize;
1100
+ var window = getWindow(state.elements.popper);
1101
+ var scrollParents = [].concat(state.scrollParents.reference, state.scrollParents.popper);
1102
+
1103
+ if (scroll) {
1104
+ scrollParents.forEach(function (scrollParent) {
1105
+ scrollParent.addEventListener('scroll', instance.update, passive);
1106
+ });
1107
+ }
1108
+
1109
+ if (resize) {
1110
+ window.addEventListener('resize', instance.update, passive);
1111
+ }
1112
+
1113
+ return function () {
1114
+ if (scroll) {
1115
+ scrollParents.forEach(function (scrollParent) {
1116
+ scrollParent.removeEventListener('scroll', instance.update, passive);
1117
+ });
1118
+ }
1119
+
1120
+ if (resize) {
1121
+ window.removeEventListener('resize', instance.update, passive);
1122
+ }
1123
+ };
1124
+ } // eslint-disable-next-line import/no-unused-modules
1125
+
1126
+
1127
+ var eventListeners = {
1128
+ name: 'eventListeners',
1129
+ enabled: true,
1130
+ phase: 'write',
1131
+ fn: function fn()
1132
+ {},
1133
+ effect: effect,
1134
+ data: {}
1135
+ };
1136
+
1137
+ function popperOffsets(_ref)
1138
+ {
1139
+ var state = _ref.state,
1140
+ name = _ref.name;
1141
+ // Offsets are the actual position the popper needs to have to be
1142
+ // properly positioned near its reference element
1143
+ // This is the most basic placement, and will be adjusted by
1144
+ // the modifiers in the next step
1145
+ state.modifiersData[name] = computeOffsets({
1146
+ reference: state.rects.reference,
1147
+ element: state.rects.popper,
1148
+ strategy: 'absolute',
1149
+ placement: state.placement
1150
+ });
1151
+ } // eslint-disable-next-line import/no-unused-modules
1152
+
1153
+
1154
+ var popperOffsets$1 = {
1155
+ name: 'popperOffsets',
1156
+ enabled: true,
1157
+ phase: 'read',
1158
+ fn: popperOffsets,
1159
+ data: {}
1160
+ };
1161
+
1162
+ var unsetSides = {
1163
+ top: 'auto',
1164
+ right: 'auto',
1165
+ bottom: 'auto',
1166
+ left: 'auto'
1167
+ }; // Round the offsets to the nearest suitable subpixel based on the DPR.
1168
+ // Zooming can change the DPR, but it seems to report a value that will
1169
+ // cleanly divide the values into the appropriate subpixels.
1170
+
1171
+ function roundOffsetsByDPR(_ref)
1172
+ {
1173
+ var x = _ref.x,
1174
+ y = _ref.y;
1175
+ var win = window;
1176
+ var dpr = win.devicePixelRatio || 1;
1177
+ return {
1178
+ x: Math.round(x * dpr) / dpr || 0,
1179
+ y: Math.round(y * dpr) / dpr || 0
1180
+ };
1181
+ }
1182
+
1183
+ function mapToStyles(_ref2)
1184
+ {
1185
+ var _Object$assign2;
1186
+
1187
+ var popper = _ref2.popper,
1188
+ popperRect = _ref2.popperRect,
1189
+ placement = _ref2.placement,
1190
+ offsets = _ref2.offsets,
1191
+ position = _ref2.position,
1192
+ gpuAcceleration = _ref2.gpuAcceleration,
1193
+ adaptive = _ref2.adaptive,
1194
+ roundOffsets = _ref2.roundOffsets;
1195
+
1196
+ var _ref3 = roundOffsets ? roundOffsetsByDPR(offsets) : offsets,
1197
+ _ref3$x = _ref3.x,
1198
+ x = _ref3$x === void 0 ? 0 : _ref3$x,
1199
+ _ref3$y = _ref3.y,
1200
+ y = _ref3$y === void 0 ? 0 : _ref3$y;
1201
+
1202
+ var hasX = offsets.hasOwnProperty('x');
1203
+ var hasY = offsets.hasOwnProperty('y');
1204
+ var sideX = left;
1205
+ var sideY = top;
1206
+ var win = window;
1207
+
1208
+ if (adaptive) {
1209
+ var offsetParent = getOffsetParent(popper);
1210
+
1211
+ if (offsetParent === getWindow(popper)) {
1212
+ offsetParent = getDocumentElement(popper);
1213
+ } // $FlowFixMe[incompatible-cast]: force type refinement, we compare offsetParent with window above, but Flow doesn't detect it
1214
+
1215
+ /*:: offsetParent = (offsetParent: Element); */
1216
+
1217
+
1218
+ if (placement === top) {
1219
+ sideY = bottom;
1220
+ y -= offsetParent.clientHeight - popperRect.height;
1221
+ y *= gpuAcceleration ? 1 : -1;
1222
+ }
1223
+
1224
+ if (placement === left) {
1225
+ sideX = right;
1226
+ x -= offsetParent.clientWidth - popperRect.width;
1227
+ x *= gpuAcceleration ? 1 : -1;
1228
+ }
1229
+ }
1230
+
1231
+ var commonStyles = Object.assign({
1232
+ position: position
1233
+ }, adaptive && unsetSides);
1234
+
1235
+ if (gpuAcceleration) {
1236
+ var _Object$assign;
1237
+
1238
+ return Object.assign(Object.assign({}, commonStyles), {}, (_Object$assign = {}, _Object$assign[sideY] = hasY ? '0' : '', _Object$assign[sideX] = hasX ? '0' : '', _Object$assign.transform = (win.devicePixelRatio || 1) < 2 ? "translate(" + x + "px, " + y + "px)" : "translate3d(" + x + "px, " + y + "px, 0)", _Object$assign));
1239
+ }
1240
+
1241
+ return Object.assign(Object.assign({}, commonStyles), {}, (_Object$assign2 = {}, _Object$assign2[sideY] = hasY ? y + "px" : '', _Object$assign2[sideX] = hasX ? x + "px" : '', _Object$assign2.transform = '', _Object$assign2));
1242
+ }
1243
+
1244
+ function computeStyles(_ref4)
1245
+ {
1246
+ var state = _ref4.state,
1247
+ options = _ref4.options;
1248
+ var _options$gpuAccelerat = options.gpuAcceleration,
1249
+ gpuAcceleration = _options$gpuAccelerat === void 0 ? true : _options$gpuAccelerat,
1250
+ _options$adaptive = options.adaptive,
1251
+ adaptive = _options$adaptive === void 0 ? true : _options$adaptive,
1252
+ _options$roundOffsets = options.roundOffsets,
1253
+ roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets;
1254
+
1255
+ {
1256
+ var transitionProperty = getComputedStyle(state.elements.popper).transitionProperty || '';
1257
+
1258
+ if (adaptive && ['transform', 'top', 'right', 'bottom', 'left'].some(function (property) {
1259
+ return transitionProperty.indexOf(property) >= 0;
1260
+ })) {
1261
+ console.warn(['Popper: Detected CSS transitions on at least one of the following', 'CSS properties: "transform", "top", "right", "bottom", "left".', '\n\n', 'Disable the "computeStyles" modifier\'s `adaptive` option to allow', 'for smooth transitions, or remove these properties from the CSS', 'transition declaration on the popper element if only transitioning', 'opacity or background-color for example.', '\n\n', 'We recommend using the popper element as a wrapper around an inner', 'element that can have any CSS property transitioned for animations.'].join(' '));
1262
+ }
1263
+ }
1264
+
1265
+ var commonStyles = {
1266
+ placement: getBasePlacement(state.placement),
1267
+ popper: state.elements.popper,
1268
+ popperRect: state.rects.popper,
1269
+ gpuAcceleration: gpuAcceleration
1270
+ };
1271
+
1272
+ if (state.modifiersData.popperOffsets != null) {
1273
+ state.styles.popper = Object.assign(Object.assign({}, state.styles.popper), mapToStyles(Object.assign(Object.assign({}, commonStyles), {}, {
1274
+ offsets: state.modifiersData.popperOffsets,
1275
+ position: state.options.strategy,
1276
+ adaptive: adaptive,
1277
+ roundOffsets: roundOffsets
1278
+ })));
1279
+ }
1280
+
1281
+ if (state.modifiersData.arrow != null) {
1282
+ state.styles.arrow = Object.assign(Object.assign({}, state.styles.arrow), mapToStyles(Object.assign(Object.assign({}, commonStyles), {}, {
1283
+ offsets: state.modifiersData.arrow,
1284
+ position: 'absolute',
1285
+ adaptive: false,
1286
+ roundOffsets: roundOffsets
1287
+ })));
1288
+ }
1289
+
1290
+ state.attributes.popper = Object.assign(Object.assign({}, state.attributes.popper), {}, {
1291
+ 'data-popper-placement': state.placement
1292
+ });
1293
+ } // eslint-disable-next-line import/no-unused-modules
1294
+
1295
+
1296
+ var computeStyles$1 = {
1297
+ name: 'computeStyles',
1298
+ enabled: true,
1299
+ phase: 'beforeWrite',
1300
+ fn: computeStyles,
1301
+ data: {}
1302
+ };
1303
+
1304
+ // and applies them to the HTMLElements such as popper and arrow
1305
+
1306
+ function applyStyles(_ref)
1307
+ {
1308
+ var state = _ref.state;
1309
+ Object.keys(state.elements).forEach(function (name) {
1310
+ var style = state.styles[name] || {};
1311
+ var attributes = state.attributes[name] || {};
1312
+ var element = state.elements[name]; // arrow is optional + virtual elements
1313
+
1314
+ if (!isHTMLElement(element) || !getNodeName(element)) {
1315
+ return;
1316
+ } // Flow doesn't support to extend this property, but it's the most
1317
+ // effective way to apply styles to an HTMLElement
1318
+ // $FlowFixMe[cannot-write]
1319
+
1320
+
1321
+ Object.assign(element.style, style);
1322
+ Object.keys(attributes).forEach(function (name) {
1323
+ var value = attributes[name];
1324
+
1325
+ if (value === false) {
1326
+ element.removeAttribute(name);
1327
+ } else {
1328
+ element.setAttribute(name, value === true ? '' : value);
1329
+ }
1330
+ });
1331
+ });
1332
+ }
1333
+
1334
+ function effect$1(_ref2)
1335
+ {
1336
+ var state = _ref2.state;
1337
+ var initialStyles = {
1338
+ popper: {
1339
+ position: state.options.strategy,
1340
+ left: '0',
1341
+ top: '0',
1342
+ margin: '0'
1343
+ },
1344
+ arrow: {
1345
+ position: 'absolute'
1346
+ },
1347
+ reference: {}
1348
+ };
1349
+ Object.assign(state.elements.popper.style, initialStyles.popper);
1350
+
1351
+ if (state.elements.arrow) {
1352
+ Object.assign(state.elements.arrow.style, initialStyles.arrow);
1353
+ }
1354
+
1355
+ return function () {
1356
+ Object.keys(state.elements).forEach(function (name) {
1357
+ var element = state.elements[name];
1358
+ var attributes = state.attributes[name] || {};
1359
+ var styleProperties = Object.keys(state.styles.hasOwnProperty(name) ? state.styles[name] : initialStyles[name]); // Set all values to an empty string to unset them
1360
+
1361
+ var style = styleProperties.reduce(function (style, property) {
1362
+ style[property] = '';
1363
+ return style;
1364
+ }, {}); // arrow is optional + virtual elements
1365
+
1366
+ if (!isHTMLElement(element) || !getNodeName(element)) {
1367
+ return;
1368
+ }
1369
+
1370
+ Object.assign(element.style, style);
1371
+ Object.keys(attributes).forEach(function (attribute) {
1372
+ element.removeAttribute(attribute);
1373
+ });
1374
+ });
1375
+ };
1376
+ } // eslint-disable-next-line import/no-unused-modules
1377
+
1378
+
1379
+ var applyStyles$1 = {
1380
+ name: 'applyStyles',
1381
+ enabled: true,
1382
+ phase: 'write',
1383
+ fn: applyStyles,
1384
+ effect: effect$1,
1385
+ requires: ['computeStyles']
1386
+ };
1387
+
1388
+ function distanceAndSkiddingToXY(placement, rects, offset)
1389
+ {
1390
+ var basePlacement = getBasePlacement(placement);
1391
+ var invertDistance = [left, top].indexOf(basePlacement) >= 0 ? -1 : 1;
1392
+
1393
+ var _ref = typeof offset === 'function' ? offset(Object.assign(Object.assign({}, rects), {}, {
1394
+ placement: placement
1395
+ })) : offset,
1396
+ skidding = _ref[0],
1397
+ distance = _ref[1];
1398
+
1399
+ skidding = skidding || 0;
1400
+ distance = (distance || 0) * invertDistance;
1401
+ return [left, right].indexOf(basePlacement) >= 0 ? {
1402
+ x: distance,
1403
+ y: skidding
1404
+ } : {
1405
+ x: skidding,
1406
+ y: distance
1407
+ };
1408
+ }
1409
+
1410
+ function offset(_ref2)
1411
+ {
1412
+ var state = _ref2.state,
1413
+ options = _ref2.options,
1414
+ name = _ref2.name;
1415
+ var _options$offset = options.offset,
1416
+ offset = _options$offset === void 0 ? [0, 0] : _options$offset;
1417
+ var data = placements.reduce(function (acc, placement) {
1418
+ acc[placement] = distanceAndSkiddingToXY(placement, state.rects, offset);
1419
+ return acc;
1420
+ }, {});
1421
+ var _data$state$placement = data[state.placement],
1422
+ x = _data$state$placement.x,
1423
+ y = _data$state$placement.y;
1424
+
1425
+ if (state.modifiersData.popperOffsets != null) {
1426
+ state.modifiersData.popperOffsets.x += x;
1427
+ state.modifiersData.popperOffsets.y += y;
1428
+ }
1429
+
1430
+ state.modifiersData[name] = data;
1431
+ } // eslint-disable-next-line import/no-unused-modules
1432
+
1433
+
1434
+ var offset$1 = {
1435
+ name: 'offset',
1436
+ enabled: true,
1437
+ phase: 'main',
1438
+ requires: ['popperOffsets'],
1439
+ fn: offset
1440
+ };
1441
+
1442
+ var hash = {
1443
+ left: 'right',
1444
+ right: 'left',
1445
+ bottom: 'top',
1446
+ top: 'bottom'
1447
+ };
1448
+
1449
+ function getOppositePlacement(placement)
1450
+ {
1451
+ return placement.replace(/left|right|bottom|top/g, function (matched) {
1452
+ return hash[matched];
1453
+ });
1454
+ }
1455
+
1456
+ var hash$1 = {
1457
+ start: 'end',
1458
+ end: 'start'
1459
+ };
1460
+
1461
+ function getOppositeVariationPlacement(placement)
1462
+ {
1463
+ return placement.replace(/start|end/g, function (matched) {
1464
+ return hash$1[matched];
1465
+ });
1466
+ }
1467
+
1468
+ /*:: type OverflowsMap = { [ComputedPlacement]: number }; */
1469
+
1470
+ /*;; type OverflowsMap = { [key in ComputedPlacement]: number }; */
1471
+ function computeAutoPlacement(state, options)
1472
+ {
1473
+ if (options === void 0) {
1474
+ options = {};
1475
+ }
1476
+
1477
+ var _options = options,
1478
+ placement = _options.placement,
1479
+ boundary = _options.boundary,
1480
+ rootBoundary = _options.rootBoundary,
1481
+ padding = _options.padding,
1482
+ flipVariations = _options.flipVariations,
1483
+ _options$allowedAutoP = _options.allowedAutoPlacements,
1484
+ allowedAutoPlacements = _options$allowedAutoP === void 0 ? placements : _options$allowedAutoP;
1485
+ var variation = getVariation(placement);
1486
+ var placements$1 = variation ? flipVariations ? variationPlacements : variationPlacements.filter(function (placement) {
1487
+ return getVariation(placement) === variation;
1488
+ }) : basePlacements;
1489
+ var allowedPlacements = placements$1.filter(function (placement) {
1490
+ return allowedAutoPlacements.indexOf(placement) >= 0;
1491
+ });
1492
+
1493
+ if (allowedPlacements.length === 0) {
1494
+ allowedPlacements = placements$1;
1495
+
1496
+ {
1497
+ console.error(['Popper: The `allowedAutoPlacements` option did not allow any', 'placements. Ensure the `placement` option matches the variation', 'of the allowed placements.', 'For example, "auto" cannot be used to allow "bottom-start".', 'Use "auto-start" instead.'].join(' '));
1498
+ }
1499
+ } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions...
1500
+
1501
+
1502
+ var overflows = allowedPlacements.reduce(function (acc, placement) {
1503
+ acc[placement] = detectOverflow(state, {
1504
+ placement: placement,
1505
+ boundary: boundary,
1506
+ rootBoundary: rootBoundary,
1507
+ padding: padding
1508
+ })[getBasePlacement(placement)];
1509
+ return acc;
1510
+ }, {});
1511
+ return Object.keys(overflows).sort(function (a, b) {
1512
+ return overflows[a] - overflows[b];
1513
+ });
1514
+ }
1515
+
1516
+ function getExpandedFallbackPlacements(placement)
1517
+ {
1518
+ if (getBasePlacement(placement) === auto) {
1519
+ return [];
1520
+ }
1521
+
1522
+ var oppositePlacement = getOppositePlacement(placement);
1523
+ return [getOppositeVariationPlacement(placement), oppositePlacement, getOppositeVariationPlacement(oppositePlacement)];
1524
+ }
1525
+
1526
+ function flip(_ref)
1527
+ {
1528
+ var state = _ref.state,
1529
+ options = _ref.options,
1530
+ name = _ref.name;
1531
+
1532
+ if (state.modifiersData[name]._skip) {
1533
+ return;
1534
+ }
1535
+
1536
+ var _options$mainAxis = options.mainAxis,
1537
+ checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,
1538
+ _options$altAxis = options.altAxis,
1539
+ checkAltAxis = _options$altAxis === void 0 ? true : _options$altAxis,
1540
+ specifiedFallbackPlacements = options.fallbackPlacements,
1541
+ padding = options.padding,
1542
+ boundary = options.boundary,
1543
+ rootBoundary = options.rootBoundary,
1544
+ altBoundary = options.altBoundary,
1545
+ _options$flipVariatio = options.flipVariations,
1546
+ flipVariations = _options$flipVariatio === void 0 ? true : _options$flipVariatio,
1547
+ allowedAutoPlacements = options.allowedAutoPlacements;
1548
+ var preferredPlacement = state.options.placement;
1549
+ var basePlacement = getBasePlacement(preferredPlacement);
1550
+ var isBasePlacement = basePlacement === preferredPlacement;
1551
+ var fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipVariations ? [getOppositePlacement(preferredPlacement)] : getExpandedFallbackPlacements(preferredPlacement));
1552
+ var placements = [preferredPlacement].concat(fallbackPlacements).reduce(function (acc, placement) {
1553
+ return acc.concat(getBasePlacement(placement) === auto ? computeAutoPlacement(state, {
1554
+ placement: placement,
1555
+ boundary: boundary,
1556
+ rootBoundary: rootBoundary,
1557
+ padding: padding,
1558
+ flipVariations: flipVariations,
1559
+ allowedAutoPlacements: allowedAutoPlacements
1560
+ }) : placement);
1561
+ }, []);
1562
+ var referenceRect = state.rects.reference;
1563
+ var popperRect = state.rects.popper;
1564
+ var checksMap = new Map();
1565
+ var makeFallbackChecks = true;
1566
+ var firstFittingPlacement = placements[0];
1567
+
1568
+ for (var i = 0; i < placements.length; i++) {
1569
+ var placement = placements[i];
1570
+
1571
+ var _basePlacement = getBasePlacement(placement);
1572
+
1573
+ var isStartVariation = getVariation(placement) === start;
1574
+ var isVertical = [top, bottom].indexOf(_basePlacement) >= 0;
1575
+ var len = isVertical ? 'width' : 'height';
1576
+ var overflow = detectOverflow(state, {
1577
+ placement: placement,
1578
+ boundary: boundary,
1579
+ rootBoundary: rootBoundary,
1580
+ altBoundary: altBoundary,
1581
+ padding: padding
1582
+ });
1583
+ var mainVariationSide = isVertical ? isStartVariation ? right : left : isStartVariation ? bottom : top;
1584
+
1585
+ if (referenceRect[len] > popperRect[len]) {
1586
+ mainVariationSide = getOppositePlacement(mainVariationSide);
1587
+ }
1588
+
1589
+ var altVariationSide = getOppositePlacement(mainVariationSide);
1590
+ var checks = [];
1591
+
1592
+ if (checkMainAxis) {
1593
+ checks.push(overflow[_basePlacement] <= 0);
1594
+ }
1595
+
1596
+ if (checkAltAxis) {
1597
+ checks.push(overflow[mainVariationSide] <= 0, overflow[altVariationSide] <= 0);
1598
+ }
1599
+
1600
+ if (checks.every(function (check) {
1601
+ return check;
1602
+ })) {
1603
+ firstFittingPlacement = placement;
1604
+ makeFallbackChecks = false;
1605
+ break;
1606
+ }
1607
+
1608
+ checksMap.set(placement, checks);
1609
+ }
1610
+
1611
+ if (makeFallbackChecks) {
1612
+ // `2` may be desired in some cases – research later
1613
+ var numberOfChecks = flipVariations ? 3 : 1;
1614
+
1615
+ var _loop = function _loop(_i)
1616
+ {
1617
+ var fittingPlacement = placements.find(function (placement) {
1618
+ var checks = checksMap.get(placement);
1619
+
1620
+ if (checks) {
1621
+ return checks.slice(0, _i).every(function (check) {
1622
+ return check;
1623
+ });
1624
+ }
1625
+ });
1626
+
1627
+ if (fittingPlacement) {
1628
+ firstFittingPlacement = fittingPlacement;
1629
+ return "break";
1630
+ }
1631
+ };
1632
+
1633
+ for (var _i = numberOfChecks; _i > 0; _i--) {
1634
+ var _ret = _loop(_i);
1635
+
1636
+ if (_ret === "break") {
1637
+ break;
1638
+ }
1639
+ }
1640
+ }
1641
+
1642
+ if (state.placement !== firstFittingPlacement) {
1643
+ state.modifiersData[name]._skip = true;
1644
+ state.placement = firstFittingPlacement;
1645
+ state.reset = true;
1646
+ }
1647
+ } // eslint-disable-next-line import/no-unused-modules
1648
+
1649
+
1650
+ var flip$1 = {
1651
+ name: 'flip',
1652
+ enabled: true,
1653
+ phase: 'main',
1654
+ fn: flip,
1655
+ requiresIfExists: ['offset'],
1656
+ data: {
1657
+ _skip: false
1658
+ }
1659
+ };
1660
+
1661
+ function getAltAxis(axis)
1662
+ {
1663
+ return axis === 'x' ? 'y' : 'x';
1664
+ }
1665
+
1666
+ function within(min, value, max)
1667
+ {
1668
+ return Math.max(min, Math.min(value, max));
1669
+ }
1670
+
1671
+ function preventOverflow(_ref)
1672
+ {
1673
+ var state = _ref.state,
1674
+ options = _ref.options,
1675
+ name = _ref.name;
1676
+ var _options$mainAxis = options.mainAxis,
1677
+ checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,
1678
+ _options$altAxis = options.altAxis,
1679
+ checkAltAxis = _options$altAxis === void 0 ? false : _options$altAxis,
1680
+ boundary = options.boundary,
1681
+ rootBoundary = options.rootBoundary,
1682
+ altBoundary = options.altBoundary,
1683
+ padding = options.padding,
1684
+ _options$tether = options.tether,
1685
+ tether = _options$tether === void 0 ? true : _options$tether,
1686
+ _options$tetherOffset = options.tetherOffset,
1687
+ tetherOffset = _options$tetherOffset === void 0 ? 0 : _options$tetherOffset;
1688
+ var overflow = detectOverflow(state, {
1689
+ boundary: boundary,
1690
+ rootBoundary: rootBoundary,
1691
+ padding: padding,
1692
+ altBoundary: altBoundary
1693
+ });
1694
+ var basePlacement = getBasePlacement(state.placement);
1695
+ var variation = getVariation(state.placement);
1696
+ var isBasePlacement = !variation;
1697
+ var mainAxis = getMainAxisFromPlacement(basePlacement);
1698
+ var altAxis = getAltAxis(mainAxis);
1699
+ var popperOffsets = state.modifiersData.popperOffsets;
1700
+ var referenceRect = state.rects.reference;
1701
+ var popperRect = state.rects.popper;
1702
+ var tetherOffsetValue = typeof tetherOffset === 'function' ? tetherOffset(Object.assign(Object.assign({}, state.rects), {}, {
1703
+ placement: state.placement
1704
+ })) : tetherOffset;
1705
+ var data = {
1706
+ x: 0,
1707
+ y: 0
1708
+ };
1709
+
1710
+ if (!popperOffsets) {
1711
+ return;
1712
+ }
1713
+
1714
+ if (checkMainAxis) {
1715
+ var mainSide = mainAxis === 'y' ? top : left;
1716
+ var altSide = mainAxis === 'y' ? bottom : right;
1717
+ var len = mainAxis === 'y' ? 'height' : 'width';
1718
+ var offset = popperOffsets[mainAxis];
1719
+ var min = popperOffsets[mainAxis] + overflow[mainSide];
1720
+ var max = popperOffsets[mainAxis] - overflow[altSide];
1721
+ var additive = tether ? -popperRect[len] / 2 : 0;
1722
+ var minLen = variation === start ? referenceRect[len] : popperRect[len];
1723
+ var maxLen = variation === start ? -popperRect[len] : -referenceRect[len]; // We need to include the arrow in the calculation so the arrow doesn't go
1724
+ // outside the reference bounds
1725
+
1726
+ var arrowElement = state.elements.arrow;
1727
+ var arrowRect = tether && arrowElement ? getLayoutRect(arrowElement) : {
1728
+ width: 0,
1729
+ height: 0
1730
+ };
1731
+ var arrowPaddingObject = state.modifiersData['arrow#persistent'] ? state.modifiersData['arrow#persistent'].padding : getFreshSideObject();
1732
+ var arrowPaddingMin = arrowPaddingObject[mainSide];
1733
+ var arrowPaddingMax = arrowPaddingObject[altSide]; // If the reference length is smaller than the arrow length, we don't want
1734
+ // to include its full size in the calculation. If the reference is small
1735
+ // and near the edge of a boundary, the popper can overflow even if the
1736
+ // reference is not overflowing as well (e.g. virtual elements with no
1737
+ // width or height)
1738
+
1739
+ var arrowLen = within(0, referenceRect[len], arrowRect[len]);
1740
+ var minOffset = isBasePlacement ? referenceRect[len] / 2 - additive - arrowLen - arrowPaddingMin - tetherOffsetValue : minLen - arrowLen - arrowPaddingMin - tetherOffsetValue;
1741
+ var maxOffset = isBasePlacement ? -referenceRect[len] / 2 + additive + arrowLen + arrowPaddingMax + tetherOffsetValue : maxLen + arrowLen + arrowPaddingMax + tetherOffsetValue;
1742
+ var arrowOffsetParent = state.elements.arrow && getOffsetParent(state.elements.arrow);
1743
+ var clientOffset = arrowOffsetParent ? mainAxis === 'y' ? arrowOffsetParent.clientTop || 0 : arrowOffsetParent.clientLeft || 0 : 0;
1744
+ var offsetModifierValue = state.modifiersData.offset ? state.modifiersData.offset[state.placement][mainAxis] : 0;
1745
+ var tetherMin = popperOffsets[mainAxis] + minOffset - offsetModifierValue - clientOffset;
1746
+ var tetherMax = popperOffsets[mainAxis] + maxOffset - offsetModifierValue;
1747
+ var preventedOffset = within(tether ? Math.min(min, tetherMin) : min, offset, tether ? Math.max(max, tetherMax) : max);
1748
+ popperOffsets[mainAxis] = preventedOffset;
1749
+ data[mainAxis] = preventedOffset - offset;
1750
+ }
1751
+
1752
+ if (checkAltAxis) {
1753
+ var _mainSide = mainAxis === 'x' ? top : left;
1754
+
1755
+ var _altSide = mainAxis === 'x' ? bottom : right;
1756
+
1757
+ var _offset = popperOffsets[altAxis];
1758
+
1759
+ var _min = _offset + overflow[_mainSide];
1760
+
1761
+ var _max = _offset - overflow[_altSide];
1762
+
1763
+ var _preventedOffset = within(_min, _offset, _max);
1764
+
1765
+ popperOffsets[altAxis] = _preventedOffset;
1766
+ data[altAxis] = _preventedOffset - _offset;
1767
+ }
1768
+
1769
+ state.modifiersData[name] = data;
1770
+ } // eslint-disable-next-line import/no-unused-modules
1771
+
1772
+
1773
+ var preventOverflow$1 = {
1774
+ name: 'preventOverflow',
1775
+ enabled: true,
1776
+ phase: 'main',
1777
+ fn: preventOverflow,
1778
+ requiresIfExists: ['offset']
1779
+ };
1780
+
1781
+ function arrow(_ref)
1782
+ {
1783
+ var _state$modifiersData$;
1784
+
1785
+ var state = _ref.state,
1786
+ name = _ref.name;
1787
+ var arrowElement = state.elements.arrow;
1788
+ var popperOffsets = state.modifiersData.popperOffsets;
1789
+ var basePlacement = getBasePlacement(state.placement);
1790
+ var axis = getMainAxisFromPlacement(basePlacement);
1791
+ var isVertical = [left, right].indexOf(basePlacement) >= 0;
1792
+ var len = isVertical ? 'height' : 'width';
1793
+
1794
+ if (!arrowElement || !popperOffsets) {
1795
+ return;
1796
+ }
1797
+
1798
+ var paddingObject = state.modifiersData[name + "#persistent"].padding;
1799
+ var arrowRect = getLayoutRect(arrowElement);
1800
+ var minProp = axis === 'y' ? top : left;
1801
+ var maxProp = axis === 'y' ? bottom : right;
1802
+ var endDiff = state.rects.reference[len] + state.rects.reference[axis] - popperOffsets[axis] - state.rects.popper[len];
1803
+ var startDiff = popperOffsets[axis] - state.rects.reference[axis];
1804
+ var arrowOffsetParent = getOffsetParent(arrowElement);
1805
+ var clientSize = arrowOffsetParent ? axis === 'y' ? arrowOffsetParent.clientHeight || 0 : arrowOffsetParent.clientWidth || 0 : 0;
1806
+ var centerToReference = endDiff / 2 - startDiff / 2; // Make sure the arrow doesn't overflow the popper if the center point is
1807
+ // outside of the popper bounds
1808
+
1809
+ var min = paddingObject[minProp];
1810
+ var max = clientSize - arrowRect[len] - paddingObject[maxProp];
1811
+ var center = clientSize / 2 - arrowRect[len] / 2 + centerToReference;
1812
+ var offset = within(min, center, max); // Prevents breaking syntax highlighting...
1813
+
1814
+ var axisProp = axis;
1815
+ state.modifiersData[name] = (_state$modifiersData$ = {}, _state$modifiersData$[axisProp] = offset, _state$modifiersData$.centerOffset = offset - center, _state$modifiersData$);
1816
+ }
1817
+
1818
+ function effect$2(_ref2)
1819
+ {
1820
+ var state = _ref2.state,
1821
+ options = _ref2.options,
1822
+ name = _ref2.name;
1823
+ var _options$element = options.element,
1824
+ arrowElement = _options$element === void 0 ? '[data-popper-arrow]' : _options$element,
1825
+ _options$padding = options.padding,
1826
+ padding = _options$padding === void 0 ? 0 : _options$padding;
1827
+
1828
+ if (arrowElement == null) {
1829
+ return;
1830
+ } // CSS selector
1831
+
1832
+
1833
+ if (typeof arrowElement === 'string') {
1834
+ arrowElement = state.elements.popper.querySelector(arrowElement);
1835
+
1836
+ if (!arrowElement) {
1837
+ return;
1838
+ }
1839
+ }
1840
+
1841
+ {
1842
+ if (!isHTMLElement(arrowElement)) {
1843
+ console.error(['Popper: "arrow" element must be an HTMLElement (not an SVGElement).', 'To use an SVG arrow, wrap it in an HTMLElement that will be used as', 'the arrow.'].join(' '));
1844
+ }
1845
+ }
1846
+
1847
+ if (!contains(state.elements.popper, arrowElement)) {
1848
+ {
1849
+ console.error(['Popper: "arrow" modifier\'s `element` must be a child of the popper', 'element.'].join(' '));
1850
+ }
1851
+
1852
+ return;
1853
+ }
1854
+
1855
+ state.elements.arrow = arrowElement;
1856
+ state.modifiersData[name + "#persistent"] = {
1857
+ padding: mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements))
1858
+ };
1859
+ } // eslint-disable-next-line import/no-unused-modules
1860
+
1861
+
1862
+ var arrow$1 = {
1863
+ name: 'arrow',
1864
+ enabled: true,
1865
+ phase: 'main',
1866
+ fn: arrow,
1867
+ effect: effect$2,
1868
+ requires: ['popperOffsets'],
1869
+ requiresIfExists: ['preventOverflow']
1870
+ };
1871
+
1872
+ function getSideOffsets(overflow, rect, preventedOffsets)
1873
+ {
1874
+ if (preventedOffsets === void 0) {
1875
+ preventedOffsets = {
1876
+ x: 0,
1877
+ y: 0
1878
+ };
1879
+ }
1880
+
1881
+ return {
1882
+ top: overflow.top - rect.height - preventedOffsets.y,
1883
+ right: overflow.right - rect.width + preventedOffsets.x,
1884
+ bottom: overflow.bottom - rect.height + preventedOffsets.y,
1885
+ left: overflow.left - rect.width - preventedOffsets.x
1886
+ };
1887
+ }
1888
+
1889
+ function isAnySideFullyClipped(overflow)
1890
+ {
1891
+ return [top, right, bottom, left].some(function (side) {
1892
+ return overflow[side] >= 0;
1893
+ });
1894
+ }
1895
+
1896
+ function hide(_ref)
1897
+ {
1898
+ var state = _ref.state,
1899
+ name = _ref.name;
1900
+ var referenceRect = state.rects.reference;
1901
+ var popperRect = state.rects.popper;
1902
+ var preventedOffsets = state.modifiersData.preventOverflow;
1903
+ var referenceOverflow = detectOverflow(state, {
1904
+ elementContext: 'reference'
1905
+ });
1906
+ var popperAltOverflow = detectOverflow(state, {
1907
+ altBoundary: true
1908
+ });
1909
+ var referenceClippingOffsets = getSideOffsets(referenceOverflow, referenceRect);
1910
+ var popperEscapeOffsets = getSideOffsets(popperAltOverflow, popperRect, preventedOffsets);
1911
+ var isReferenceHidden = isAnySideFullyClipped(referenceClippingOffsets);
1912
+ var hasPopperEscaped = isAnySideFullyClipped(popperEscapeOffsets);
1913
+ state.modifiersData[name] = {
1914
+ referenceClippingOffsets: referenceClippingOffsets,
1915
+ popperEscapeOffsets: popperEscapeOffsets,
1916
+ isReferenceHidden: isReferenceHidden,
1917
+ hasPopperEscaped: hasPopperEscaped
1918
+ };
1919
+ state.attributes.popper = Object.assign(Object.assign({}, state.attributes.popper), {}, {
1920
+ 'data-popper-reference-hidden': isReferenceHidden,
1921
+ 'data-popper-escaped': hasPopperEscaped
1922
+ });
1923
+ } // eslint-disable-next-line import/no-unused-modules
1924
+
1925
+
1926
+ var hide$1 = {
1927
+ name: 'hide',
1928
+ enabled: true,
1929
+ phase: 'main',
1930
+ requiresIfExists: ['preventOverflow'],
1931
+ fn: hide
1932
+ };
1933
+
1934
+ var defaultModifiers = [eventListeners, popperOffsets$1, computeStyles$1, applyStyles$1];
1935
+ var createPopper = /*#__PURE__*/ popperGenerator({
1936
+ defaultModifiers: defaultModifiers
1937
+ }); // eslint-disable-next-line import/no-unused-modules
1938
+
1939
+ var defaultModifiers$1 = [eventListeners, popperOffsets$1, computeStyles$1, applyStyles$1, offset$1, flip$1, preventOverflow$1, arrow$1, hide$1];
1940
+ var createPopper$1 = /*#__PURE__*/ popperGenerator({
1941
+ defaultModifiers: defaultModifiers$1
1942
+ }); // eslint-disable-next-line import/no-unused-modules
1943
+
1944
+ exports.applyStyles = applyStyles$1;
1945
+ exports.arrow = arrow$1;
1946
+ exports.computeStyles = computeStyles$1;
1947
+ exports.createPopper = createPopper$1;
1948
+ exports.createPopperLite = createPopper;
1949
+ exports.defaultModifiers = defaultModifiers$1;
1950
+ exports.detectOverflow = detectOverflow;
1951
+ exports.eventListeners = eventListeners;
1952
+ exports.flip = flip$1;
1953
+ exports.hide = hide$1;
1954
+ exports.offset = offset$1;
1955
+ exports.popperGenerator = popperGenerator;
1956
+ exports.popperOffsets = popperOffsets$1;
1957
+ exports.preventOverflow = preventOverflow$1;
1958
+
1959
+ Object.defineProperty(exports, '__esModule', { value: true });
1960
+
1961
+ })));
assets/js/popper/popper.min.js ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ /*!
2
+ * @popperjs/core v2.6.0 - MIT License
3
+ */
4
+
5
+ "use strict";!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e=e||self).Popper={})}(this,(function(e){function t(e){return{width:(e=e.getBoundingClientRect()).width,height:e.height,top:e.top,right:e.right,bottom:e.bottom,left:e.left,x:e.left,y:e.top}}function n(e){return"[object Window]"!==e.toString()?(e=e.ownerDocument)&&e.defaultView||window:e}function r(e){return{scrollLeft:(e=n(e)).pageXOffset,scrollTop:e.pageYOffset}}function o(e){return e instanceof n(e).Element||e instanceof Element}function i(e){return e instanceof n(e).HTMLElement||e instanceof HTMLElement}function a(e){return e?(e.nodeName||"").toLowerCase():null}function s(e){return((o(e)?e.ownerDocument:e.document)||window.document).documentElement}function f(e){return t(s(e)).left+r(e).scrollLeft}function c(e){return n(e).getComputedStyle(e)}function p(e){return e=c(e),/auto|scroll|overlay|hidden/.test(e.overflow+e.overflowY+e.overflowX)}function l(e,o,c){void 0===c&&(c=!1);var l=s(o);e=t(e);var u=i(o),d={scrollLeft:0,scrollTop:0},m={x:0,y:0};return(u||!u&&!c)&&(("body"!==a(o)||p(l))&&(d=o!==n(o)&&i(o)?{scrollLeft:o.scrollLeft,scrollTop:o.scrollTop}:r(o)),i(o)?((m=t(o)).x+=o.clientLeft,m.y+=o.clientTop):l&&(m.x=f(l))),{x:e.left+d.scrollLeft-m.x,y:e.top+d.scrollTop-m.y,width:e.width,height:e.height}}function u(e){return{x:e.offsetLeft,y:e.offsetTop,width:e.offsetWidth,height:e.offsetHeight}}function d(e){return"html"===a(e)?e:e.assignedSlot||e.parentNode||e.host||s(e)}function m(e,t){void 0===t&&(t=[]);var r=function e(t){return 0<=["html","body","#document"].indexOf(a(t))?t.ownerDocument.body:i(t)&&p(t)?t:e(d(t))}(e);e="body"===a(r);var o=n(r);return r=e?[o].concat(o.visualViewport||[],p(r)?r:[]):r,t=t.concat(r),e?t:t.concat(m(d(r)))}function h(e){if(!i(e)||"fixed"===c(e).position)return null;if(e=e.offsetParent){var t=s(e);if("body"===a(e)&&"static"===c(e).position&&"static"!==c(t).position)return t}return e}function g(e){for(var t=n(e),r=h(e);r&&0<=["table","td","th"].indexOf(a(r))&&"static"===c(r).position;)r=h(r);if(r&&"body"===a(r)&&"static"===c(r).position)return t;if(!r)e:{for(e=d(e);i(e)&&0>["html","body"].indexOf(a(e));){if("none"!==(r=c(e)).transform||"none"!==r.perspective||r.willChange&&"auto"!==r.willChange){r=e;break e}e=e.parentNode}r=null}return r||t}function v(e){var t=new Map,n=new Set,r=[];return e.forEach((function(e){t.set(e.name,e)})),e.forEach((function(e){n.has(e.name)||function e(o){n.add(o.name),[].concat(o.requires||[],o.requiresIfExists||[]).forEach((function(r){n.has(r)||(r=t.get(r))&&e(r)})),r.push(o)}(e)})),r}function b(e){var t;return function(){return t||(t=new Promise((function(n){Promise.resolve().then((function(){t=void 0,n(e())}))}))),t}}function y(e){return e.split("-")[0]}function O(e,t){var r,o=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if((r=o)&&(r=o instanceof(r=n(o).ShadowRoot)||o instanceof ShadowRoot),r)do{if(t&&e.isSameNode(t))return!0;t=t.parentNode||t.host}while(t);return!1}function w(e){return Object.assign(Object.assign({},e),{},{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function x(e,o){if("viewport"===o){o=n(e);var a=s(e);o=o.visualViewport;var p=a.clientWidth;a=a.clientHeight;var l=0,u=0;o&&(p=o.width,a=o.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(l=o.offsetLeft,u=o.offsetTop)),e=w(e={width:p,height:a,x:l+f(e),y:u})}else i(o)?((e=t(o)).top+=o.clientTop,e.left+=o.clientLeft,e.bottom=e.top+o.clientHeight,e.right=e.left+o.clientWidth,e.width=o.clientWidth,e.height=o.clientHeight,e.x=e.left,e.y=e.top):(u=s(e),e=s(u),l=r(u),o=u.ownerDocument.body,p=Math.max(e.scrollWidth,e.clientWidth,o?o.scrollWidth:0,o?o.clientWidth:0),a=Math.max(e.scrollHeight,e.clientHeight,o?o.scrollHeight:0,o?o.clientHeight:0),u=-l.scrollLeft+f(u),l=-l.scrollTop,"rtl"===c(o||e).direction&&(u+=Math.max(e.clientWidth,o?o.clientWidth:0)-p),e=w({width:p,height:a,x:u,y:l}));return e}function j(e,t,n){return t="clippingParents"===t?function(e){var t=m(d(e)),n=0<=["absolute","fixed"].indexOf(c(e).position)&&i(e)?g(e):e;return o(n)?t.filter((function(e){return o(e)&&O(e,n)&&"body"!==a(e)})):[]}(e):[].concat(t),(n=(n=[].concat(t,[n])).reduce((function(t,n){return n=x(e,n),t.top=Math.max(n.top,t.top),t.right=Math.min(n.right,t.right),t.bottom=Math.min(n.bottom,t.bottom),t.left=Math.max(n.left,t.left),t}),x(e,n[0]))).width=n.right-n.left,n.height=n.bottom-n.top,n.x=n.left,n.y=n.top,n}function M(e){return 0<=["top","bottom"].indexOf(e)?"x":"y"}function E(e){var t=e.reference,n=e.element,r=(e=e.placement)?y(e):null;e=e?e.split("-")[1]:null;var o=t.x+t.width/2-n.width/2,i=t.y+t.height/2-n.height/2;switch(r){case"top":o={x:o,y:t.y-n.height};break;case"bottom":o={x:o,y:t.y+t.height};break;case"right":o={x:t.x+t.width,y:i};break;case"left":o={x:t.x-n.width,y:i};break;default:o={x:t.x,y:t.y}}if(null!=(r=r?M(r):null))switch(i="y"===r?"height":"width",e){case"start":o[r]-=t[i]/2-n[i]/2;break;case"end":o[r]+=t[i]/2-n[i]/2}return o}function D(e){return Object.assign(Object.assign({},{top:0,right:0,bottom:0,left:0}),e)}function P(e,t){return t.reduce((function(t,n){return t[n]=e,t}),{})}function L(e,n){void 0===n&&(n={});var r=n;n=void 0===(n=r.placement)?e.placement:n;var i=r.boundary,a=void 0===i?"clippingParents":i,f=void 0===(i=r.rootBoundary)?"viewport":i;i=void 0===(i=r.elementContext)?"popper":i;var c=r.altBoundary,p=void 0!==c&&c;r=D("number"!=typeof(r=void 0===(r=r.padding)?0:r)?r:P(r,T));var l=e.elements.reference;c=e.rects.popper,a=j(o(p=e.elements[p?"popper"===i?"reference":"popper":i])?p:p.contextElement||s(e.elements.popper),a,f),p=E({reference:f=t(l),element:c,strategy:"absolute",placement:n}),c=w(Object.assign(Object.assign({},c),p)),f="popper"===i?c:f;var u={top:a.top-f.top+r.top,bottom:f.bottom-a.bottom+r.bottom,left:a.left-f.left+r.left,right:f.right-a.right+r.right};if(e=e.modifiersData.offset,"popper"===i&&e){var d=e[n];Object.keys(u).forEach((function(e){var t=0<=["right","bottom"].indexOf(e)?1:-1,n=0<=["top","bottom"].indexOf(e)?"y":"x";u[e]+=d[n]*t}))}return u}function k(){for(var e=arguments.length,t=Array(e),n=0;n<e;n++)t[n]=arguments[n];return!t.some((function(e){return!(e&&"function"==typeof e.getBoundingClientRect)}))}function B(e){void 0===e&&(e={});var t=e.defaultModifiers,n=void 0===t?[]:t,r=void 0===(e=e.defaultOptions)?V:e;return function(e,t,i){function a(){f.forEach((function(e){return e()})),f=[]}void 0===i&&(i=r);var s={placement:"bottom",orderedModifiers:[],options:Object.assign(Object.assign({},V),r),modifiersData:{},elements:{reference:e,popper:t},attributes:{},styles:{}},f=[],c=!1,p={state:s,setOptions:function(i){return a(),s.options=Object.assign(Object.assign(Object.assign({},r),s.options),i),s.scrollParents={reference:o(e)?m(e):e.contextElement?m(e.contextElement):[],popper:m(t)},i=function(e){var t=v(e);return N.reduce((function(e,n){return e.concat(t.filter((function(e){return e.phase===n})))}),[])}(function(e){var t=e.reduce((function(e,t){var n=e[t.name];return e[t.name]=n?Object.assign(Object.assign(Object.assign({},n),t),{},{options:Object.assign(Object.assign({},n.options),t.options),data:Object.assign(Object.assign({},n.data),t.data)}):t,e}),{});return Object.keys(t).map((function(e){return t[e]}))}([].concat(n,s.options.modifiers))),s.orderedModifiers=i.filter((function(e){return e.enabled})),s.orderedModifiers.forEach((function(e){var t=e.name,n=e.options;n=void 0===n?{}:n,"function"==typeof(e=e.effect)&&(t=e({state:s,name:t,instance:p,options:n}),f.push(t||function(){}))})),p.update()},forceUpdate:function(){if(!c){var e=s.elements,t=e.reference;if(k(t,e=e.popper))for(s.rects={reference:l(t,g(e),"fixed"===s.options.strategy),popper:u(e)},s.reset=!1,s.placement=s.options.placement,s.orderedModifiers.forEach((function(e){return s.modifiersData[e.name]=Object.assign({},e.data)})),t=0;t<s.orderedModifiers.length;t++)if(!0===s.reset)s.reset=!1,t=-1;else{var n=s.orderedModifiers[t];e=n.fn;var r=n.options;r=void 0===r?{}:r,n=n.name,"function"==typeof e&&(s=e({state:s,options:r,name:n,instance:p})||s)}}},update:b((function(){return new Promise((function(e){p.forceUpdate(),e(s)}))})),destroy:function(){a(),c=!0}};return k(e,t)?(p.setOptions(i).then((function(e){!c&&i.onFirstUpdate&&i.onFirstUpdate(e)})),p):p}}function W(e){var t,r=e.popper,o=e.popperRect,i=e.placement,a=e.offsets,f=e.position,c=e.gpuAcceleration,p=e.adaptive;e.roundOffsets?(e=window.devicePixelRatio||1,e={x:Math.round(a.x*e)/e||0,y:Math.round(a.y*e)/e||0}):e=a;var l=e;e=void 0===(e=l.x)?0:e,l=void 0===(l=l.y)?0:l;var u=a.hasOwnProperty("x");a=a.hasOwnProperty("y");var d,m="left",h="top",v=window;if(p){var b=g(r);b===n(r)&&(b=s(r)),"top"===i&&(h="bottom",l-=b.clientHeight-o.height,l*=c?1:-1),"left"===i&&(m="right",e-=b.clientWidth-o.width,e*=c?1:-1)}return r=Object.assign({position:f},p&&z),c?Object.assign(Object.assign({},r),{},((d={})[h]=a?"0":"",d[m]=u?"0":"",d.transform=2>(v.devicePixelRatio||1)?"translate("+e+"px, "+l+"px)":"translate3d("+e+"px, "+l+"px, 0)",d)):Object.assign(Object.assign({},r),{},((t={})[h]=a?l+"px":"",t[m]=u?e+"px":"",t.transform="",t))}function A(e){return e.replace(/left|right|bottom|top/g,(function(e){return G[e]}))}function H(e){return e.replace(/start|end/g,(function(e){return J[e]}))}function R(e,t,n){return void 0===n&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function S(e){return["top","right","bottom","left"].some((function(t){return 0<=e[t]}))}var T=["top","bottom","right","left"],q=T.reduce((function(e,t){return e.concat([t+"-start",t+"-end"])}),[]),C=[].concat(T,["auto"]).reduce((function(e,t){return e.concat([t,t+"-start",t+"-end"])}),[]),N="beforeRead read afterRead beforeMain main afterMain beforeWrite write afterWrite".split(" "),V={placement:"bottom",modifiers:[],strategy:"absolute"},I={passive:!0},_={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(e){var t=e.state,r=e.instance,o=(e=e.options).scroll,i=void 0===o||o,a=void 0===(e=e.resize)||e,s=n(t.elements.popper),f=[].concat(t.scrollParents.reference,t.scrollParents.popper);return i&&f.forEach((function(e){e.addEventListener("scroll",r.update,I)})),a&&s.addEventListener("resize",r.update,I),function(){i&&f.forEach((function(e){e.removeEventListener("scroll",r.update,I)})),a&&s.removeEventListener("resize",r.update,I)}},data:{}},U={name:"popperOffsets",enabled:!0,phase:"read",fn:function(e){var t=e.state;t.modifiersData[e.name]=E({reference:t.rects.reference,element:t.rects.popper,strategy:"absolute",placement:t.placement})},data:{}},z={top:"auto",right:"auto",bottom:"auto",left:"auto"},F={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(e){var t=e.state,n=e.options;e=void 0===(e=n.gpuAcceleration)||e;var r=n.adaptive;r=void 0===r||r,n=void 0===(n=n.roundOffsets)||n,e={placement:y(t.placement),popper:t.elements.popper,popperRect:t.rects.popper,gpuAcceleration:e},null!=t.modifiersData.popperOffsets&&(t.styles.popper=Object.assign(Object.assign({},t.styles.popper),W(Object.assign(Object.assign({},e),{},{offsets:t.modifiersData.popperOffsets,position:t.options.strategy,adaptive:r,roundOffsets:n})))),null!=t.modifiersData.arrow&&(t.styles.arrow=Object.assign(Object.assign({},t.styles.arrow),W(Object.assign(Object.assign({},e),{},{offsets:t.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:n})))),t.attributes.popper=Object.assign(Object.assign({},t.attributes.popper),{},{"data-popper-placement":t.placement})},data:{}},X={name:"applyStyles",enabled:!0,phase:"write",fn:function(e){var t=e.state;Object.keys(t.elements).forEach((function(e){var n=t.styles[e]||{},r=t.attributes[e]||{},o=t.elements[e];i(o)&&a(o)&&(Object.assign(o.style,n),Object.keys(r).forEach((function(e){var t=r[e];!1===t?o.removeAttribute(e):o.setAttribute(e,!0===t?"":t)})))}))},effect:function(e){var t=e.state,n={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(t.elements.popper.style,n.popper),t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow),function(){Object.keys(t.elements).forEach((function(e){var r=t.elements[e],o=t.attributes[e]||{};e=Object.keys(t.styles.hasOwnProperty(e)?t.styles[e]:n[e]).reduce((function(e,t){return e[t]="",e}),{}),i(r)&&a(r)&&(Object.assign(r.style,e),Object.keys(o).forEach((function(e){r.removeAttribute(e)})))}))}},requires:["computeStyles"]},Y={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(e){var t=e.state,n=e.name,r=void 0===(e=e.options.offset)?[0,0]:e,o=(e=C.reduce((function(e,n){var o=t.rects,i=y(n),a=0<=["left","top"].indexOf(i)?-1:1,s="function"==typeof r?r(Object.assign(Object.assign({},o),{},{placement:n})):r;return o=(o=s[0])||0,s=((s=s[1])||0)*a,i=0<=["left","right"].indexOf(i)?{x:s,y:o}:{x:o,y:s},e[n]=i,e}),{}))[t.placement],i=o.x;o=o.y,null!=t.modifiersData.popperOffsets&&(t.modifiersData.popperOffsets.x+=i,t.modifiersData.popperOffsets.y+=o),t.modifiersData[n]=e}},G={left:"right",right:"left",bottom:"top",top:"bottom"},J={start:"end",end:"start"},K={name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options;if(e=e.name,!t.modifiersData[e]._skip){var r=n.mainAxis;r=void 0===r||r;var o=n.altAxis;o=void 0===o||o;var i=n.fallbackPlacements,a=n.padding,s=n.boundary,f=n.rootBoundary,c=n.altBoundary,p=n.flipVariations,l=void 0===p||p,u=n.allowedAutoPlacements;p=y(n=t.options.placement),i=i||(p!==n&&l?function(e){if("auto"===y(e))return[];var t=A(e);return[H(e),t,H(t)]}(n):[A(n)]);var d=[n].concat(i).reduce((function(e,n){return e.concat("auto"===y(n)?function(e,t){void 0===t&&(t={});var n=t.boundary,r=t.rootBoundary,o=t.padding,i=t.flipVariations,a=t.allowedAutoPlacements,s=void 0===a?C:a,f=t.placement.split("-")[1];0===(i=(t=f?i?q:q.filter((function(e){return e.split("-")[1]===f})):T).filter((function(e){return 0<=s.indexOf(e)}))).length&&(i=t);var c=i.reduce((function(t,i){return t[i]=L(e,{placement:i,boundary:n,rootBoundary:r,padding:o})[y(i)],t}),{});return Object.keys(c).sort((function(e,t){return c[e]-c[t]}))}(t,{placement:n,boundary:s,rootBoundary:f,padding:a,flipVariations:l,allowedAutoPlacements:u}):n)}),[]);n=t.rects.reference,i=t.rects.popper;var m=new Map;p=!0;for(var h=d[0],g=0;g<d.length;g++){var v=d[g],b=y(v),O="start"===v.split("-")[1],w=0<=["top","bottom"].indexOf(b),x=w?"width":"height",j=L(t,{placement:v,boundary:s,rootBoundary:f,altBoundary:c,padding:a});if(O=w?O?"right":"left":O?"bottom":"top",n[x]>i[x]&&(O=A(O)),x=A(O),w=[],r&&w.push(0>=j[b]),o&&w.push(0>=j[O],0>=j[x]),w.every((function(e){return e}))){h=v,p=!1;break}m.set(v,w)}if(p)for(r=function(e){var t=d.find((function(t){if(t=m.get(t))return t.slice(0,e).every((function(e){return e}))}));if(t)return h=t,"break"},o=l?3:1;0<o&&"break"!==r(o);o--);t.placement!==h&&(t.modifiersData[e]._skip=!0,t.placement=h,t.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}},Q={name:"preventOverflow",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options;e=e.name;var r=n.mainAxis,o=void 0===r||r;r=void 0!==(r=n.altAxis)&&r;var i=n.tether;i=void 0===i||i;var a=n.tetherOffset,s=void 0===a?0:a;n=L(t,{boundary:n.boundary,rootBoundary:n.rootBoundary,padding:n.padding,altBoundary:n.altBoundary}),a=y(t.placement);var f=t.placement.split("-")[1],c=!f,p=M(a);a="x"===p?"y":"x";var l=t.modifiersData.popperOffsets,d=t.rects.reference,m=t.rects.popper,h="function"==typeof s?s(Object.assign(Object.assign({},t.rects),{},{placement:t.placement})):s;if(s={x:0,y:0},l){if(o){var v="y"===p?"top":"left",b="y"===p?"bottom":"right",O="y"===p?"height":"width";o=l[p];var w=l[p]+n[v],x=l[p]-n[b],j=i?-m[O]/2:0,E="start"===f?d[O]:m[O];f="start"===f?-m[O]:-d[O],m=t.elements.arrow,m=i&&m?u(m):{width:0,height:0};var D=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0};v=D[v],b=D[b],m=Math.max(0,Math.min(d[O],m[O])),E=c?d[O]/2-j-m-v-h:E-m-v-h,c=c?-d[O]/2+j+m+b+h:f+m+b+h,h=t.elements.arrow&&g(t.elements.arrow),d=t.modifiersData.offset?t.modifiersData.offset[t.placement][p]:0,h=l[p]+E-d-(h?"y"===p?h.clientTop||0:h.clientLeft||0:0),c=l[p]+c-d,i=Math.max(i?Math.min(w,h):w,Math.min(o,i?Math.max(x,c):x)),l[p]=i,s[p]=i-o}r&&(r=l[a],i=Math.max(r+n["x"===p?"top":"left"],Math.min(r,r-n["x"===p?"bottom":"right"])),l[a]=i,s[a]=i-r),t.modifiersData[e]=s}},requiresIfExists:["offset"]},Z={name:"arrow",enabled:!0,phase:"main",fn:function(e){var t,n=e.state;e=e.name;var r=n.elements.arrow,o=n.modifiersData.popperOffsets,i=y(n.placement),a=M(i);if(i=0<=["left","right"].indexOf(i)?"height":"width",r&&o){var s=n.modifiersData[e+"#persistent"].padding,f=u(r),c="y"===a?"top":"left",p="y"===a?"bottom":"right",l=n.rects.reference[i]+n.rects.reference[a]-o[a]-n.rects.popper[i];o=o[a]-n.rects.reference[a],l=(r=(r=g(r))?"y"===a?r.clientHeight||0:r.clientWidth||0:0)/2-f[i]/2+(l/2-o/2),i=Math.max(s[c],Math.min(l,r-f[i]-s[p])),n.modifiersData[e]=((t={})[a]=i,t.centerOffset=i-l,t)}},effect:function(e){var t=e.state,n=e.options;e=e.name;var r=n.element;if(r=void 0===r?"[data-popper-arrow]":r,n=void 0===(n=n.padding)?0:n,null!=r){if("string"==typeof r&&!(r=t.elements.popper.querySelector(r)))return;O(t.elements.popper,r)&&(t.elements.arrow=r,t.modifiersData[e+"#persistent"]={padding:D("number"!=typeof n?n:P(n,T))})}},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]},$={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(e){var t=e.state;e=e.name;var n=t.rects.reference,r=t.rects.popper,o=t.modifiersData.preventOverflow,i=L(t,{elementContext:"reference"}),a=L(t,{altBoundary:!0});n=R(i,n),r=R(a,r,o),o=S(n),a=S(r),t.modifiersData[e]={referenceClippingOffsets:n,popperEscapeOffsets:r,isReferenceHidden:o,hasPopperEscaped:a},t.attributes.popper=Object.assign(Object.assign({},t.attributes.popper),{},{"data-popper-reference-hidden":o,"data-popper-escaped":a})}},ee=B({defaultModifiers:[_,U,F,X]}),te=[_,U,F,X,Y,K,Q,Z,$],ne=B({defaultModifiers:te});e.applyStyles=X,e.arrow=Z,e.computeStyles=F,e.createPopper=ne,e.createPopperLite=ee,e.defaultModifiers=te,e.detectOverflow=L,e.eventListeners=_,e.flip=K,e.hide=$,e.offset=Y,e.popperGenerator=B,e.popperOffsets=U,e.preventOverflow=Q,Object.defineProperty(e,"__esModule",{value:!0})}));
assets/js/tippy/dup-pro-tippy.css ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*! ================================================
2
+ * DUPLICATOR TIPPY STYLE
3
+ * Copyright:Snap Creek LLC 2015-2021
4
+ * ================================================ */
5
+
6
+ .tippy-box[data-theme~='duplicator'],
7
+ .tippy-box[data-theme~='duplicator-filled'] {
8
+ background-color: white;
9
+ color: black;
10
+ font-size: 12px;
11
+ border-width: 1px;
12
+ border-style: solid;
13
+ border-radius: 0;
14
+ padding: 0;
15
+ max-width: 250px;
16
+ }
17
+
18
+ .tippy-box[data-theme~='duplicator'] .tippy-content,
19
+ .tippy-box[data-theme~='duplicator-filled'] .tippy-content {
20
+ padding: 0;
21
+ }
22
+
23
+ .tippy-box[data-theme~='duplicator'] h3,
24
+ .tippy-box[data-theme~='duplicator-filled'] h3 {
25
+ display: block;
26
+ box-sizing: border-box;
27
+ margin: 0;
28
+ width: 100%;
29
+ padding: 5px;
30
+ font-weight: bold;
31
+ font-size: 12px;
32
+ }
33
+
34
+ .tippy-box[data-theme~='duplicator'] .dup-tippy-content,
35
+ .tippy-box[data-theme~='duplicator-filled'] .dup-tippy-content {
36
+ padding: 5px;
37
+ }
38
+
39
+ .tippy-box[data-theme~='duplicator'] .dup-tippy-content *:first-child,
40
+ .tippy-box[data-theme~='duplicator-filled'] .dup-tippy-content *:first-child {
41
+ margin-top: 0;
42
+ }
43
+
44
+ .tippy-box[data-theme~='duplicator'] .dup-tippy-content *:last-child,
45
+ .tippy-box[data-theme~='duplicator-filled'] .dup-tippy-content *:last-child {
46
+ margin-bottom: 0;
47
+ }
48
+
49
+ .tippy-box[data-placement^='top']>.tippy-arrow::before {
50
+ bottom: -9px;
51
+ }
52
+
53
+ .tippy-box[data-placement^='bottom']>.tippy-arrow::before {
54
+ top: -9px;
55
+ }
56
+
57
+ .tippy-box[data-theme~='duplicator'],
58
+ .tippy-box[data-theme~='duplicator-filled'] {
59
+ border-color: #13659C;
60
+ }
61
+
62
+ .tippy-box[data-theme~='duplicator'] h3,
63
+ .tippy-box[data-theme~='duplicato-filled'] h3 {
64
+ background-color: #13659C;
65
+ color: white;
66
+ }
67
+
68
+ .tippy-box[data-theme~='duplicator-filled'] .tippy-content {
69
+ background-color: #13659C;
70
+ color: white;
71
+ }
72
+
73
+ .tippy-box[data-theme~='duplicator'][data-placement^='top']>.tippy-arrow::before,
74
+ .tippy-box[data-theme~='duplicator-filled'][data-placement^='top']>.tippy-arrow::before {
75
+ border-top-color: #13659C;
76
+ }
77
+
78
+ .tippy-box[data-theme~='duplicator'][data-placement^='bottom']>.tippy-arrow::before,
79
+ .tippy-box[data-theme~='duplicator-filled'][data-placement^='bottom']>.tippy-arrow::before {
80
+ border-bottom-color: #13659C;
81
+ }
82
+
83
+ .tippy-box[data-theme~='duplicator'][data-placement^='left']>.tippy-arrow::before,
84
+ .tippy-box[data-theme~='duplicator-filled'][data-placement^='left']>.tippy-arrow::before {
85
+ border-left-color: #13659C;
86
+ }
87
+
88
+ .tippy-box[data-theme~='duplicator'][data-placement^='right']>.tippy-arrow::before,
89
+ .tippy-box[data-theme~='duplicator-filled'][data-placement^='right']>.tippy-arrow::before {
90
+ border-right-color: #13659C;
91
+ }
assets/js/tippy/index.php ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <?php
2
+
3
+ //silent
assets/js/tippy/tippy-bundle.umd.js ADDED
@@ -0,0 +1,2509 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * tippy.js v6.2.7
3
+ * (c) 2017-2020 atomiks
4
+ * MIT License
5
+ */
6
+ (function (global, factory) {
7
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@popperjs/core')) :
8
+ typeof define === 'function' && define.amd ? define(['@popperjs/core'], factory) :
9
+ (global = global || self, global.tippy = factory(global.Popper));
10
+ }(this, (function (core) {
11
+ 'use strict';
12
+
13
+ var css = ".tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:\"\";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1}";
14
+
15
+ function injectCSS(css)
16
+ {
17
+ var style = document.createElement('style');
18
+ style.textContent = css;
19
+ style.setAttribute('data-tippy-stylesheet', '');
20
+ var head = document.head;
21
+ var firstStyleOrLinkTag = document.querySelector('head>style,head>link');
22
+
23
+ if (firstStyleOrLinkTag) {
24
+ head.insertBefore(style, firstStyleOrLinkTag);
25
+ } else {
26
+ head.appendChild(style);
27
+ }
28
+ }
29
+
30
+ var isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
31
+ var ua = isBrowser ? navigator.userAgent : '';
32
+ var isIE = /MSIE |Trident\//.test(ua);
33
+
34
+ var ROUND_ARROW = '<svg width="16" height="6" xmlns="http://www.w3.org/2000/svg"><path d="M0 6s1.796-.013 4.67-3.615C5.851.9 6.93.006 8 0c1.07-.006 2.148.887 3.343 2.385C14.233 6.005 16 6 16 6H0z"></svg>';
35
+ var BOX_CLASS = "tippy-box";
36
+ var CONTENT_CLASS = "tippy-content";
37
+ var BACKDROP_CLASS = "tippy-backdrop";
38
+ var ARROW_CLASS = "tippy-arrow";
39
+ var SVG_ARROW_CLASS = "tippy-svg-arrow";
40
+ var TOUCH_OPTIONS = {
41
+ passive: true,
42
+ capture: true
43
+ };
44
+
45
+ function hasOwnProperty(obj, key)
46
+ {
47
+ return {}.hasOwnProperty.call(obj, key);
48
+ }
49
+ function getValueAtIndexOrReturn(value, index, defaultValue)
50
+ {
51
+ if (Array.isArray(value)) {
52
+ var v = value[index];
53
+ return v == null ? Array.isArray(defaultValue) ? defaultValue[index] : defaultValue : v;
54
+ }
55
+
56
+ return value;
57
+ }
58
+ function isType(value, type)
59
+ {
60
+ var str = {}.toString.call(value);
61
+ return str.indexOf('[object') === 0 && str.indexOf(type + "]") > -1;
62
+ }
63
+ function invokeWithArgsOrReturn(value, args)
64
+ {
65
+ return typeof value === 'function' ? value.apply(void 0, args) : value;
66
+ }
67
+ function debounce(fn, ms)
68
+ {
69
+ // Avoid wrapping in `setTimeout` if ms is 0 anyway
70
+ if (ms === 0) {
71
+ return fn;
72
+ }
73
+
74
+ var timeout;
75
+ return function (arg) {
76
+ clearTimeout(timeout);
77
+ timeout = setTimeout(function () {
78
+ fn(arg);
79
+ }, ms);
80
+ };
81
+ }
82
+ function removeProperties(obj, keys)
83
+ {
84
+ var clone = Object.assign({}, obj);
85
+ keys.forEach(function (key) {
86
+ delete clone[key];
87
+ });
88
+ return clone;
89
+ }
90
+ function splitBySpaces(value)
91
+ {
92
+ return value.split(/\s+/).filter(Boolean);
93
+ }
94
+ function normalizeToArray(value)
95
+ {
96
+ return [].concat(value);
97
+ }
98
+ function pushIfUnique(arr, value)
99
+ {
100
+ if (arr.indexOf(value) === -1) {
101
+ arr.push(value);
102
+ }
103
+ }
104
+ function unique(arr)
105
+ {
106
+ return arr.filter(function (item, index) {
107
+ return arr.indexOf(item) === index;
108
+ });
109
+ }
110
+ function getBasePlacement(placement)
111
+ {
112
+ return placement.split('-')[0];
113
+ }
114
+ function arrayFrom(value)
115
+ {
116
+ return [].slice.call(value);
117
+ }
118
+ function removeUndefinedProps(obj)
119
+ {
120
+ return Object.keys(obj).reduce(function (acc, key) {
121
+ if (obj[key] !== undefined) {
122
+ acc[key] = obj[key];
123
+ }
124
+
125
+ return acc;
126
+ }, {});
127
+ }
128
+
129
+ function div()
130
+ {
131
+ return document.createElement('div');
132
+ }
133
+ function isElement(value)
134
+ {
135
+ return ['Element', 'Fragment'].some(function (type) {
136
+ return isType(value, type);
137
+ });
138
+ }
139
+ function isNodeList(value)
140
+ {
141
+ return isType(value, 'NodeList');
142
+ }
143
+ function isMouseEvent(value)
144
+ {
145
+ return isType(value, 'MouseEvent');
146
+ }
147
+ function isReferenceElement(value)
148
+ {
149
+ return !!(value && value._tippy && value._tippy.reference === value);
150
+ }
151
+ function getArrayOfElements(value)
152
+ {
153
+ if (isElement(value)) {
154
+ return [value];
155
+ }
156
+
157
+ if (isNodeList(value)) {
158
+ return arrayFrom(value);
159
+ }
160
+
161
+ if (Array.isArray(value)) {
162
+ return value;
163
+ }
164
+
165
+ return arrayFrom(document.querySelectorAll(value));
166
+ }
167
+ function setTransitionDuration(els, value)
168
+ {
169
+ els.forEach(function (el) {
170
+ if (el) {
171
+ el.style.transitionDuration = value + "ms";
172
+ }
173
+ });
174
+ }
175
+ function setVisibilityState(els, state)
176
+ {
177
+ els.forEach(function (el) {
178
+ if (el) {
179
+ el.setAttribute('data-state', state);
180
+ }
181
+ });
182
+ }
183
+ function getOwnerDocument(elementOrElements)
184
+ {
185
+ var _normalizeToArray = normalizeToArray(elementOrElements),
186
+ element = _normalizeToArray[0];
187
+
188
+ return element ? element.ownerDocument || document : document;
189
+ }
190
+ function isCursorOutsideInteractiveBorder(popperTreeData, event)
191
+ {
192
+ var clientX = event.clientX,
193
+ clientY = event.clientY;
194
+ return popperTreeData.every(function (_ref) {
195
+ var popperRect = _ref.popperRect,
196
+ popperState = _ref.popperState,
197
+ props = _ref.props;
198
+ var interactiveBorder = props.interactiveBorder;
199
+ var basePlacement = getBasePlacement(popperState.placement);
200
+ var offsetData = popperState.modifiersData.offset;
201
+
202
+ if (!offsetData) {
203
+ return true;
204
+ }
205
+
206
+ var topDistance = basePlacement === 'bottom' ? offsetData.top.y : 0;
207
+ var bottomDistance = basePlacement === 'top' ? offsetData.bottom.y : 0;
208
+ var leftDistance = basePlacement === 'right' ? offsetData.left.x : 0;
209
+ var rightDistance = basePlacement === 'left' ? offsetData.right.x : 0;
210
+ var exceedsTop = popperRect.top - clientY + topDistance > interactiveBorder;
211
+ var exceedsBottom = clientY - popperRect.bottom - bottomDistance > interactiveBorder;
212
+ var exceedsLeft = popperRect.left - clientX + leftDistance > interactiveBorder;
213
+ var exceedsRight = clientX - popperRect.right - rightDistance > interactiveBorder;
214
+ return exceedsTop || exceedsBottom || exceedsLeft || exceedsRight;
215
+ });
216
+ }
217
+ function updateTransitionEndListener(box, action, listener)
218
+ {
219
+ var method = action + "EventListener"; // some browsers apparently support `transition` (unprefixed) but only fire
220
+ // `webkitTransitionEnd`...
221
+
222
+ ['transitionend', 'webkitTransitionEnd'].forEach(function (event) {
223
+ box[method](event, listener);
224
+ });
225
+ }
226
+
227
+ var currentInput = {
228
+ isTouch: false
229
+ };
230
+ var lastMouseMoveTime = 0;
231
+ /**
232
+ * When a `touchstart` event is fired, it's assumed the user is using touch
233
+ * input. We'll bind a `mousemove` event listener to listen for mouse input in
234
+ * the future. This way, the `isTouch` property is fully dynamic and will handle
235
+ * hybrid devices that use a mix of touch + mouse input.
236
+ */
237
+
238
+ function onDocumentTouchStart()
239
+ {
240
+ if (currentInput.isTouch) {
241
+ return;
242
+ }
243
+
244
+ currentInput.isTouch = true;
245
+
246
+ if (window.performance) {
247
+ document.addEventListener('mousemove', onDocumentMouseMove);
248
+ }
249
+ }
250
+ /**
251
+ * When two `mousemove` event are fired consecutively within 20ms, it's assumed
252
+ * the user is using mouse input again. `mousemove` can fire on touch devices as
253
+ * well, but very rarely that quickly.
254
+ */
255
+
256
+ function onDocumentMouseMove()
257
+ {
258
+ var now = performance.now();
259
+
260
+ if (now - lastMouseMoveTime < 20) {
261
+ currentInput.isTouch = false;
262
+ document.removeEventListener('mousemove', onDocumentMouseMove);
263
+ }
264
+
265
+ lastMouseMoveTime = now;
266
+ }
267
+ /**
268
+ * When an element is in focus and has a tippy, leaving the tab/window and
269
+ * returning causes it to show again. For mouse users this is unexpected, but
270
+ * for keyboard use it makes sense.
271
+ * TODO: find a better technique to solve this problem
272
+ */
273
+
274
+ function onWindowBlur()
275
+ {
276
+ var activeElement = document.activeElement;
277
+
278
+ if (isReferenceElement(activeElement)) {
279
+ var instance = activeElement._tippy;
280
+
281
+ if (activeElement.blur && !instance.state.isVisible) {
282
+ activeElement.blur();
283
+ }
284
+ }
285
+ }
286
+ function bindGlobalEventListeners()
287
+ {
288
+ document.addEventListener('touchstart', onDocumentTouchStart, TOUCH_OPTIONS);
289
+ window.addEventListener('blur', onWindowBlur);
290
+ }
291
+
292
+ function createMemoryLeakWarning(method)
293
+ {
294
+ var txt = method === 'destroy' ? 'n already-' : ' ';
295
+ return [method + "() was called on a" + txt + "destroyed instance. This is a no-op but", 'indicates a potential memory leak.'].join(' ');
296
+ }
297
+ function clean(value)
298
+ {
299
+ var spacesAndTabs = /[ \t]{2,}/g;
300
+ var lineStartWithSpaces = /^[ \t]*/gm;
301
+ return value.replace(spacesAndTabs, ' ').replace(lineStartWithSpaces, '').trim();
302
+ }
303
+
304
+ function getDevMessage(message)
305
+ {
306
+ return clean("\n %ctippy.js\n\n %c" + clean(message) + "\n\n %c\uD83D\uDC77\u200D This is a development-only message. It will be removed in production.\n ");
307
+ }
308
+
309
+ function getFormattedMessage(message)
310
+ {
311
+ return [getDevMessage(message), // title
312
+ 'color: #00C584; font-size: 1.3em; font-weight: bold;', // message
313
+ 'line-height: 1.5', // footer
314
+ 'color: #a6a095;'];
315
+ } // Assume warnings and errors never have the same message
316
+
317
+ var visitedMessages;
318
+
319
+ {
320
+ resetVisitedMessages();
321
+ }
322
+
323
+ function resetVisitedMessages()
324
+ {
325
+ visitedMessages = new Set();
326
+ }
327
+ function warnWhen(condition, message)
328
+ {
329
+ if (condition && !visitedMessages.has(message)) {
330
+ var _console;
331
+
332
+ visitedMessages.add(message);
333
+
334
+ (_console = console).warn.apply(_console, getFormattedMessage(message));
335
+ }
336
+ }
337
+ function errorWhen(condition, message)
338
+ {
339
+ if (condition && !visitedMessages.has(message)) {
340
+ var _console2;
341
+
342
+ visitedMessages.add(message);
343
+
344
+ (_console2 = console).error.apply(_console2, getFormattedMessage(message));
345
+ }
346
+ }
347
+ function validateTargets(targets)
348
+ {
349
+ var didPassFalsyValue = !targets;
350
+ var didPassPlainObject = Object.prototype.toString.call(targets) === '[object Object]' && !targets.addEventListener;
351
+ errorWhen(didPassFalsyValue, ['tippy() was passed', '`' + String(targets) + '`', 'as its targets (first) argument. Valid types are: String, Element,', 'Element[], or NodeList.'].join(' '));
352
+ errorWhen(didPassPlainObject, ['tippy() was passed a plain object which is not supported as an argument', 'for virtual positioning. Use props.getReferenceClientRect instead.'].join(' '));
353
+ }
354
+
355
+ var pluginProps = {
356
+ animateFill: false,
357
+ followCursor: false,
358
+ inlinePositioning: false,
359
+ sticky: false
360
+ };
361
+ var renderProps = {
362
+ allowHTML: false,
363
+ animation: 'fade',
364
+ arrow: true,
365
+ content: '',
366
+ inertia: false,
367
+ maxWidth: 350,
368
+ role: 'tooltip',
369
+ theme: '',
370
+ zIndex: 9999
371
+ };
372
+ var defaultProps = Object.assign({
373
+ appendTo: function appendTo()
374
+ {
375
+ return document.body;
376
+ },
377
+ aria: {
378
+ content: 'auto',
379
+ expanded: 'auto'
380
+ },
381
+ delay: 0,
382
+ duration: [300, 250],
383
+ getReferenceClientRect: null,
384
+ hideOnClick: true,
385
+ ignoreAttributes: false,
386
+ interactive: false,
387
+ interactiveBorder: 2,
388
+ interactiveDebounce: 0,
389
+ moveTransition: '',
390
+ offset: [0, 10],
391
+ onAfterUpdate: function onAfterUpdate()
392
+ {},
393
+ onBeforeUpdate: function onBeforeUpdate()
394
+ {},
395
+ onCreate: function onCreate()
396
+ {},
397
+ onDestroy: function onDestroy()
398
+ {},
399
+ onHidden: function onHidden()
400
+ {},
401
+ onHide: function onHide()
402
+ {},
403
+ onMount: function onMount()
404
+ {},
405
+ onShow: function onShow()
406
+ {},
407
+ onShown: function onShown()
408
+ {},
409
+ onTrigger: function onTrigger()
410
+ {},
411
+ onUntrigger: function onUntrigger()
412
+ {},
413
+ onClickOutside: function onClickOutside()
414
+ {},
415
+ placement: 'top',
416
+ plugins: [],
417
+ popperOptions: {},
418
+ render: null,
419
+ showOnCreate: false,
420
+ touch: true,
421
+ trigger: 'mouseenter focus',
422
+ triggerTarget: null
423
+ }, pluginProps, {}, renderProps);
424
+ var defaultKeys = Object.keys(defaultProps);
425
+ var setDefaultProps = function setDefaultProps(partialProps)
426
+ {
427
+ /* istanbul ignore else */
428
+ {
429
+ validateProps(partialProps, []);
430
+ }
431
+
432
+ var keys = Object.keys(partialProps);
433
+ keys.forEach(function (key) {
434
+ defaultProps[key] = partialProps[key];
435
+ });
436
+ };
437
+ function getExtendedPassedProps(passedProps)
438
+ {
439
+ var plugins = passedProps.plugins || [];
440
+ var pluginProps = plugins.reduce(function (acc, plugin) {
441
+ var name = plugin.name,
442
+ defaultValue = plugin.defaultValue;
443
+
444
+ if (name) {
445
+ acc[name] = passedProps[name] !== undefined ? passedProps[name] : defaultValue;
446
+ }
447
+
448
+ return acc;
449
+ }, {});
450
+ return Object.assign({}, passedProps, {}, pluginProps);
451
+ }
452
+ function getDataAttributeProps(reference, plugins)
453
+ {
454
+ var propKeys = plugins ? Object.keys(getExtendedPassedProps(Object.assign({}, defaultProps, {
455
+ plugins: plugins
456
+ }))) : defaultKeys;
457
+ var props = propKeys.reduce(function (acc, key) {
458
+ var valueAsString = (reference.getAttribute("data-tippy-" + key) || '').trim();
459
+
460
+ if (!valueAsString) {
461
+ return acc;
462
+ }
463
+
464
+ if (key === 'content') {
465
+ acc[key] = valueAsString;
466
+ } else {
467
+ try {
468
+ acc[key] = JSON.parse(valueAsString);
469
+ } catch (e) {
470
+ acc[key] = valueAsString;
471
+ }
472
+ }
473
+
474
+ return acc;
475
+ }, {});
476
+ return props;
477
+ }
478
+ function evaluateProps(reference, props)
479
+ {
480
+ var out = Object.assign({}, props, {
481
+ content: invokeWithArgsOrReturn(props.content, [reference])
482
+ }, props.ignoreAttributes ? {} : getDataAttributeProps(reference, props.plugins));
483
+ out.aria = Object.assign({}, defaultProps.aria, {}, out.aria);
484
+ out.aria = {
485
+ expanded: out.aria.expanded === 'auto' ? props.interactive : out.aria.expanded,
486
+ content: out.aria.content === 'auto' ? props.interactive ? null : 'describedby' : out.aria.content
487
+ };
488
+ return out;
489
+ }
490
+ function validateProps(partialProps, plugins)
491
+ {
492
+ if (partialProps === void 0) {
493
+ partialProps = {};
494
+ }
495
+
496
+ if (plugins === void 0) {
497
+ plugins = [];
498
+ }
499
+
500
+ var keys = Object.keys(partialProps);
501
+ keys.forEach(function (prop) {
502
+ var nonPluginProps = removeProperties(defaultProps, Object.keys(pluginProps));
503
+ var didPassUnknownProp = !hasOwnProperty(nonPluginProps, prop); // Check if the prop exists in `plugins`
504
+
505
+ if (didPassUnknownProp) {
506
+ didPassUnknownProp = plugins.filter(function (plugin) {
507
+ return plugin.name === prop;
508
+ }).length === 0;
509
+ }
510
+
511
+ warnWhen(didPassUnknownProp, ["`" + prop + "`", "is not a valid prop. You may have spelled it incorrectly, or if it's", 'a plugin, forgot to pass it in an array as props.plugins.', '\n\n', 'All props: https://atomiks.github.io/tippyjs/v6/all-props/\n', 'Plugins: https://atomiks.github.io/tippyjs/v6/plugins/'].join(' '));
512
+ });
513
+ }
514
+
515
+ var innerHTML = function innerHTML()
516
+ {
517
+ return 'innerHTML';
518
+ };
519
+
520
+ function dangerouslySetInnerHTML(element, html)
521
+ {
522
+ element[innerHTML()] = html;
523
+ }
524
+
525
+ function createArrowElement(value)
526
+ {
527
+ var arrow = div();
528
+
529
+ if (value === true) {
530
+ arrow.className = ARROW_CLASS;
531
+ } else {
532
+ arrow.className = SVG_ARROW_CLASS;
533
+
534
+ if (isElement(value)) {
535
+ arrow.appendChild(value);
536
+ } else {
537
+ dangerouslySetInnerHTML(arrow, value);
538
+ }
539
+ }
540
+
541
+ return arrow;
542
+ }
543
+
544
+ function setContent(content, props)
545
+ {
546
+ if (isElement(props.content)) {
547
+ dangerouslySetInnerHTML(content, '');
548
+ content.appendChild(props.content);
549
+ } else if (typeof props.content !== 'function') {
550
+ if (props.allowHTML) {
551
+ dangerouslySetInnerHTML(content, props.content);
552
+ } else {
553
+ content.textContent = props.content;
554
+ }
555
+ }
556
+ }
557
+ function getChildren(popper)
558
+ {
559
+ var box = popper.firstElementChild;
560
+ var boxChildren = arrayFrom(box.children);
561
+ return {
562
+ box: box,
563
+ content: boxChildren.find(function (node) {
564
+ return node.classList.contains(CONTENT_CLASS);
565
+ }),
566
+ arrow: boxChildren.find(function (node) {
567
+ return node.classList.contains(ARROW_CLASS) || node.classList.contains(SVG_ARROW_CLASS);
568
+ }),
569
+ backdrop: boxChildren.find(function (node) {
570
+ return node.classList.contains(BACKDROP_CLASS);
571
+ })
572
+ };
573
+ }
574
+ function render(instance)
575
+ {
576
+ var popper = div();
577
+ var box = div();
578
+ box.className = BOX_CLASS;
579
+ box.setAttribute('data-state', 'hidden');
580
+ box.setAttribute('tabindex', '-1');
581
+ var content = div();
582
+ content.className = CONTENT_CLASS;
583
+ content.setAttribute('data-state', 'hidden');
584
+ setContent(content, instance.props);
585
+ popper.appendChild(box);
586
+ box.appendChild(content);
587
+ onUpdate(instance.props, instance.props);
588
+
589
+ function onUpdate(prevProps, nextProps)
590
+ {
591
+ var _getChildren = getChildren(popper),
592
+ box = _getChildren.box,
593
+ content = _getChildren.content,
594
+ arrow = _getChildren.arrow;
595
+
596
+ if (nextProps.theme) {
597
+ box.setAttribute('data-theme', nextProps.theme);
598
+ } else {
599
+ box.removeAttribute('data-theme');
600
+ }
601
+
602
+ if (typeof nextProps.animation === 'string') {
603
+ box.setAttribute('data-animation', nextProps.animation);
604
+ } else {
605
+ box.removeAttribute('data-animation');
606
+ }
607
+
608
+ if (nextProps.inertia) {
609
+ box.setAttribute('data-inertia', '');
610
+ } else {
611
+ box.removeAttribute('data-inertia');
612
+ }
613
+
614
+ box.style.maxWidth = typeof nextProps.maxWidth === 'number' ? nextProps.maxWidth + "px" : nextProps.maxWidth;
615
+
616
+ if (nextProps.role) {
617
+ box.setAttribute('role', nextProps.role);
618
+ } else {
619
+ box.removeAttribute('role');
620
+ }
621
+
622
+ if (prevProps.content !== nextProps.content || prevProps.allowHTML !== nextProps.allowHTML) {
623
+ setContent(content, instance.props);
624
+ }
625
+
626
+ if (nextProps.arrow) {
627
+ if (!arrow) {
628
+ box.appendChild(createArrowElement(nextProps.arrow));
629
+ } else if (prevProps.arrow !== nextProps.arrow) {
630
+ box.removeChild(arrow);
631
+ box.appendChild(createArrowElement(nextProps.arrow));
632
+ }
633
+ } else if (arrow) {
634
+ box.removeChild(arrow);
635
+ }
636
+ }
637
+
638
+ return {
639
+ popper: popper,
640
+ onUpdate: onUpdate
641
+ };
642
+ } // Runtime check to identify if the render function is the default one; this
643
+ // way we can apply default CSS transitions logic and it can be tree-shaken away
644
+
645
+ render.$$tippy = true;
646
+
647
+ var idCounter = 1;
648
+ var mouseMoveListeners = []; // Used by `hideAll()`
649
+
650
+ var mountedInstances = [];
651
+ function createTippy(reference, passedProps)
652
+ {
653
+ var props = evaluateProps(reference, Object.assign({}, defaultProps, {}, getExtendedPassedProps(removeUndefinedProps(passedProps)))); // ===========================================================================
654
+ // 🔒 Private members
655
+ // ===========================================================================
656
+
657
+ var showTimeout;
658
+ var hideTimeout;
659
+ var scheduleHideAnimationFrame;
660
+ var isVisibleFromClick = false;
661
+ var didHideDueToDocumentMouseDown = false;
662
+ var didTouchMove = false;
663
+ var ignoreOnFirstUpdate = false;
664
+ var lastTriggerEvent;
665
+ var currentTransitionEndListener;
666
+ var onFirstUpdate;
667
+ var listeners = [];
668
+ var debouncedOnMouseMove = debounce(onMouseMove, props.interactiveDebounce);
669
+ var currentTarget; // ===========================================================================
670
+ // 🔑 Public members
671
+ // ===========================================================================
672
+
673
+ var id = idCounter++;
674
+ var popperInstance = null;
675
+ var plugins = unique(props.plugins);
676
+ var state = {
677
+ // Is the instance currently enabled?
678
+ isEnabled: true,
679
+ // Is the tippy currently showing and not transitioning out?
680
+ isVisible: false,
681
+ // Has the instance been destroyed?
682
+ isDestroyed: false,
683
+ // Is the tippy currently mounted to the DOM?
684
+ isMounted: false,
685
+ // Has the tippy finished transitioning in?
686
+ isShown: false
687
+ };
688
+ var instance = {
689
+ // properties
690
+ id: id,
691
+ reference: reference,
692
+ popper: div(),
693
+ popperInstance: popperInstance,
694
+ props: props,
695
+ state: state,
696
+ plugins: plugins,
697
+ // methods
698
+ clearDelayTimeouts: clearDelayTimeouts,
699
+ setProps: setProps,
700
+ setContent: setContent,
701
+ show: show,
702
+ hide: hide,
703
+ hideWithInteractivity: hideWithInteractivity,
704
+ enable: enable,
705
+ disable: disable,
706
+ unmount: unmount,
707
+ destroy: destroy
708
+ }; // TODO: Investigate why this early return causes a TDZ error in the tests —
709
+ // it doesn't seem to happen in the browser
710
+
711
+ /* istanbul ignore if */
712
+
713
+ if (!props.render) {
714
+ {
715
+ errorWhen(true, 'render() function has not been supplied.');
716
+ }
717
+
718
+ return instance;
719
+ } // ===========================================================================
720
+ // Initial mutations
721
+ // ===========================================================================
722
+
723
+
724
+ var _props$render = props.render(instance),
725
+ popper = _props$render.popper,
726
+ onUpdate = _props$render.onUpdate;
727
+
728
+ popper.setAttribute('data-tippy-root', '');
729
+ popper.id = "tippy-" + instance.id;
730
+ instance.popper = popper;
731
+ reference._tippy = instance;
732
+ popper._tippy = instance;
733
+ var pluginsHooks = plugins.map(function (plugin) {
734
+ return plugin.fn(instance);
735
+ });
736
+ var hasAriaExpanded = reference.hasAttribute('aria-expanded');
737
+ addListeners();
738
+ handleAriaExpandedAttribute();
739
+ handleStyles();
740
+ invokeHook('onCreate', [instance]);
741
+
742
+ if (props.showOnCreate) {
743
+ scheduleShow();
744
+ } // Prevent a tippy with a delay from hiding if the cursor left then returned
745
+ // before it started hiding
746
+
747
+
748
+ popper.addEventListener('mouseenter', function () {
749
+ if (instance.props.interactive && instance.state.isVisible) {
750
+ instance.clearDelayTimeouts();
751
+ }
752
+ });
753
+ popper.addEventListener('mouseleave', function (event) {
754
+ if (instance.props.interactive && instance.props.trigger.indexOf('mouseenter') >= 0) {
755
+ getDocument().addEventListener('mousemove', debouncedOnMouseMove);
756
+ debouncedOnMouseMove(event);
757
+ }
758
+ });
759
+ return instance; // ===========================================================================
760
+ // 🔒 Private methods
761
+ // ===========================================================================
762
+
763
+ function getNormalizedTouchSettings()
764
+ {
765
+ var touch = instance.props.touch;
766
+ return Array.isArray(touch) ? touch : [touch, 0];
767
+ }
768
+
769
+ function getIsCustomTouchBehavior()
770
+ {
771
+ return getNormalizedTouchSettings()[0] === 'hold';
772
+ }
773
+
774
+ function getIsDefaultRenderFn()
775
+ {
776
+ var _instance$props$rende;
777
+
778
+ // @ts-ignore
779
+ return !!((_instance$props$rende = instance.props.render) == null ? void 0 : _instance$props$rende.$$tippy);
780
+ }
781
+
782
+ function getCurrentTarget()
783
+ {
784
+ return currentTarget || reference;
785
+ }
786
+
787
+ function getDocument()
788
+ {
789
+ var parent = getCurrentTarget().parentNode;
790
+ return parent ? getOwnerDocument(parent) : document;
791
+ }
792
+
793
+ function getDefaultTemplateChildren()
794
+ {
795
+ return getChildren(popper);
796
+ }
797
+
798
+ function getDelay(isShow)
799
+ {
800
+ // For touch or keyboard input, force `0` delay for UX reasons
801
+ // Also if the instance is mounted but not visible (transitioning out),
802
+ // ignore delay
803
+ if (instance.state.isMounted && !instance.state.isVisible || currentInput.isTouch || lastTriggerEvent && lastTriggerEvent.type === 'focus') {
804
+ return 0;
805
+ }
806
+
807
+ return getValueAtIndexOrReturn(instance.props.delay, isShow ? 0 : 1, defaultProps.delay);
808
+ }
809
+
810
+ function handleStyles()
811
+ {
812
+ popper.style.pointerEvents = instance.props.interactive && instance.state.isVisible ? '' : 'none';
813
+ popper.style.zIndex = "" + instance.props.zIndex;
814
+ }
815
+
816
+ function invokeHook(hook, args, shouldInvokePropsHook)
817
+ {
818
+ if (shouldInvokePropsHook === void 0) {
819
+ shouldInvokePropsHook = true;
820
+ }
821
+
822
+ pluginsHooks.forEach(function (pluginHooks) {
823
+ if (pluginHooks[hook]) {
824
+ pluginHooks[hook].apply(void 0, args);
825
+ }
826
+ });
827
+
828
+ if (shouldInvokePropsHook) {
829
+ var _instance$props;
830
+
831
+ (_instance$props = instance.props)[hook].apply(_instance$props, args);
832
+ }
833
+ }
834
+
835
+ function handleAriaContentAttribute()
836
+ {
837
+ var aria = instance.props.aria;
838
+
839
+ if (!aria.content) {
840
+ return;
841
+ }
842
+
843
+ var attr = "aria-" + aria.content;
844
+ var id = popper.id;
845
+ var nodes = normalizeToArray(instance.props.triggerTarget || reference);
846
+ nodes.forEach(function (node) {
847
+ var currentValue = node.getAttribute(attr);
848
+
849
+ if (instance.state.isVisible) {
850
+ node.setAttribute(attr, currentValue ? currentValue + " " + id : id);
851
+ } else {
852
+ var nextValue = currentValue && currentValue.replace(id, '').trim();
853
+
854
+ if (nextValue) {
855
+ node.setAttribute(attr, nextValue);
856
+ } else {
857
+ node.removeAttribute(attr);
858
+ }
859
+ }
860
+ });
861
+ }
862
+
863
+ function handleAriaExpandedAttribute()
864
+ {
865
+ if (hasAriaExpanded || !instance.props.aria.expanded) {
866
+ return;
867
+ }
868
+
869
+ var nodes = normalizeToArray(instance.props.triggerTarget || reference);
870
+ nodes.forEach(function (node) {
871
+ if (instance.props.interactive) {
872
+ node.setAttribute('aria-expanded', instance.state.isVisible && node === getCurrentTarget() ? 'true' : 'false');
873
+ } else {
874
+ node.removeAttribute('aria-expanded');
875
+ }
876
+ });
877
+ }
878
+
879
+ function cleanupInteractiveMouseListeners()
880
+ {
881
+ getDocument().removeEventListener('mousemove', debouncedOnMouseMove);
882
+ mouseMoveListeners = mouseMoveListeners.filter(function (listener) {
883
+ return listener !== debouncedOnMouseMove;
884
+ });
885
+ }
886
+
887
+ function onDocumentPress(event)
888
+ {
889
+ // Moved finger to scroll instead of an intentional tap outside
890
+ if (currentInput.isTouch) {
891
+ if (didTouchMove || event.type === 'mousedown') {
892
+ return;
893
+ }
894
+ } // Clicked on interactive popper
895
+
896
+
897
+ if (instance.props.interactive && popper.contains(event.target)) {
898
+ return;
899
+ } // Clicked on the event listeners target
900
+
901
+
902
+ if (getCurrentTarget().contains(event.target)) {
903
+ if (currentInput.isTouch) {
904
+ return;
905
+ }
906
+
907
+ if (instance.state.isVisible && instance.props.trigger.indexOf('click') >= 0) {
908
+ return;
909
+ }
910
+ } else {
911
+ invokeHook('onClickOutside', [instance, event]);
912
+ }
913
+
914
+ if (instance.props.hideOnClick === true) {
915
+ instance.clearDelayTimeouts();
916
+ instance.hide(); // `mousedown` event is fired right before `focus` if pressing the
917
+ // currentTarget. This lets a tippy with `focus` trigger know that it
918
+ // should not show
919
+
920
+ didHideDueToDocumentMouseDown = true;
921
+ setTimeout(function () {
922
+ didHideDueToDocumentMouseDown = false;
923
+ }); // The listener gets added in `scheduleShow()`, but this may be hiding it
924
+ // before it shows, and hide()'s early bail-out behavior can prevent it
925
+ // from being cleaned up
926
+
927
+ if (!instance.state.isMounted) {
928
+ removeDocumentPress();
929
+ }
930
+ }
931
+ }
932
+
933
+ function onTouchMove()
934
+ {
935
+ didTouchMove = true;
936
+ }
937
+
938
+ function onTouchStart()
939
+ {
940
+ didTouchMove = false;
941
+ }
942
+
943
+ function addDocumentPress()
944
+ {
945
+ var doc = getDocument();
946
+ doc.addEventListener('mousedown', onDocumentPress, true);
947
+ doc.addEventListener('touchend', onDocumentPress, TOUCH_OPTIONS);
948
+ doc.addEventListener('touchstart', onTouchStart, TOUCH_OPTIONS);
949
+ doc.addEventListener('touchmove', onTouchMove, TOUCH_OPTIONS);
950
+ }
951
+
952
+ function removeDocumentPress()
953
+ {
954
+ var doc = getDocument();
955
+ doc.removeEventListener('mousedown', onDocumentPress, true);
956
+ doc.removeEventListener('touchend', onDocumentPress, TOUCH_OPTIONS);
957
+ doc.removeEventListener('touchstart', onTouchStart, TOUCH_OPTIONS);
958
+ doc.removeEventListener('touchmove', onTouchMove, TOUCH_OPTIONS);
959
+ }
960
+
961
+ function onTransitionedOut(duration, callback)
962
+ {
963
+ onTransitionEnd(duration, function () {
964
+ if (!instance.state.isVisible && popper.parentNode && popper.parentNode.contains(popper)) {
965
+ callback();
966
+ }
967
+ });
968
+ }
969
+
970
+ function onTransitionedIn(duration, callback)
971
+ {
972
+ onTransitionEnd(duration, callback);
973
+ }
974
+
975
+ function onTransitionEnd(duration, callback)
976
+ {
977
+ var box = getDefaultTemplateChildren().box;
978
+
979
+ function listener(event)
980
+ {
981
+ if (event.target === box) {
982
+ updateTransitionEndListener(box, 'remove', listener);
983
+ callback();
984
+ }
985
+ } // Make callback synchronous if duration is 0
986
+ // `transitionend` won't fire otherwise
987
+
988
+
989
+ if (duration === 0) {
990
+ return callback();
991
+ }
992
+
993
+ updateTransitionEndListener(box, 'remove', currentTransitionEndListener);
994
+ updateTransitionEndListener(box, 'add', listener);
995
+ currentTransitionEndListener = listener;
996
+ }
997
+
998
+ function on(eventType, handler, options)
999
+ {
1000
+ if (options === void 0) {
1001
+ options = false;
1002
+ }
1003
+
1004
+ var nodes = normalizeToArray(instance.props.triggerTarget || reference);
1005
+ nodes.forEach(function (node) {
1006
+ node.addEventListener(eventType, handler, options);
1007
+ listeners.push({
1008
+ node: node,
1009
+ eventType: eventType,
1010
+ handler: handler,
1011
+ options: options
1012
+ });
1013
+ });
1014
+ }
1015
+
1016
+ function addListeners()
1017
+ {
1018
+ if (getIsCustomTouchBehavior()) {
1019
+ on('touchstart', onTrigger, {
1020
+ passive: true
1021
+ });
1022
+ on('touchend', onMouseLeave, {
1023
+ passive: true
1024
+ });
1025
+ }
1026
+
1027
+ splitBySpaces(instance.props.trigger).forEach(function (eventType) {
1028
+ if (eventType === 'manual') {
1029
+ return;
1030
+ }
1031
+
1032
+ on(eventType, onTrigger);
1033
+
1034
+ switch (eventType) {
1035
+ case 'mouseenter':
1036
+ on('mouseleave', onMouseLeave);
1037
+ break;
1038
+
1039
+ case 'focus':
1040
+ on(isIE ? 'focusout' : 'blur', onBlurOrFocusOut);
1041
+ break;
1042
+
1043
+ case 'focusin':
1044
+ on('focusout', onBlurOrFocusOut);
1045
+ break;
1046
+ }
1047
+ });
1048
+ }
1049
+
1050
+ function removeListeners()
1051
+ {
1052
+ listeners.forEach(function (_ref) {
1053
+ var node = _ref.node,
1054
+ eventType = _ref.eventType,
1055
+ handler = _ref.handler,
1056
+ options = _ref.options;
1057
+ node.removeEventListener(eventType, handler, options);
1058
+ });
1059
+ listeners = [];
1060
+ }
1061
+
1062
+ function onTrigger(event)
1063
+ {
1064
+ var _lastTriggerEvent;
1065
+
1066
+ var shouldScheduleClickHide = false;
1067
+
1068
+ if (!instance.state.isEnabled || isEventListenerStopped(event) || didHideDueToDocumentMouseDown) {
1069
+ return;
1070
+ }
1071
+
1072
+ var wasFocused = ((_lastTriggerEvent = lastTriggerEvent) == null ? void 0 : _lastTriggerEvent.type) === 'focus';
1073
+ lastTriggerEvent = event;
1074
+ currentTarget = event.currentTarget;
1075
+ handleAriaExpandedAttribute();
1076
+
1077
+ if (!instance.state.isVisible && isMouseEvent(event)) {
1078
+ // If scrolling, `mouseenter` events can be fired if the cursor lands
1079
+ // over a new target, but `mousemove` events don't get fired. This
1080
+ // causes interactive tooltips to get stuck open until the cursor is
1081
+ // moved
1082
+ mouseMoveListeners.forEach(function (listener) {
1083
+ return listener(event);
1084
+ });
1085
+ } // Toggle show/hide when clicking click-triggered tooltips
1086
+
1087
+
1088
+ if (event.type === 'click' && (instance.props.trigger.indexOf('mouseenter') < 0 || isVisibleFromClick) && instance.props.hideOnClick !== false && instance.state.isVisible) {
1089
+ shouldScheduleClickHide = true;
1090
+ } else {
1091
+ scheduleShow(event);
1092
+ }
1093
+
1094
+ if (event.type === 'click') {
1095
+ isVisibleFromClick = !shouldScheduleClickHide;
1096
+ }
1097
+
1098
+ if (shouldScheduleClickHide && !wasFocused) {
1099
+ scheduleHide(event);
1100
+ }
1101
+ }
1102
+
1103
+ function onMouseMove(event)
1104
+ {
1105
+ var target = event.target;
1106
+ var isCursorOverReferenceOrPopper = getCurrentTarget().contains(target) || popper.contains(target);
1107
+
1108
+ if (event.type === 'mousemove' && isCursorOverReferenceOrPopper) {
1109
+ return;
1110
+ }
1111
+
1112
+ var popperTreeData = getNestedPopperTree().concat(popper).map(function (popper) {
1113
+ var _instance$popperInsta;
1114
+
1115
+ var instance = popper._tippy;
1116
+ var state = (_instance$popperInsta = instance.popperInstance) == null ? void 0 : _instance$popperInsta.state;
1117
+
1118
+ if (state) {
1119
+ return {
1120
+ popperRect: popper.getBoundingClientRect(),
1121
+ popperState: state,
1122
+ props: props
1123
+ };
1124
+ }
1125
+
1126
+ return null;
1127
+ }).filter(Boolean);
1128
+
1129
+ if (isCursorOutsideInteractiveBorder(popperTreeData, event)) {
1130
+ cleanupInteractiveMouseListeners();
1131
+ scheduleHide(event);
1132
+ }
1133
+ }
1134
+
1135
+ function onMouseLeave(event)
1136
+ {
1137
+ var shouldBail = isEventListenerStopped(event) || instance.props.trigger.indexOf('click') >= 0 && isVisibleFromClick;
1138
+
1139
+ if (shouldBail) {
1140
+ return;
1141
+ }
1142
+
1143
+ if (instance.props.interactive) {
1144
+ instance.hideWithInteractivity(event);
1145
+ return;
1146
+ }
1147
+
1148
+ scheduleHide(event);
1149
+ }
1150
+
1151
+ function onBlurOrFocusOut(event)
1152
+ {
1153
+ if (instance.props.trigger.indexOf('focusin') < 0 && event.target !== getCurrentTarget()) {
1154
+ return;
1155
+ } // If focus was moved to within the popper
1156
+
1157
+
1158
+ if (instance.props.interactive && event.relatedTarget && popper.contains(event.relatedTarget)) {
1159
+ return;
1160
+ }
1161
+
1162
+ scheduleHide(event);
1163
+ }
1164
+
1165
+ function isEventListenerStopped(event)
1166
+ {
1167
+ return currentInput.isTouch ? getIsCustomTouchBehavior() !== event.type.indexOf('touch') >= 0 : false;
1168
+ }
1169
+
1170
+ function createPopperInstance()
1171
+ {
1172
+ destroyPopperInstance();
1173
+ var _instance$props2 = instance.props,
1174
+ popperOptions = _instance$props2.popperOptions,
1175
+ placement = _instance$props2.placement,
1176
+ offset = _instance$props2.offset,
1177
+ getReferenceClientRect = _instance$props2.getReferenceClientRect,
1178
+ moveTransition = _instance$props2.moveTransition;
1179
+ var arrow = getIsDefaultRenderFn() ? getChildren(popper).arrow : null;
1180
+ var computedReference = getReferenceClientRect ? {
1181
+ getBoundingClientRect: getReferenceClientRect,
1182
+ contextElement: getReferenceClientRect.contextElement || getCurrentTarget()
1183
+ } : reference;
1184
+ var tippyModifier = {
1185
+ name: '$$tippy',
1186
+ enabled: true,
1187
+ phase: 'beforeWrite',
1188
+ requires: ['computeStyles'],
1189
+ fn: function fn(_ref2)
1190
+ {
1191
+ var state = _ref2.state;
1192
+
1193
+ if (getIsDefaultRenderFn()) {
1194
+ var _getDefaultTemplateCh = getDefaultTemplateChildren(),
1195
+ box = _getDefaultTemplateCh.box;
1196
+
1197
+ ['placement', 'reference-hidden', 'escaped'].forEach(function (attr) {
1198
+ if (attr === 'placement') {
1199
+ box.setAttribute('data-placement', state.placement);
1200
+ } else {
1201
+ if (state.attributes.popper["data-popper-" + attr]) {
1202
+ box.setAttribute("data-" + attr, '');
1203
+ } else {
1204
+ box.removeAttribute("data-" + attr);
1205
+ }
1206
+ }
1207
+ });
1208
+ state.attributes.popper = {};
1209
+ }
1210
+ }
1211
+ };
1212
+ var modifiers = [{
1213
+ name: 'offset',
1214
+ options: {
1215
+ offset: offset
1216
+ }
1217
+ }, {
1218
+ name: 'preventOverflow',
1219
+ options: {
1220
+ padding: {
1221
+ top: 2,
1222
+ bottom: 2,
1223
+ left: 5,
1224
+ right: 5
1225
+ }
1226
+ }
1227
+ }, {
1228
+ name: 'flip',
1229
+ options: {
1230
+ padding: 5
1231
+ }
1232
+ }, {
1233
+ name: 'computeStyles',
1234
+ options: {
1235
+ adaptive: !moveTransition
1236
+ }
1237
+ }, tippyModifier];
1238
+
1239
+ if (getIsDefaultRenderFn() && arrow) {
1240
+ modifiers.push({
1241
+ name: 'arrow',
1242
+ options: {
1243
+ element: arrow,
1244
+ padding: 3
1245
+ }
1246
+ });
1247
+ }
1248
+
1249
+ modifiers.push.apply(modifiers, (popperOptions == null ? void 0 : popperOptions.modifiers) || []);
1250
+ instance.popperInstance = core.createPopper(computedReference, popper, Object.assign({}, popperOptions, {
1251
+ placement: placement,
1252
+ onFirstUpdate: onFirstUpdate,
1253
+ modifiers: modifiers
1254
+ }));
1255
+ }
1256
+
1257
+ function destroyPopperInstance()
1258
+ {
1259
+ if (instance.popperInstance) {
1260
+ instance.popperInstance.destroy();
1261
+ instance.popperInstance = null;
1262
+ }
1263
+ }
1264
+
1265
+ function mount()
1266
+ {
1267
+ var appendTo = instance.props.appendTo;
1268
+ var parentNode; // By default, we'll append the popper to the triggerTargets's parentNode so
1269
+ // it's directly after the reference element so the elements inside the
1270
+ // tippy can be tabbed to
1271
+ // If there are clipping issues, the user can specify a different appendTo
1272
+ // and ensure focus management is handled correctly manually
1273
+
1274
+ var node = getCurrentTarget();
1275
+
1276
+ if (instance.props.interactive && appendTo === defaultProps.appendTo || appendTo === 'parent') {
1277
+ parentNode = node.parentNode;
1278
+ } else {
1279
+ parentNode = invokeWithArgsOrReturn(appendTo, [node]);
1280
+ } // The popper element needs to exist on the DOM before its position can be
1281
+ // updated as Popper needs to read its dimensions
1282
+
1283
+
1284
+ if (!parentNode.contains(popper)) {
1285
+ parentNode.appendChild(popper);
1286
+ }
1287
+
1288
+ createPopperInstance();
1289
+ /* istanbul ignore else */
1290
+
1291
+ {
1292
+ // Accessibility check
1293
+ warnWhen(instance.props.interactive && appendTo === defaultProps.appendTo && node.nextElementSibling !== popper, ['Interactive tippy element may not be accessible via keyboard', 'navigation because it is not directly after the reference element', 'in the DOM source order.', '\n\n', 'Using a wrapper <div> or <span> tag around the reference element', 'solves this by creating a new parentNode context.', '\n\n', 'Specifying `appendTo: document.body` silences this warning, but it', 'assumes you are using a focus management solution to handle', 'keyboard navigation.', '\n\n', 'See: https://atomiks.github.io/tippyjs/v6/accessibility/#interactivity'].join(' '));
1294
+ }
1295
+ }
1296
+
1297
+ function getNestedPopperTree()
1298
+ {
1299
+ return arrayFrom(popper.querySelectorAll('[data-tippy-root]'));
1300
+ }
1301
+
1302
+ function scheduleShow(event)
1303
+ {
1304
+ instance.clearDelayTimeouts();
1305
+
1306
+ if (event) {
1307
+ invokeHook('onTrigger', [instance, event]);
1308
+ }
1309
+
1310
+ addDocumentPress();
1311
+ var delay = getDelay(true);
1312
+
1313
+ var _getNormalizedTouchSe = getNormalizedTouchSettings(),
1314
+ touchValue = _getNormalizedTouchSe[0],
1315
+ touchDelay = _getNormalizedTouchSe[1];
1316
+
1317
+ if (currentInput.isTouch && touchValue === 'hold' && touchDelay) {
1318
+ delay = touchDelay;
1319
+ }
1320
+
1321
+ if (delay) {
1322
+ showTimeout = setTimeout(function () {
1323
+ instance.show();
1324
+ }, delay);
1325
+ } else {
1326
+ instance.show();
1327
+ }
1328
+ }
1329
+
1330
+ function scheduleHide(event)
1331
+ {
1332
+ instance.clearDelayTimeouts();
1333
+ invokeHook('onUntrigger', [instance, event]);
1334
+
1335
+ if (!instance.state.isVisible) {
1336
+ removeDocumentPress();
1337
+ return;
1338
+ } // For interactive tippies, scheduleHide is added to a document.body handler
1339
+ // from onMouseLeave so must intercept scheduled hides from mousemove/leave
1340
+ // events when trigger contains mouseenter and click, and the tip is
1341
+ // currently shown as a result of a click.
1342
+
1343
+
1344
+ if (instance.props.trigger.indexOf('mouseenter') >= 0 && instance.props.trigger.indexOf('click') >= 0 && ['mouseleave', 'mousemove'].indexOf(event.type) >= 0 && isVisibleFromClick) {
1345
+ return;
1346
+ }
1347
+
1348
+ var delay = getDelay(false);
1349
+
1350
+ if (delay) {
1351
+ hideTimeout = setTimeout(function () {
1352
+ if (instance.state.isVisible) {
1353
+ instance.hide();
1354
+ }
1355
+ }, delay);
1356
+ } else {
1357
+ // Fixes a `transitionend` problem when it fires 1 frame too
1358
+ // late sometimes, we don't want hide() to be called.
1359
+ scheduleHideAnimationFrame = requestAnimationFrame(function () {
1360
+ instance.hide();
1361
+ });
1362
+ }
1363
+ } // ===========================================================================
1364
+ // 🔑 Public methods
1365
+ // ===========================================================================
1366
+
1367
+
1368
+ function enable()
1369
+ {
1370
+ instance.state.isEnabled = true;
1371
+ }
1372
+
1373
+ function disable()
1374
+ {
1375
+ // Disabling the instance should also hide it
1376
+ // https://github.com/atomiks/tippy.js-react/issues/106
1377
+ instance.hide();
1378
+ instance.state.isEnabled = false;
1379
+ }
1380
+
1381
+ function clearDelayTimeouts()
1382
+ {
1383
+ clearTimeout(showTimeout);
1384
+ clearTimeout(hideTimeout);
1385
+ cancelAnimationFrame(scheduleHideAnimationFrame);
1386
+ }
1387
+
1388
+ function setProps(partialProps)
1389
+ {
1390
+ /* istanbul ignore else */
1391
+ {
1392
+ warnWhen(instance.state.isDestroyed, createMemoryLeakWarning('setProps'));
1393
+ }
1394
+
1395
+ if (instance.state.isDestroyed) {
1396
+ return;
1397
+ }
1398
+
1399
+ invokeHook('onBeforeUpdate', [instance, partialProps]);
1400
+ removeListeners();
1401
+ var prevProps = instance.props;
1402
+ var nextProps = evaluateProps(reference, Object.assign({}, instance.props, {}, partialProps, {
1403
+ ignoreAttributes: true
1404
+ }));
1405
+ instance.props = nextProps;
1406
+ addListeners();
1407
+
1408
+ if (prevProps.interactiveDebounce !== nextProps.interactiveDebounce) {
1409
+ cleanupInteractiveMouseListeners();
1410
+ debouncedOnMouseMove = debounce(onMouseMove, nextProps.interactiveDebounce);
1411
+ } // Ensure stale aria-expanded attributes are removed
1412
+
1413
+
1414
+ if (prevProps.triggerTarget && !nextProps.triggerTarget) {
1415
+ normalizeToArray(prevProps.triggerTarget).forEach(function (node) {
1416
+ node.removeAttribute('aria-expanded');
1417
+ });
1418
+ } else if (nextProps.triggerTarget) {
1419
+ reference.removeAttribute('aria-expanded');
1420
+ }
1421
+
1422
+ handleAriaExpandedAttribute();
1423
+ handleStyles();
1424
+
1425
+ if (onUpdate) {
1426
+ onUpdate(prevProps, nextProps);
1427
+ }
1428
+
1429
+ if (instance.popperInstance) {
1430
+ createPopperInstance(); // Fixes an issue with nested tippies if they are all getting re-rendered,
1431
+ // and the nested ones get re-rendered first.
1432
+ // https://github.com/atomiks/tippyjs-react/issues/177
1433
+ // TODO: find a cleaner / more efficient solution(!)
1434
+
1435
+ getNestedPopperTree().forEach(function (nestedPopper) {
1436
+ // React (and other UI libs likely) requires a rAF wrapper as it flushes
1437
+ // its work in one
1438
+ requestAnimationFrame(nestedPopper._tippy.popperInstance.forceUpdate);
1439
+ });
1440
+ }
1441
+
1442
+ invokeHook('onAfterUpdate', [instance, partialProps]);
1443
+ }
1444
+
1445
+ function setContent(content)
1446
+ {
1447
+ instance.setProps({
1448
+ content: content
1449
+ });
1450
+ }
1451
+
1452
+ function show()
1453
+ {
1454
+ /* istanbul ignore else */
1455
+ {
1456
+ warnWhen(instance.state.isDestroyed, createMemoryLeakWarning('show'));
1457
+ } // Early bail-out
1458
+
1459
+
1460
+ var isAlreadyVisible = instance.state.isVisible;
1461
+ var isDestroyed = instance.state.isDestroyed;
1462
+ var isDisabled = !instance.state.isEnabled;
1463
+ var isTouchAndTouchDisabled = currentInput.isTouch && !instance.props.touch;
1464
+ var duration = getValueAtIndexOrReturn(instance.props.duration, 0, defaultProps.duration);
1465
+
1466
+ if (isAlreadyVisible || isDestroyed || isDisabled || isTouchAndTouchDisabled) {
1467
+ return;
1468
+ } // Normalize `disabled` behavior across browsers.
1469
+ // Firefox allows events on disabled elements, but Chrome doesn't.
1470
+ // Using a wrapper element (i.e. <span>) is recommended.
1471
+
1472
+
1473
+ if (getCurrentTarget().hasAttribute('disabled')) {
1474
+ return;
1475
+ }
1476
+
1477
+ invokeHook('onShow', [instance], false);
1478
+
1479
+ if (instance.props.onShow(instance) === false) {
1480
+ return;
1481
+ }
1482
+
1483
+ instance.state.isVisible = true;
1484
+
1485
+ if (getIsDefaultRenderFn()) {
1486
+ popper.style.visibility = 'visible';
1487
+ }
1488
+
1489
+ handleStyles();
1490
+ addDocumentPress();
1491
+
1492
+ if (!instance.state.isMounted) {
1493
+ popper.style.transition = 'none';
1494
+ } // If flipping to the opposite side after hiding at least once, the
1495
+ // animation will use the wrong placement without resetting the duration
1496
+
1497
+
1498
+ if (getIsDefaultRenderFn()) {
1499
+ var _getDefaultTemplateCh2 = getDefaultTemplateChildren(),
1500
+ box = _getDefaultTemplateCh2.box,
1501
+ content = _getDefaultTemplateCh2.content;
1502
+
1503
+ setTransitionDuration([box, content], 0);
1504
+ }
1505
+
1506
+ onFirstUpdate = function onFirstUpdate()
1507
+ {
1508
+ if (!instance.state.isVisible || ignoreOnFirstUpdate) {
1509
+ return;
1510
+ }
1511
+
1512
+ ignoreOnFirstUpdate = true; // reflow
1513
+
1514
+ void popper.offsetHeight;
1515
+ popper.style.transition = instance.props.moveTransition;
1516
+
1517
+ if (getIsDefaultRenderFn() && instance.props.animation) {
1518
+ var _getDefaultTemplateCh3 = getDefaultTemplateChildren(),
1519
+ _box = _getDefaultTemplateCh3.box,
1520
+ _content = _getDefaultTemplateCh3.content;
1521
+
1522
+ setTransitionDuration([_box, _content], duration);
1523
+ setVisibilityState([_box, _content], 'visible');
1524
+ }
1525
+
1526
+ handleAriaContentAttribute();
1527
+ handleAriaExpandedAttribute();
1528
+ pushIfUnique(mountedInstances, instance);
1529
+ instance.state.isMounted = true;
1530
+ invokeHook('onMount', [instance]);
1531
+
1532
+ if (instance.props.animation && getIsDefaultRenderFn()) {
1533
+ onTransitionedIn(duration, function () {
1534
+ instance.state.isShown = true;
1535
+ invokeHook('onShown', [instance]);
1536
+ });
1537
+ }
1538
+ };
1539
+
1540
+ mount();
1541
+ }
1542
+
1543
+ function hide()
1544
+ {
1545
+ /* istanbul ignore else */
1546
+ {
1547
+ warnWhen(instance.state.isDestroyed, createMemoryLeakWarning('hide'));
1548
+ } // Early bail-out
1549
+
1550
+
1551
+ var isAlreadyHidden = !instance.state.isVisible;
1552
+ var isDestroyed = instance.state.isDestroyed;
1553
+ var isDisabled = !instance.state.isEnabled;
1554
+ var duration = getValueAtIndexOrReturn(instance.props.duration, 1, defaultProps.duration);
1555
+
1556
+ if (isAlreadyHidden || isDestroyed || isDisabled) {
1557
+ return;
1558
+ }
1559
+
1560
+ invokeHook('onHide', [instance], false);
1561
+
1562
+ if (instance.props.onHide(instance) === false) {
1563
+ return;
1564
+ }
1565
+
1566
+ instance.state.isVisible = false;
1567
+ instance.state.isShown = false;
1568
+ ignoreOnFirstUpdate = false;
1569
+ isVisibleFromClick = false;
1570
+
1571
+ if (getIsDefaultRenderFn()) {
1572
+ popper.style.visibility = 'hidden';
1573
+ }
1574
+
1575
+ cleanupInteractiveMouseListeners();
1576
+ removeDocumentPress();
1577
+ handleStyles();
1578
+
1579
+ if (getIsDefaultRenderFn()) {
1580
+ var _getDefaultTemplateCh4 = getDefaultTemplateChildren(),
1581
+ box = _getDefaultTemplateCh4.box,
1582
+ content = _getDefaultTemplateCh4.content;
1583
+
1584
+ if (instance.props.animation) {
1585
+ setTransitionDuration([box, content], duration);
1586
+ setVisibilityState([box, content], 'hidden');
1587
+ }
1588
+ }
1589
+
1590
+ handleAriaContentAttribute();
1591
+ handleAriaExpandedAttribute();
1592
+
1593
+ if (instance.props.animation) {
1594
+ if (getIsDefaultRenderFn()) {
1595
+ onTransitionedOut(duration, instance.unmount);
1596
+ }
1597
+ } else {
1598
+ instance.unmount();
1599
+ }
1600
+ }
1601
+
1602
+ function hideWithInteractivity(event)
1603
+ {
1604
+ /* istanbul ignore else */
1605
+ {
1606
+ warnWhen(instance.state.isDestroyed, createMemoryLeakWarning('hideWithInteractivity'));
1607
+ }
1608
+
1609
+ getDocument().addEventListener('mousemove', debouncedOnMouseMove);
1610
+ pushIfUnique(mouseMoveListeners, debouncedOnMouseMove);
1611
+ debouncedOnMouseMove(event);
1612
+ }
1613
+
1614
+ function unmount()
1615
+ {
1616
+ /* istanbul ignore else */
1617
+ {
1618
+ warnWhen(instance.state.isDestroyed, createMemoryLeakWarning('unmount'));
1619
+ }
1620
+
1621
+ if (instance.state.isVisible) {
1622
+ instance.hide();
1623
+ }
1624
+
1625
+ if (!instance.state.isMounted) {
1626
+ return;
1627
+ }
1628
+
1629
+ destroyPopperInstance(); // If a popper is not interactive, it will be appended outside the popper
1630
+ // tree by default. This seems mainly for interactive tippies, but we should
1631
+ // find a workaround if possible
1632
+
1633
+ getNestedPopperTree().forEach(function (nestedPopper) {
1634
+ nestedPopper._tippy.unmount();
1635
+ });
1636
+
1637
+ if (popper.parentNode) {
1638
+ popper.parentNode.removeChild(popper);
1639
+ }
1640
+
1641
+ mountedInstances = mountedInstances.filter(function (i) {
1642
+ return i !== instance;
1643
+ });
1644
+ instance.state.isMounted = false;
1645
+ invokeHook('onHidden', [instance]);
1646
+ }
1647
+
1648
+ function destroy()
1649
+ {
1650
+ /* istanbul ignore else */
1651
+ {
1652
+ warnWhen(instance.state.isDestroyed, createMemoryLeakWarning('destroy'));
1653
+ }
1654
+
1655
+ if (instance.state.isDestroyed) {
1656
+ return;
1657
+ }
1658
+
1659
+ instance.clearDelayTimeouts();
1660
+ instance.unmount();
1661
+ removeListeners();
1662
+ delete reference._tippy;
1663
+ instance.state.isDestroyed = true;
1664
+ invokeHook('onDestroy', [instance]);
1665
+ }
1666
+ }
1667
+
1668
+ function tippy(targets, optionalProps)
1669
+ {
1670
+ if (optionalProps === void 0) {
1671
+ optionalProps = {};
1672
+ }
1673
+
1674
+ var plugins = defaultProps.plugins.concat(optionalProps.plugins || []);
1675
+ /* istanbul ignore else */
1676
+
1677
+ {
1678
+ validateTargets(targets);
1679
+ validateProps(optionalProps, plugins);
1680
+ }
1681
+
1682
+ bindGlobalEventListeners();
1683
+ var passedProps = Object.assign({}, optionalProps, {
1684
+ plugins: plugins
1685
+ });
1686
+ var elements = getArrayOfElements(targets);
1687
+ /* istanbul ignore else */
1688
+
1689
+ {
1690
+ var isSingleContentElement = isElement(passedProps.content);
1691
+ var isMoreThanOneReferenceElement = elements.length > 1;
1692
+ warnWhen(isSingleContentElement && isMoreThanOneReferenceElement, ['tippy() was passed an Element as the `content` prop, but more than', 'one tippy instance was created by this invocation. This means the', 'content element will only be appended to the last tippy instance.', '\n\n', 'Instead, pass the .innerHTML of the element, or use a function that', 'returns a cloned version of the element instead.', '\n\n', '1) content: element.innerHTML\n', '2) content: () => element.cloneNode(true)'].join(' '));
1693
+ }
1694
+
1695
+ var instances = elements.reduce(function (acc, reference) {
1696
+ var instance = reference && createTippy(reference, passedProps);
1697
+
1698
+ if (instance) {
1699
+ acc.push(instance);
1700
+ }
1701
+
1702
+ return acc;
1703
+ }, []);
1704
+ return isElement(targets) ? instances[0] : instances;
1705
+ }
1706
+
1707
+ tippy.defaultProps = defaultProps;
1708
+ tippy.setDefaultProps = setDefaultProps;
1709
+ tippy.currentInput = currentInput;
1710
+ var hideAll = function hideAll(_temp)
1711
+ {
1712
+ var _ref = _temp === void 0 ? {} : _temp,
1713
+ excludedReferenceOrInstance = _ref.exclude,
1714
+ duration = _ref.duration;
1715
+
1716
+ mountedInstances.forEach(function (instance) {
1717
+ var isExcluded = false;
1718
+
1719
+ if (excludedReferenceOrInstance) {
1720
+ isExcluded = isReferenceElement(excludedReferenceOrInstance) ? instance.reference === excludedReferenceOrInstance : instance.popper === excludedReferenceOrInstance.popper;
1721
+ }
1722
+
1723
+ if (!isExcluded) {
1724
+ var originalDuration = instance.props.duration;
1725
+ instance.setProps({
1726
+ duration: duration
1727
+ });
1728
+ instance.hide();
1729
+
1730
+ if (!instance.state.isDestroyed) {
1731
+ instance.setProps({
1732
+ duration: originalDuration
1733
+ });
1734
+ }
1735
+ }
1736
+ });
1737
+ };
1738
+
1739
+ var createSingleton = function createSingleton(tippyInstances, optionalProps)
1740
+ {
1741
+ if (optionalProps === void 0) {
1742
+ optionalProps = {};
1743
+ }
1744
+
1745
+ /* istanbul ignore else */
1746
+ {
1747
+ errorWhen(!Array.isArray(tippyInstances), ['The first argument passed to createSingleton() must be an array of', 'tippy instances. The passed value was', String(tippyInstances)].join(' '));
1748
+ }
1749
+
1750
+ var individualInstances = tippyInstances;
1751
+ var references = [];
1752
+ var currentTarget;
1753
+ var overrides = optionalProps.overrides;
1754
+ var interceptSetPropsCleanups = [];
1755
+
1756
+ function setReferences()
1757
+ {
1758
+ references = individualInstances.map(function (instance) {
1759
+ return instance.reference;
1760
+ });
1761
+ }
1762
+
1763
+ function enableInstances(isEnabled)
1764
+ {
1765
+ individualInstances.forEach(function (instance) {
1766
+ if (isEnabled) {
1767
+ instance.enable();
1768
+ } else {
1769
+ instance.disable();
1770
+ }
1771
+ });
1772
+ }
1773
+
1774
+ function interceptSetProps(singleton)
1775
+ {
1776
+ return individualInstances.map(function (instance) {
1777
+ var originalSetProps = instance.setProps;
1778
+
1779
+ instance.setProps = function (props) {
1780
+ originalSetProps(props);
1781
+
1782
+ if (instance.reference === currentTarget) {
1783
+ singleton.setProps(props);
1784
+ }
1785
+ };
1786
+
1787
+ return function () {
1788
+ instance.setProps = originalSetProps;
1789
+ };
1790
+ });
1791
+ }
1792
+
1793
+ enableInstances(false);
1794
+ setReferences();
1795
+ var plugin = {
1796
+ fn: function fn()
1797
+ {
1798
+ return {
1799
+ onDestroy: function onDestroy()
1800
+ {
1801
+ enableInstances(true);
1802
+ },
1803
+ onTrigger: function onTrigger(instance, event)
1804
+ {
1805
+ var target = event.currentTarget;
1806
+ var index = references.indexOf(target); // bail-out
1807
+
1808
+ if (target === currentTarget) {
1809
+ return;
1810
+ }
1811
+
1812
+ currentTarget = target;
1813
+ var overrideProps = (overrides || []).concat('content').reduce(function (acc, prop) {
1814
+ acc[prop] = individualInstances[index].props[prop];
1815
+ return acc;
1816
+ }, {});
1817
+ instance.setProps(Object.assign({}, overrideProps, {
1818
+ getReferenceClientRect: typeof overrideProps.getReferenceClientRect === 'function' ? overrideProps.getReferenceClientRect : function () {
1819
+ return target.getBoundingClientRect();
1820
+ }
1821
+ }));
1822
+ }
1823
+ };
1824
+ }
1825
+ };
1826
+ var singleton = tippy(div(), Object.assign({}, removeProperties(optionalProps, ['overrides']), {
1827
+ plugins: [plugin].concat(optionalProps.plugins || []),
1828
+ triggerTarget: references
1829
+ }));
1830
+ var originalSetProps = singleton.setProps;
1831
+
1832
+ singleton.setProps = function (props) {
1833
+ overrides = props.overrides || overrides;
1834
+ originalSetProps(props);
1835
+ };
1836
+
1837
+ singleton.setInstances = function (nextInstances) {
1838
+ enableInstances(true);
1839
+ interceptSetPropsCleanups.forEach(function (fn) {
1840
+ return fn();
1841
+ });
1842
+ individualInstances = nextInstances;
1843
+ enableInstances(false);
1844
+ setReferences();
1845
+ interceptSetProps(singleton);
1846
+ singleton.setProps({
1847
+ triggerTarget: references
1848
+ });
1849
+ };
1850
+
1851
+ interceptSetPropsCleanups = interceptSetProps(singleton);
1852
+ return singleton;
1853
+ };
1854
+
1855
+ var BUBBLING_EVENTS_MAP = {
1856
+ mouseover: 'mouseenter',
1857
+ focusin: 'focus',
1858
+ click: 'click'
1859
+ };
1860
+ /**
1861
+ * Creates a delegate instance that controls the creation of tippy instances
1862
+ * for child elements (`target` CSS selector).
1863
+ */
1864
+
1865
+ function delegate(targets, props)
1866
+ {
1867
+ /* istanbul ignore else */
1868
+ {
1869
+ errorWhen(!(props && props.target), ['You must specity a `target` prop indicating a CSS selector string matching', 'the target elements that should receive a tippy.'].join(' '));
1870
+ }
1871
+
1872
+ var listeners = [];
1873
+ var childTippyInstances = [];
1874
+ var disabled = false;
1875
+ var target = props.target;
1876
+ var nativeProps = removeProperties(props, ['target']);
1877
+ var parentProps = Object.assign({}, nativeProps, {
1878
+ trigger: 'manual',
1879
+ touch: false
1880
+ });
1881
+ var childProps = Object.assign({}, nativeProps, {
1882
+ showOnCreate: true
1883
+ });
1884
+ var returnValue = tippy(targets, parentProps);
1885
+ var normalizedReturnValue = normalizeToArray(returnValue);
1886
+
1887
+ function onTrigger(event)
1888
+ {
1889
+ if (!event.target || disabled) {
1890
+ return;
1891
+ }
1892
+
1893
+ var targetNode = event.target.closest(target);
1894
+
1895
+ if (!targetNode) {
1896
+ return;
1897
+ } // Get relevant trigger with fallbacks:
1898
+ // 1. Check `data-tippy-trigger` attribute on target node
1899
+ // 2. Fallback to `trigger` passed to `delegate()`
1900
+ // 3. Fallback to `defaultProps.trigger`
1901
+
1902
+
1903
+ var trigger = targetNode.getAttribute('data-tippy-trigger') || props.trigger || defaultProps.trigger; // @ts-ignore
1904
+
1905
+ if (targetNode._tippy) {
1906
+ return;
1907
+ }
1908
+
1909
+ if (event.type === 'touchstart' && typeof childProps.touch === 'boolean') {
1910
+ return;
1911
+ }
1912
+
1913
+ if (event.type !== 'touchstart' && trigger.indexOf(BUBBLING_EVENTS_MAP[event.type]) < 0) {
1914
+ return;
1915
+ }
1916
+
1917
+ var instance = tippy(targetNode, childProps);
1918
+
1919
+ if (instance) {
1920
+ childTippyInstances = childTippyInstances.concat(instance);
1921
+ }
1922
+ }
1923
+
1924
+ function on(node, eventType, handler, options)
1925
+ {
1926
+ if (options === void 0) {
1927
+ options = false;
1928
+ }
1929
+
1930
+ node.addEventListener(eventType, handler, options);
1931
+ listeners.push({
1932
+ node: node,
1933
+ eventType: eventType,
1934
+ handler: handler,
1935
+ options: options
1936
+ });
1937
+ }
1938
+
1939
+ function addEventListeners(instance)
1940
+ {
1941
+ var reference = instance.reference;
1942
+ on(reference, 'touchstart', onTrigger);
1943
+ on(reference, 'mouseover', onTrigger);
1944
+ on(reference, 'focusin', onTrigger);
1945
+ on(reference, 'click', onTrigger);
1946
+ }
1947
+
1948
+ function removeEventListeners()
1949
+ {
1950
+ listeners.forEach(function (_ref) {
1951
+ var node = _ref.node,
1952
+ eventType = _ref.eventType,
1953
+ handler = _ref.handler,
1954
+ options = _ref.options;
1955
+ node.removeEventListener(eventType, handler, options);
1956
+ });
1957
+ listeners = [];
1958
+ }
1959
+
1960
+ function applyMutations(instance)
1961
+ {
1962
+ var originalDestroy = instance.destroy;
1963
+ var originalEnable = instance.enable;
1964
+ var originalDisable = instance.disable;
1965
+
1966
+ instance.destroy = function (shouldDestroyChildInstances) {
1967
+ if (shouldDestroyChildInstances === void 0) {
1968
+ shouldDestroyChildInstances = true;
1969
+ }
1970
+
1971
+ if (shouldDestroyChildInstances) {
1972
+ childTippyInstances.forEach(function (instance) {
1973
+ instance.destroy();
1974
+ });
1975
+ }
1976
+
1977
+ childTippyInstances = [];
1978
+ removeEventListeners();
1979
+ originalDestroy();
1980
+ };
1981
+
1982
+ instance.enable = function () {
1983
+ originalEnable();
1984
+ childTippyInstances.forEach(function (instance) {
1985
+ return instance.enable();
1986
+ });
1987
+ disabled = false;
1988
+ };
1989
+
1990
+ instance.disable = function () {
1991
+ originalDisable();
1992
+ childTippyInstances.forEach(function (instance) {
1993
+ return instance.disable();
1994
+ });
1995
+ disabled = true;
1996
+ };
1997
+
1998
+ addEventListeners(instance);
1999
+ }
2000
+
2001
+ normalizedReturnValue.forEach(applyMutations);
2002
+ return returnValue;
2003
+ }
2004
+
2005
+ var animateFill = {
2006
+ name: 'animateFill',
2007
+ defaultValue: false,
2008
+ fn: function fn(instance)
2009
+ {
2010
+ var _instance$props$rende;
2011
+
2012
+ // @ts-ignore
2013
+ if (!((_instance$props$rende = instance.props.render) == null ? void 0 : _instance$props$rende.$$tippy)) {
2014
+ {
2015
+ errorWhen(instance.props.animateFill, 'The `animateFill` plugin requires the default render function.');
2016
+ }
2017
+
2018
+ return {};
2019
+ }
2020
+
2021
+ var _getChildren = getChildren(instance.popper),
2022
+ box = _getChildren.box,
2023
+ content = _getChildren.content;
2024
+
2025
+ var backdrop = instance.props.animateFill ? createBackdropElement() : null;
2026
+ return {
2027
+ onCreate: function onCreate()
2028
+ {
2029
+ if (backdrop) {
2030
+ box.insertBefore(backdrop, box.firstElementChild);
2031
+ box.setAttribute('data-animatefill', '');
2032
+ box.style.overflow = 'hidden';
2033
+ instance.setProps({
2034
+ arrow: false,
2035
+ animation: 'shift-away'
2036
+ });
2037
+ }
2038
+ },
2039
+ onMount: function onMount()
2040
+ {
2041
+ if (backdrop) {
2042
+ var transitionDuration = box.style.transitionDuration;
2043
+ var duration = Number(transitionDuration.replace('ms', '')); // The content should fade in after the backdrop has mostly filled the
2044
+ // tooltip element. `clip-path` is the other alternative but is not
2045
+ // well-supported and is buggy on some devices.
2046
+
2047
+ content.style.transitionDelay = Math.round(duration / 10) + "ms";
2048
+ backdrop.style.transitionDuration = transitionDuration;
2049
+ setVisibilityState([backdrop], 'visible');
2050
+ }
2051
+ },
2052
+ onShow: function onShow()
2053
+ {
2054
+ if (backdrop) {
2055
+ backdrop.style.transitionDuration = '0ms';
2056
+ }
2057
+ },
2058
+ onHide: function onHide()
2059
+ {
2060
+ if (backdrop) {
2061
+ setVisibilityState([backdrop], 'hidden');
2062
+ }
2063
+ }
2064
+ };
2065
+ }
2066
+ };
2067
+
2068
+ function createBackdropElement()
2069
+ {
2070
+ var backdrop = div();
2071
+ backdrop.className = BACKDROP_CLASS;
2072
+ setVisibilityState([backdrop], 'hidden');
2073
+ return backdrop;
2074
+ }
2075
+
2076
+ var mouseCoords = {
2077
+ clientX: 0,
2078
+ clientY: 0
2079
+ };
2080
+ var activeInstances = [];
2081
+
2082
+ function storeMouseCoords(_ref)
2083
+ {
2084
+ var clientX = _ref.clientX,
2085
+ clientY = _ref.clientY;
2086
+ mouseCoords = {
2087
+ clientX: clientX,
2088
+ clientY: clientY
2089
+ };
2090
+ }
2091
+
2092
+ function addMouseCoordsListener(doc)
2093
+ {
2094
+ doc.addEventListener('mousemove', storeMouseCoords);
2095
+ }
2096
+
2097
+ function removeMouseCoordsListener(doc)
2098
+ {
2099
+ doc.removeEventListener('mousemove', storeMouseCoords);
2100
+ }
2101
+
2102
+ var followCursor = {
2103
+ name: 'followCursor',
2104
+ defaultValue: false,
2105
+ fn: function fn(instance)
2106
+ {
2107
+ var reference = instance.reference;
2108
+ var doc = getOwnerDocument(instance.props.triggerTarget || reference);
2109
+ var isInternalUpdate = false;
2110
+ var wasFocusEvent = false;
2111
+ var isUnmounted = true;
2112
+ var prevProps = instance.props;
2113
+
2114
+ function getIsInitialBehavior()
2115
+ {
2116
+ return instance.props.followCursor === 'initial' && instance.state.isVisible;
2117
+ }
2118
+
2119
+ function addListener()
2120
+ {
2121
+ doc.addEventListener('mousemove', onMouseMove);
2122
+ }
2123
+
2124
+ function removeListener()
2125
+ {
2126
+ doc.removeEventListener('mousemove', onMouseMove);
2127
+ }
2128
+
2129
+ function unsetGetReferenceClientRect()
2130
+ {
2131
+ isInternalUpdate = true;
2132
+ instance.setProps({
2133
+ getReferenceClientRect: null
2134
+ });
2135
+ isInternalUpdate = false;
2136
+ }
2137
+
2138
+ function onMouseMove(event)
2139
+ {
2140
+ // If the instance is interactive, avoid updating the position unless it's
2141
+ // over the reference element
2142
+ var isCursorOverReference = event.target ? reference.contains(event.target) : true;
2143
+ var followCursor = instance.props.followCursor;
2144
+ var clientX = event.clientX,
2145
+ clientY = event.clientY;
2146
+ var rect = reference.getBoundingClientRect();
2147
+ var relativeX = clientX - rect.left;
2148
+ var relativeY = clientY - rect.top;
2149
+
2150
+ if (isCursorOverReference || !instance.props.interactive) {
2151
+ instance.setProps({
2152
+ getReferenceClientRect: function getReferenceClientRect()
2153
+ {
2154
+ var rect = reference.getBoundingClientRect();
2155
+ var x = clientX;
2156
+ var y = clientY;
2157
+
2158
+ if (followCursor === 'initial') {
2159
+ x = rect.left + relativeX;
2160
+ y = rect.top + relativeY;
2161
+ }
2162
+
2163
+ var top = followCursor === 'horizontal' ? rect.top : y;
2164
+ var right = followCursor === 'vertical' ? rect.right : x;
2165
+ var bottom = followCursor === 'horizontal' ? rect.bottom : y;
2166
+ var left = followCursor === 'vertical' ? rect.left : x;
2167
+ return {
2168
+ width: right - left,
2169
+ height: bottom - top,
2170
+ top: top,
2171
+ right: right,
2172
+ bottom: bottom,
2173
+ left: left
2174
+ };
2175
+ }
2176
+ });
2177
+ }
2178
+ }
2179
+
2180
+ function create()
2181
+ {
2182
+ if (instance.props.followCursor) {
2183
+ activeInstances.push({
2184
+ instance: instance,
2185
+ doc: doc
2186
+ });
2187
+ addMouseCoordsListener(doc);
2188
+ }
2189
+ }
2190
+
2191
+ function destroy()
2192
+ {
2193
+ activeInstances = activeInstances.filter(function (data) {
2194
+ return data.instance !== instance;
2195
+ });
2196
+
2197
+ if (activeInstances.filter(function (data) {
2198
+ return data.doc === doc;
2199
+ }).length === 0) {
2200
+ removeMouseCoordsListener(doc);
2201
+ }
2202
+ }
2203
+
2204
+ return {
2205
+ onCreate: create,
2206
+ onDestroy: destroy,
2207
+ onBeforeUpdate: function onBeforeUpdate()
2208
+ {
2209
+ prevProps = instance.props;
2210
+ },
2211
+ onAfterUpdate: function onAfterUpdate(_, _ref2)
2212
+ {
2213
+ var followCursor = _ref2.followCursor;
2214
+
2215
+ if (isInternalUpdate) {
2216
+ return;
2217
+ }
2218
+
2219
+ if (followCursor !== undefined && prevProps.followCursor !== followCursor) {
2220
+ destroy();
2221
+
2222
+ if (followCursor) {
2223
+ create();
2224
+
2225
+ if (instance.state.isMounted && !wasFocusEvent && !getIsInitialBehavior()) {
2226
+ addListener();
2227
+ }
2228
+ } else {
2229
+ removeListener();
2230
+ unsetGetReferenceClientRect();
2231
+ }
2232
+ }
2233
+ },
2234
+ onMount: function onMount()
2235
+ {
2236
+ if (instance.props.followCursor && !wasFocusEvent) {
2237
+ if (isUnmounted) {
2238
+ onMouseMove(mouseCoords);
2239
+ isUnmounted = false;
2240
+ }
2241
+
2242
+ if (!getIsInitialBehavior()) {
2243
+ addListener();
2244
+ }
2245
+ }
2246
+ },
2247
+ onTrigger: function onTrigger(_, event)
2248
+ {
2249
+ if (isMouseEvent(event)) {
2250
+ mouseCoords = {
2251
+ clientX: event.clientX,
2252
+ clientY: event.clientY
2253
+ };
2254
+ }
2255
+
2256
+ wasFocusEvent = event.type === 'focus';
2257
+ },
2258
+ onHidden: function onHidden()
2259
+ {
2260
+ if (instance.props.followCursor) {
2261
+ unsetGetReferenceClientRect();
2262
+ removeListener();
2263
+ isUnmounted = true;
2264
+ }
2265
+ }
2266
+ };
2267
+ }
2268
+ };
2269
+
2270
+ function getProps(props, modifier)
2271
+ {
2272
+ var _props$popperOptions;
2273
+
2274
+ return {
2275
+ popperOptions: Object.assign({}, props.popperOptions, {
2276
+ modifiers: [].concat((((_props$popperOptions = props.popperOptions) == null ? void 0 : _props$popperOptions.modifiers) || []).filter(function (_ref) {
2277
+ var name = _ref.name;
2278
+ return name !== modifier.name;
2279
+ }), [modifier])
2280
+ })
2281
+ };
2282
+ }
2283
+
2284
+ var inlinePositioning = {
2285
+ name: 'inlinePositioning',
2286
+ defaultValue: false,
2287
+ fn: function fn(instance)
2288
+ {
2289
+ var reference = instance.reference;
2290
+
2291
+ function isEnabled()
2292
+ {
2293
+ return !!instance.props.inlinePositioning;
2294
+ }
2295
+
2296
+ var placement;
2297
+ var cursorRectIndex = -1;
2298
+ var isInternalUpdate = false;
2299
+ var modifier = {
2300
+ name: 'tippyInlinePositioning',
2301
+ enabled: true,
2302
+ phase: 'afterWrite',
2303
+ fn: function fn(_ref2)
2304
+ {
2305
+ var state = _ref2.state;
2306
+
2307
+ if (isEnabled()) {
2308
+ if (placement !== state.placement) {
2309
+ instance.setProps({
2310
+ getReferenceClientRect: function getReferenceClientRect()
2311
+ {
2312
+ return _getReferenceClientRect(state.placement);
2313
+ }
2314
+ });
2315
+ }
2316
+
2317
+ placement = state.placement;
2318
+ }
2319
+ }
2320
+ };
2321
+
2322
+ function _getReferenceClientRect(placement)
2323
+ {
2324
+ return getInlineBoundingClientRect(getBasePlacement(placement), reference.getBoundingClientRect(), arrayFrom(reference.getClientRects()), cursorRectIndex);
2325
+ }
2326
+
2327
+ function setInternalProps(partialProps)
2328
+ {
2329
+ isInternalUpdate = true;
2330
+ instance.setProps(partialProps);
2331
+ isInternalUpdate = false;
2332
+ }
2333
+
2334
+ function addModifier()
2335
+ {
2336
+ if (!isInternalUpdate) {
2337
+ setInternalProps(getProps(instance.props, modifier));
2338
+ }
2339
+ }
2340
+
2341
+ return {
2342
+ onCreate: addModifier,
2343
+ onAfterUpdate: addModifier,
2344
+ onTrigger: function onTrigger(_, event)
2345
+ {
2346
+ if (isMouseEvent(event)) {
2347
+ var rects = arrayFrom(instance.reference.getClientRects());
2348
+ var cursorRect = rects.find(function (rect) {
2349
+ return rect.left - 2 <= event.clientX && rect.right + 2 >= event.clientX && rect.top - 2 <= event.clientY && rect.bottom + 2 >= event.clientY;
2350
+ });
2351
+ cursorRectIndex = rects.indexOf(cursorRect);
2352
+ }
2353
+ },
2354
+ onUntrigger: function onUntrigger()
2355
+ {
2356
+ cursorRectIndex = -1;
2357
+ }
2358
+ };
2359
+ }
2360
+ };
2361
+ function getInlineBoundingClientRect(currentBasePlacement, boundingRect, clientRects, cursorRectIndex)
2362
+ {
2363
+ // Not an inline element, or placement is not yet known
2364
+ if (clientRects.length < 2 || currentBasePlacement === null) {
2365
+ return boundingRect;
2366
+ } // There are two rects and they are disjoined
2367
+
2368
+
2369
+ if (clientRects.length === 2 && cursorRectIndex >= 0 && clientRects[0].left > clientRects[1].right) {
2370
+ return clientRects[cursorRectIndex] || boundingRect;
2371
+ }
2372
+
2373
+ switch (currentBasePlacement) {
2374
+ case 'top':
2375
+ case 'bottom':
2376
+ {
2377
+ var firstRect = clientRects[0];
2378
+ var lastRect = clientRects[clientRects.length - 1];
2379
+ var isTop = currentBasePlacement === 'top';
2380
+ var top = firstRect.top;
2381
+ var bottom = lastRect.bottom;
2382
+ var left = isTop ? firstRect.left : lastRect.left;
2383
+ var right = isTop ? firstRect.right : lastRect.right;
2384
+ var width = right - left;
2385
+ var height = bottom - top;
2386
+ return {
2387
+ top: top,
2388
+ bottom: bottom,
2389
+ left: left,
2390
+ right: right,
2391
+ width: width,
2392
+ height: height
2393
+ };
2394
+ }
2395
+
2396
+ case 'left':
2397
+ case 'right':
2398
+ {
2399
+ var minLeft = Math.min.apply(Math, clientRects.map(function (rects) {
2400
+ return rects.left;
2401
+ }));
2402
+ var maxRight = Math.max.apply(Math, clientRects.map(function (rects) {
2403
+ return rects.right;
2404
+ }));
2405
+ var measureRects = clientRects.filter(function (rect) {
2406
+ return currentBasePlacement === 'left' ? rect.left === minLeft : rect.right === maxRight;
2407
+ });
2408
+ var _top = measureRects[0].top;
2409
+ var _bottom = measureRects[measureRects.length - 1].bottom;
2410
+ var _left = minLeft;
2411
+ var _right = maxRight;
2412
+
2413
+ var _width = _right - _left;
2414
+
2415
+ var _height = _bottom - _top;
2416
+
2417
+ return {
2418
+ top: _top,
2419
+ bottom: _bottom,
2420
+ left: _left,
2421
+ right: _right,
2422
+ width: _width,
2423
+ height: _height
2424
+ };
2425
+ }
2426
+
2427
+ default:
2428
+ {
2429
+ return boundingRect;
2430
+ }
2431
+ }
2432
+ }
2433
+
2434
+ var sticky = {
2435
+ name: 'sticky',
2436
+ defaultValue: false,
2437
+ fn: function fn(instance)
2438
+ {
2439
+ var reference = instance.reference,
2440
+ popper = instance.popper;
2441
+
2442
+ function getReference()
2443
+ {
2444
+ return instance.popperInstance ? instance.popperInstance.state.elements.reference : reference;
2445
+ }
2446
+
2447
+ function shouldCheck(value)
2448
+ {
2449
+ return instance.props.sticky === true || instance.props.sticky === value;
2450
+ }
2451
+
2452
+ var prevRefRect = null;
2453
+ var prevPopRect = null;
2454
+
2455
+ function updatePosition()
2456
+ {
2457
+ var currentRefRect = shouldCheck('reference') ? getReference().getBoundingClientRect() : null;
2458
+ var currentPopRect = shouldCheck('popper') ? popper.getBoundingClientRect() : null;
2459
+
2460
+ if (currentRefRect && areRectsDifferent(prevRefRect, currentRefRect) || currentPopRect && areRectsDifferent(prevPopRect, currentPopRect)) {
2461
+ if (instance.popperInstance) {
2462
+ instance.popperInstance.update();
2463
+ }
2464
+ }
2465
+
2466
+ prevRefRect = currentRefRect;
2467
+ prevPopRect = currentPopRect;
2468
+
2469
+ if (instance.state.isMounted) {
2470
+ requestAnimationFrame(updatePosition);
2471
+ }
2472
+ }
2473
+
2474
+ return {
2475
+ onMount: function onMount()
2476
+ {
2477
+ if (instance.props.sticky) {
2478
+ updatePosition();
2479
+ }
2480
+ }
2481
+ };
2482
+ }
2483
+ };
2484
+
2485
+ function areRectsDifferent(rectA, rectB)
2486
+ {
2487
+ if (rectA && rectB) {
2488
+ return rectA.top !== rectB.top || rectA.right !== rectB.right || rectA.bottom !== rectB.bottom || rectA.left !== rectB.left;
2489
+ }
2490
+
2491
+ return true;
2492
+ }
2493
+
2494
+ if (isBrowser) {
2495
+ injectCSS(css);
2496
+ }
2497
+
2498
+ tippy.setDefaultProps({
2499
+ plugins: [animateFill, followCursor, inlinePositioning, sticky],
2500
+ render: render
2501
+ });
2502
+ tippy.createSingleton = createSingleton;
2503
+ tippy.delegate = delegate;
2504
+ tippy.hideAll = hideAll;
2505
+ tippy.roundArrow = ROUND_ARROW;
2506
+
2507
+ return tippy;
2508
+
2509
+ })));
assets/js/tippy/tippy-bundle.umd.min.js ADDED
@@ -0,0 +1 @@
 
1
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],e):(t=t||self).tippy=e(t.Popper)}(this,(function(t){"use strict";var e="undefined"!=typeof window&&"undefined"!=typeof document,n=e?navigator.userAgent:"",r=/MSIE |Trident\//.test(n),i={passive:!0,capture:!0};function o(t,e,n){if(Array.isArray(t)){var r=t[e];return null==r?Array.isArray(n)?n[e]:n:r}return t}function a(t,e){var n={}.toString.call(t);return 0===n.indexOf("[object")&&n.indexOf(e+"]")>-1}function s(t,e){return"function"==typeof t?t.apply(void 0,e):t}function u(t,e){return 0===e?t:function(r){clearTimeout(n),n=setTimeout((function(){t(r)}),e)};var n}function c(t,e){var n=Object.assign({},t);return e.forEach((function(t){delete n[t]})),n}function p(t){return[].concat(t)}function f(t,e){-1===t.indexOf(e)&&t.push(e)}function l(t){return t.split("-")[0]}function d(t){return[].slice.call(t)}function v(){return document.createElement("div")}function m(t){return["Element","Fragment"].some((function(e){return a(t,e)}))}function g(t){return a(t,"MouseEvent")}function h(t){return!(!t||!t._tippy||t._tippy.reference!==t)}function b(t){return m(t)?[t]:function(t){return a(t,"NodeList")}(t)?d(t):Array.isArray(t)?t:d(document.querySelectorAll(t))}function y(t,e){t.forEach((function(t){t&&(t.style.transitionDuration=e+"ms")}))}function x(t,e){t.forEach((function(t){t&&t.setAttribute("data-state",e)}))}function w(t){var e=p(t)[0];return e&&e.ownerDocument||document}function E(t,e,n){var r=e+"EventListener";["transitionend","webkitTransitionEnd"].forEach((function(e){t[r](e,n)}))}var T={isTouch:!1},C=0;function A(){T.isTouch||(T.isTouch=!0,window.performance&&document.addEventListener("mousemove",O))}function O(){var t=performance.now();t-C<20&&(T.isTouch=!1,document.removeEventListener("mousemove",O)),C=t}function L(){var t=document.activeElement;if(h(t)){var e=t._tippy;t.blur&&!e.state.isVisible&&t.blur()}}var D=Object.assign({appendTo:function(){return document.body},aria:{content:"auto",expanded:"auto"},delay:0,duration:[300,250],getReferenceClientRect:null,hideOnClick:!0,ignoreAttributes:!1,interactive:!1,interactiveBorder:2,interactiveDebounce:0,moveTransition:"",offset:[0,10],onAfterUpdate:function(){},onBeforeUpdate:function(){},onCreate:function(){},onDestroy:function(){},onHidden:function(){},onHide:function(){},onMount:function(){},onShow:function(){},onShown:function(){},onTrigger:function(){},onUntrigger:function(){},onClickOutside:function(){},placement:"top",plugins:[],popperOptions:{},render:null,showOnCreate:!1,touch:!0,trigger:"mouseenter focus",triggerTarget:null},{animateFill:!1,followCursor:!1,inlinePositioning:!1,sticky:!1},{},{allowHTML:!1,animation:"fade",arrow:!0,content:"",inertia:!1,maxWidth:350,role:"tooltip",theme:"",zIndex:9999}),k=Object.keys(D);function R(t){var e=(t.plugins||[]).reduce((function(e,n){var r=n.name,i=n.defaultValue;return r&&(e[r]=void 0!==t[r]?t[r]:i),e}),{});return Object.assign({},t,{},e)}function M(t,e){var n=Object.assign({},e,{content:s(e.content,[t])},e.ignoreAttributes?{}:function(t,e){return(e?Object.keys(R(Object.assign({},D,{plugins:e}))):k).reduce((function(e,n){var r=(t.getAttribute("data-tippy-"+n)||"").trim();if(!r)return e;if("content"===n)e[n]=r;else try{e[n]=JSON.parse(r)}catch(t){e[n]=r}return e}),{})}(t,e.plugins));return n.aria=Object.assign({},D.aria,{},n.aria),n.aria={expanded:"auto"===n.aria.expanded?e.interactive:n.aria.expanded,content:"auto"===n.aria.content?e.interactive?null:"describedby":n.aria.content},n}function P(t,e){t.innerHTML=e}function V(t){var e=v();return!0===t?e.className="tippy-arrow":(e.className="tippy-svg-arrow",m(t)?e.appendChild(t):P(e,t)),e}function j(t,e){m(e.content)?(P(t,""),t.appendChild(e.content)):"function"!=typeof e.content&&(e.allowHTML?P(t,e.content):t.textContent=e.content)}function I(t){var e=t.firstElementChild,n=d(e.children);return{box:e,content:n.find((function(t){return t.classList.contains("tippy-content")})),arrow:n.find((function(t){return t.classList.contains("tippy-arrow")||t.classList.contains("tippy-svg-arrow")})),backdrop:n.find((function(t){return t.classList.contains("tippy-backdrop")}))}}function S(t){var e=v(),n=v();n.className="tippy-box",n.setAttribute("data-state","hidden"),n.setAttribute("tabindex","-1");var r=v();function i(n,r){var i=I(e),o=i.box,a=i.content,s=i.arrow;r.theme?o.setAttribute("data-theme",r.theme):o.removeAttribute("data-theme"),"string"==typeof r.animation?o.setAttribute("data-animation",r.animation):o.removeAttribute("data-animation"),r.inertia?o.setAttribute("data-inertia",""):o.removeAttribute("data-inertia"),o.style.maxWidth="number"==typeof r.maxWidth?r.maxWidth+"px":r.maxWidth,r.role?o.setAttribute("role",r.role):o.removeAttribute("role"),n.content===r.content&&n.allowHTML===r.allowHTML||j(a,t.props),r.arrow?s?n.arrow!==r.arrow&&(o.removeChild(s),o.appendChild(V(r.arrow))):o.appendChild(V(r.arrow)):s&&o.removeChild(s)}return r.className="tippy-content",r.setAttribute("data-state","hidden"),j(r,t.props),e.appendChild(n),n.appendChild(r),i(t.props,t.props),{popper:e,onUpdate:i}}S.$$tippy=!0;var B=1,H=[],N=[];function U(e,n){var a,c,m,h,b,C,A,O,L,k=M(e,Object.assign({},D,{},R((a=n,Object.keys(a).reduce((function(t,e){return void 0!==a[e]&&(t[e]=a[e]),t}),{}))))),P=!1,V=!1,j=!1,S=!1,U=[],_=u(bt,k.interactiveDebounce),z=B++,F=(L=k.plugins).filter((function(t,e){return L.indexOf(t)===e})),W={id:z,reference:e,popper:v(),popperInstance:null,props:k,state:{isEnabled:!0,isVisible:!1,isDestroyed:!1,isMounted:!1,isShown:!1},plugins:F,clearDelayTimeouts:function(){clearTimeout(c),clearTimeout(m),cancelAnimationFrame(h)},setProps:function(t){if(W.state.isDestroyed)return;it("onBeforeUpdate",[W,t]),gt();var n=W.props,r=M(e,Object.assign({},W.props,{},t,{ignoreAttributes:!0}));W.props=r,mt(),n.interactiveDebounce!==r.interactiveDebounce&&(st(),_=u(bt,r.interactiveDebounce));n.triggerTarget&&!r.triggerTarget?p(n.triggerTarget).forEach((function(t){t.removeAttribute("aria-expanded")})):r.triggerTarget&&e.removeAttribute("aria-expanded");at(),rt(),q&&q(n,r);W.popperInstance&&(Et(),Ct().forEach((function(t){requestAnimationFrame(t._tippy.popperInstance.forceUpdate)})));it("onAfterUpdate",[W,t])},setContent:function(t){W.setProps({content:t})},show:function(){var t=W.state.isVisible,e=W.state.isDestroyed,n=!W.state.isEnabled,r=T.isTouch&&!W.props.touch,i=o(W.props.duration,0,D.duration);if(t||e||n||r)return;if(Z().hasAttribute("disabled"))return;if(it("onShow",[W],!1),!1===W.props.onShow(W))return;W.state.isVisible=!0,Q()&&(Y.style.visibility="visible");rt(),ft(),W.state.isMounted||(Y.style.transition="none");if(Q()){var a=et(),u=a.box,c=a.content;y([u,c],0)}A=function(){if(W.state.isVisible&&!S){if(S=!0,Y.offsetHeight,Y.style.transition=W.props.moveTransition,Q()&&W.props.animation){var t=et(),e=t.box,n=t.content;y([e,n],i),x([e,n],"visible")}ot(),at(),f(N,W),W.state.isMounted=!0,it("onMount",[W]),W.props.animation&&Q()&&function(t,e){dt(t,e)}(i,(function(){W.state.isShown=!0,it("onShown",[W])}))}},function(){var t,e=W.props.appendTo,n=Z();t=W.props.interactive&&e===D.appendTo||"parent"===e?n.parentNode:s(e,[n]);t.contains(Y)||t.appendChild(Y);Et()}()},hide:function(){var t=!W.state.isVisible,e=W.state.isDestroyed,n=!W.state.isEnabled,r=o(W.props.duration,1,D.duration);if(t||e||n)return;if(it("onHide",[W],!1),!1===W.props.onHide(W))return;W.state.isVisible=!1,W.state.isShown=!1,S=!1,P=!1,Q()&&(Y.style.visibility="hidden");if(st(),lt(),rt(),Q()){var i=et(),a=i.box,s=i.content;W.props.animation&&(y([a,s],r),x([a,s],"hidden"))}ot(),at(),W.props.animation?Q()&&function(t,e){dt(t,(function(){!W.state.isVisible&&Y.parentNode&&Y.parentNode.contains(Y)&&e()}))}(r,W.unmount):W.unmount()},hideWithInteractivity:function(t){tt().addEventListener("mousemove",_),f(H,_),_(t)},enable:function(){W.state.isEnabled=!0},disable:function(){W.hide(),W.state.isEnabled=!1},unmount:function(){W.state.isVisible&&W.hide();if(!W.state.isMounted)return;Tt(),Ct().forEach((function(t){t._tippy.unmount()})),Y.parentNode&&Y.parentNode.removeChild(Y);N=N.filter((function(t){return t!==W})),W.state.isMounted=!1,it("onHidden",[W])},destroy:function(){if(W.state.isDestroyed)return;W.clearDelayTimeouts(),W.unmount(),gt(),delete e._tippy,W.state.isDestroyed=!0,it("onDestroy",[W])}};if(!k.render)return W;var X=k.render(W),Y=X.popper,q=X.onUpdate;Y.setAttribute("data-tippy-root",""),Y.id="tippy-"+W.id,W.popper=Y,e._tippy=W,Y._tippy=W;var $=F.map((function(t){return t.fn(W)})),J=e.hasAttribute("aria-expanded");return mt(),at(),rt(),it("onCreate",[W]),k.showOnCreate&&At(),Y.addEventListener("mouseenter",(function(){W.props.interactive&&W.state.isVisible&&W.clearDelayTimeouts()})),Y.addEventListener("mouseleave",(function(t){W.props.interactive&&W.props.trigger.indexOf("mouseenter")>=0&&(tt().addEventListener("mousemove",_),_(t))})),W;function G(){var t=W.props.touch;return Array.isArray(t)?t:[t,0]}function K(){return"hold"===G()[0]}function Q(){var t;return!!(null==(t=W.props.render)?void 0:t.$$tippy)}function Z(){return O||e}function tt(){var t=Z().parentNode;return t?w(t):document}function et(){return I(Y)}function nt(t){return W.state.isMounted&&!W.state.isVisible||T.isTouch||b&&"focus"===b.type?0:o(W.props.delay,t?0:1,D.delay)}function rt(){Y.style.pointerEvents=W.props.interactive&&W.state.isVisible?"":"none",Y.style.zIndex=""+W.props.zIndex}function it(t,e,n){var r;(void 0===n&&(n=!0),$.forEach((function(n){n[t]&&n[t].apply(void 0,e)})),n)&&(r=W.props)[t].apply(r,e)}function ot(){var t=W.props.aria;if(t.content){var n="aria-"+t.content,r=Y.id;p(W.props.triggerTarget||e).forEach((function(t){var e=t.getAttribute(n);if(W.state.isVisible)t.setAttribute(n,e?e+" "+r:r);else{var i=e&&e.replace(r,"").trim();i?t.setAttribute(n,i):t.removeAttribute(n)}}))}}function at(){!J&&W.props.aria.expanded&&p(W.props.triggerTarget||e).forEach((function(t){W.props.interactive?t.setAttribute("aria-expanded",W.state.isVisible&&t===Z()?"true":"false"):t.removeAttribute("aria-expanded")}))}function st(){tt().removeEventListener("mousemove",_),H=H.filter((function(t){return t!==_}))}function ut(t){if(!(T.isTouch&&(j||"mousedown"===t.type)||W.props.interactive&&Y.contains(t.target))){if(Z().contains(t.target)){if(T.isTouch)return;if(W.state.isVisible&&W.props.trigger.indexOf("click")>=0)return}else it("onClickOutside",[W,t]);!0===W.props.hideOnClick&&(W.clearDelayTimeouts(),W.hide(),V=!0,setTimeout((function(){V=!1})),W.state.isMounted||lt())}}function ct(){j=!0}function pt(){j=!1}function ft(){var t=tt();t.addEventListener("mousedown",ut,!0),t.addEventListener("touchend",ut,i),t.addEventListener("touchstart",pt,i),t.addEventListener("touchmove",ct,i)}function lt(){var t=tt();t.removeEventListener("mousedown",ut,!0),t.removeEventListener("touchend",ut,i),t.removeEventListener("touchstart",pt,i),t.removeEventListener("touchmove",ct,i)}function dt(t,e){var n=et().box;function r(t){t.target===n&&(E(n,"remove",r),e())}if(0===t)return e();E(n,"remove",C),E(n,"add",r),C=r}function vt(t,n,r){void 0===r&&(r=!1),p(W.props.triggerTarget||e).forEach((function(e){e.addEventListener(t,n,r),U.push({node:e,eventType:t,handler:n,options:r})}))}function mt(){var t;K()&&(vt("touchstart",ht,{passive:!0}),vt("touchend",yt,{passive:!0})),(t=W.props.trigger,t.split(/\s+/).filter(Boolean)).forEach((function(t){if("manual"!==t)switch(vt(t,ht),t){case"mouseenter":vt("mouseleave",yt);break;case"focus":vt(r?"focusout":"blur",xt);break;case"focusin":vt("focusout",xt)}}))}function gt(){U.forEach((function(t){var e=t.node,n=t.eventType,r=t.handler,i=t.options;e.removeEventListener(n,r,i)})),U=[]}function ht(t){var e,n=!1;if(W.state.isEnabled&&!wt(t)&&!V){var r="focus"===(null==(e=b)?void 0:e.type);b=t,O=t.currentTarget,at(),!W.state.isVisible&&g(t)&&H.forEach((function(e){return e(t)})),"click"===t.type&&(W.props.trigger.indexOf("mouseenter")<0||P)&&!1!==W.props.hideOnClick&&W.state.isVisible?n=!0:At(t),"click"===t.type&&(P=!n),n&&!r&&Ot(t)}}function bt(t){var e=t.target,n=Z().contains(e)||Y.contains(e);"mousemove"===t.type&&n||function(t,e){var n=e.clientX,r=e.clientY;return t.every((function(t){var e=t.popperRect,i=t.popperState,o=t.props.interactiveBorder,a=l(i.placement),s=i.modifiersData.offset;if(!s)return!0;var u="bottom"===a?s.top.y:0,c="top"===a?s.bottom.y:0,p="right"===a?s.left.x:0,f="left"===a?s.right.x:0,d=e.top-r+u>o,v=r-e.bottom-c>o,m=e.left-n+p>o,g=n-e.right-f>o;return d||v||m||g}))}(Ct().concat(Y).map((function(t){var e,n=null==(e=t._tippy.popperInstance)?void 0:e.state;return n?{popperRect:t.getBoundingClientRect(),popperState:n,props:k}:null})).filter(Boolean),t)&&(st(),Ot(t))}function yt(t){wt(t)||W.props.trigger.indexOf("click")>=0&&P||(W.props.interactive?W.hideWithInteractivity(t):Ot(t))}function xt(t){W.props.trigger.indexOf("focusin")<0&&t.target!==Z()||W.props.interactive&&t.relatedTarget&&Y.contains(t.relatedTarget)||Ot(t)}function wt(t){return!!T.isTouch&&K()!==t.type.indexOf("touch")>=0}function Et(){Tt();var n=W.props,r=n.popperOptions,i=n.placement,o=n.offset,a=n.getReferenceClientRect,s=n.moveTransition,u=Q()?I(Y).arrow:null,c=a?{getBoundingClientRect:a,contextElement:a.contextElement||Z()}:e,p=[{name:"offset",options:{offset:o}},{name:"preventOverflow",options:{padding:{top:2,bottom:2,left:5,right:5}}},{name:"flip",options:{padding:5}},{name:"computeStyles",options:{adaptive:!s}},{name:"$$tippy",enabled:!0,phase:"beforeWrite",requires:["computeStyles"],fn:function(t){var e=t.state;if(Q()){var n=et().box;["placement","reference-hidden","escaped"].forEach((function(t){"placement"===t?n.setAttribute("data-placement",e.placement):e.attributes.popper["data-popper-"+t]?n.setAttribute("data-"+t,""):n.removeAttribute("data-"+t)})),e.attributes.popper={}}}}];Q()&&u&&p.push({name:"arrow",options:{element:u,padding:3}}),p.push.apply(p,(null==r?void 0:r.modifiers)||[]),W.popperInstance=t.createPopper(c,Y,Object.assign({},r,{placement:i,onFirstUpdate:A,modifiers:p}))}function Tt(){W.popperInstance&&(W.popperInstance.destroy(),W.popperInstance=null)}function Ct(){return d(Y.querySelectorAll("[data-tippy-root]"))}function At(t){W.clearDelayTimeouts(),t&&it("onTrigger",[W,t]),ft();var e=nt(!0),n=G(),r=n[0],i=n[1];T.isTouch&&"hold"===r&&i&&(e=i),e?c=setTimeout((function(){W.show()}),e):W.show()}function Ot(t){if(W.clearDelayTimeouts(),it("onUntrigger",[W,t]),W.state.isVisible){if(!(W.props.trigger.indexOf("mouseenter")>=0&&W.props.trigger.indexOf("click")>=0&&["mouseleave","mousemove"].indexOf(t.type)>=0&&P)){var e=nt(!1);e?m=setTimeout((function(){W.state.isVisible&&W.hide()}),e):h=requestAnimationFrame((function(){W.hide()}))}}else lt()}}function _(t,e){void 0===e&&(e={});var n=D.plugins.concat(e.plugins||[]);document.addEventListener("touchstart",A,i),window.addEventListener("blur",L);var r=Object.assign({},e,{plugins:n}),o=b(t).reduce((function(t,e){var n=e&&U(e,r);return n&&t.push(n),t}),[]);return m(t)?o[0]:o}_.defaultProps=D,_.setDefaultProps=function(t){Object.keys(t).forEach((function(e){D[e]=t[e]}))},_.currentInput=T;var z={mouseover:"mouseenter",focusin:"focus",click:"click"};var F={name:"animateFill",defaultValue:!1,fn:function(t){var e;if(!(null==(e=t.props.render)?void 0:e.$$tippy))return{};var n=I(t.popper),r=n.box,i=n.content,o=t.props.animateFill?function(){var t=v();return t.className="tippy-backdrop",x([t],"hidden"),t}():null;return{onCreate:function(){o&&(r.insertBefore(o,r.firstElementChild),r.setAttribute("data-animatefill",""),r.style.overflow="hidden",t.setProps({arrow:!1,animation:"shift-away"}))},onMount:function(){if(o){var t=r.style.transitionDuration,e=Number(t.replace("ms",""));i.style.transitionDelay=Math.round(e/10)+"ms",o.style.transitionDuration=t,x([o],"visible")}},onShow:function(){o&&(o.style.transitionDuration="0ms")},onHide:function(){o&&x([o],"hidden")}}}};var W={clientX:0,clientY:0},X=[];function Y(t){var e=t.clientX,n=t.clientY;W={clientX:e,clientY:n}}var q={name:"followCursor",defaultValue:!1,fn:function(t){var e=t.reference,n=w(t.props.triggerTarget||e),r=!1,i=!1,o=!0,a=t.props;function s(){return"initial"===t.props.followCursor&&t.state.isVisible}function u(){n.addEventListener("mousemove",f)}function c(){n.removeEventListener("mousemove",f)}function p(){r=!0,t.setProps({getReferenceClientRect:null}),r=!1}function f(n){var r=!n.target||e.contains(n.target),i=t.props.followCursor,o=n.clientX,a=n.clientY,s=e.getBoundingClientRect(),u=o-s.left,c=a-s.top;!r&&t.props.interactive||t.setProps({getReferenceClientRect:function(){var t=e.getBoundingClientRect(),n=o,r=a;"initial"===i&&(n=t.left+u,r=t.top+c);var s="horizontal"===i?t.top:r,p="vertical"===i?t.right:n,f="horizontal"===i?t.bottom:r,l="vertical"===i?t.left:n;return{width:p-l,height:f-s,top:s,right:p,bottom:f,left:l}}})}function l(){t.props.followCursor&&(X.push({instance:t,doc:n}),function(t){t.addEventListener("mousemove",Y)}(n))}function d(){0===(X=X.filter((function(e){return e.instance!==t}))).filter((function(t){return t.doc===n})).length&&function(t){t.removeEventListener("mousemove",Y)}(n)}return{onCreate:l,onDestroy:d,onBeforeUpdate:function(){a=t.props},onAfterUpdate:function(e,n){var o=n.followCursor;r||void 0!==o&&a.followCursor!==o&&(d(),o?(l(),!t.state.isMounted||i||s()||u()):(c(),p()))},onMount:function(){t.props.followCursor&&!i&&(o&&(f(W),o=!1),s()||u())},onTrigger:function(t,e){g(e)&&(W={clientX:e.clientX,clientY:e.clientY}),i="focus"===e.type},onHidden:function(){t.props.followCursor&&(p(),c(),o=!0)}}}};var $={name:"inlinePositioning",defaultValue:!1,fn:function(t){var e,n=t.reference;var r=-1,i=!1,o={name:"tippyInlinePositioning",enabled:!0,phase:"afterWrite",fn:function(i){var o=i.state;t.props.inlinePositioning&&(e!==o.placement&&t.setProps({getReferenceClientRect:function(){return function(t){return function(t,e,n,r){if(n.length<2||null===t)return e;if(2===n.length&&r>=0&&n[0].left>n[1].right)return n[r]||e;switch(t){case"top":case"bottom":var i=n[0],o=n[n.length-1],a="top"===t,s=i.top,u=o.bottom,c=a?i.left:o.left,p=a?i.right:o.right;return{top:s,bottom:u,left:c,right:p,width:p-c,height:u-s};case"left":case"right":var f=Math.min.apply(Math,n.map((function(t){return t.left}))),l=Math.max.apply(Math,n.map((function(t){return t.right}))),d=n.filter((function(e){return"left"===t?e.left===f:e.right===l})),v=d[0].top,m=d[d.length-1].bottom;return{top:v,bottom:m,left:f,right:l,width:l-f,height:m-v};default:return e}}(l(t),n.getBoundingClientRect(),d(n.getClientRects()),r)}(o.placement)}}),e=o.placement)}};function a(){var e;i||(e=function(t,e){var n;return{popperOptions:Object.assign({},t.popperOptions,{modifiers:[].concat(((null==(n=t.popperOptions)?void 0:n.modifiers)||[]).filter((function(t){return t.name!==e.name})),[e])})}}(t.props,o),i=!0,t.setProps(e),i=!1)}return{onCreate:a,onAfterUpdate:a,onTrigger:function(e,n){if(g(n)){var i=d(t.reference.getClientRects()),o=i.find((function(t){return t.left-2<=n.clientX&&t.right+2>=n.clientX&&t.top-2<=n.clientY&&t.bottom+2>=n.clientY}));r=i.indexOf(o)}},onUntrigger:function(){r=-1}}}};var J={name:"sticky",defaultValue:!1,fn:function(t){var e=t.reference,n=t.popper;function r(e){return!0===t.props.sticky||t.props.sticky===e}var i=null,o=null;function a(){var s=r("reference")?(t.popperInstance?t.popperInstance.state.elements.reference:e).getBoundingClientRect():null,u=r("popper")?n.getBoundingClientRect():null;(s&&G(i,s)||u&&G(o,u))&&t.popperInstance&&t.popperInstance.update(),i=s,o=u,t.state.isMounted&&requestAnimationFrame(a)}return{onMount:function(){t.props.sticky&&a()}}}};function G(t,e){return!t||!e||(t.top!==e.top||t.right!==e.right||t.bottom!==e.bottom||t.left!==e.left)}return e&&function(t){var e=document.createElement("style");e.textContent=t,e.setAttribute("data-tippy-stylesheet","");var n=document.head,r=document.querySelector("head>style,head>link");r?n.insertBefore(e,r):n.appendChild(e)}('.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1}'),_.setDefaultProps({plugins:[F,q,$,J],render:S}),_.createSingleton=function(t,e){void 0===e&&(e={});var n,r=t,i=[],o=e.overrides,a=[];function s(){i=r.map((function(t){return t.reference}))}function u(t){r.forEach((function(e){t?e.enable():e.disable()}))}function p(t){return r.map((function(e){var r=e.setProps;return e.setProps=function(i){r(i),e.reference===n&&t.setProps(i)},function(){e.setProps=r}}))}u(!1),s();var f={fn:function(){return{onDestroy:function(){u(!0)},onTrigger:function(t,e){var a=e.currentTarget,s=i.indexOf(a);if(a!==n){n=a;var u=(o||[]).concat("content").reduce((function(t,e){return t[e]=r[s].props[e],t}),{});t.setProps(Object.assign({},u,{getReferenceClientRect:"function"==typeof u.getReferenceClientRect?u.getReferenceClientRect:function(){return a.getBoundingClientRect()}}))}}}}},l=_(v(),Object.assign({},c(e,["overrides"]),{plugins:[f].concat(e.plugins||[]),triggerTarget:i})),d=l.setProps;return l.setProps=function(t){o=t.overrides||o,d(t)},l.setInstances=function(t){u(!0),a.forEach((function(t){return t()})),r=t,u(!1),s(),p(l),l.setProps({triggerTarget:i})},a=p(l),l},_.delegate=function(t,e){var n=[],r=[],i=!1,o=e.target,a=c(e,["target"]),s=Object.assign({},a,{trigger:"manual",touch:!1}),u=Object.assign({},a,{showOnCreate:!0}),f=_(t,s);function l(t){if(t.target&&!i){var n=t.target.closest(o);if(n){var a=n.getAttribute("data-tippy-trigger")||e.trigger||D.trigger;if(!n._tippy&&!("touchstart"===t.type&&"boolean"==typeof u.touch||"touchstart"!==t.type&&a.indexOf(z[t.type])<0)){var s=_(n,u);s&&(r=r.concat(s))}}}}function d(t,e,r,i){void 0===i&&(i=!1),t.addEventListener(e,r,i),n.push({node:t,eventType:e,handler:r,options:i})}return p(f).forEach((function(t){var e=t.destroy,o=t.enable,a=t.disable;t.destroy=function(t){void 0===t&&(t=!0),t&&r.forEach((function(t){t.destroy()})),r=[],n.forEach((function(t){var e=t.node,n=t.eventType,r=t.handler,i=t.options;e.removeEventListener(n,r,i)})),n=[],e()},t.enable=function(){o(),r.forEach((function(t){return t.enable()})),i=!1},t.disable=function(){a(),r.forEach((function(t){return t.disable()})),i=!0},function(t){var e=t.reference;d(e,"touchstart",l),d(e,"mouseover",l),d(e,"focusin",l),d(e,"click",l)}(t)})),f},_.hideAll=function(t){var e=void 0===t?{}:t,n=e.exclude,r=e.duration;N.forEach((function(t){var e=!1;if(n&&(e=h(n)?t.reference===n:t.popper===n.popper),!e){var i=t.props.duration;t.setProps({duration:r}),t.hide(),t.state.isDestroyed||t.setProps({duration:i})}}))},_.roundArrow='<svg width="16" height="6" xmlns="http://www.w3.org/2000/svg"><path d="M0 6s1.796-.013 4.67-3.615C5.851.9 6.93.006 8 0c1.07-.006 2.148.887 3.343 2.385C14.233 6.005 16 6 16 6H0z"></svg>',_}));
assets/js/tippy/tippy.css ADDED
@@ -0,0 +1 @@
 
1
+ .tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1}
assets/webfonts/index.php CHANGED
@@ -1,2 +1,3 @@
1
  <?php
2
- //silent
 
1
  <?php
2
+
3
+ //silent
classes/class.archive.config.php CHANGED
@@ -1,48 +1,44 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- /**
4
- * @copyright 2016 Snap Creek LLC
5
- */
6
-
7
- // Exit if accessed directly
8
- if (! defined('DUPLICATOR_VERSION')) exit;
9
-
10
- class DUP_Archive_Config
11
- {
12
- //READ-ONLY: COMPARE VALUES
13
- public $created;
14
- public $version_dup;
15
- public $version_wp;
16
- public $version_db;
17
- public $version_php;
18
- public $version_os;
19
- public $dbInfo;
20
- //READ-ONLY: GENERAL
21
- public $url_old;
22
- public $opts_delete;
23
- public $blogname;
24
- public $wproot;
25
- public $wplogin_url;
26
- public $relative_content_dir;
27
- public $exportOnlyDB;
28
- public $installSiteOverwriteOn;
29
-
30
- //PRE-FILLED: GENERAL
31
- public $secure_on;
32
- public $secure_pass;
33
- public $skipscan;
34
- public $dbhost;
35
- public $dbname;
36
- public $dbuser;
37
- public $dbpass;
38
- public $dbcharset;
39
- public $dbcollation;
40
-
41
- // MULTISITE
42
- public $mu_mode;
43
-
44
- public $wp_tableprefix;
45
-
46
- public $is_outer_root_wp_config_file;
47
- public $is_outer_root_wp_content_dir;
48
- }
1
+ <?php
2
+
3
+ class DUP_Archive_Config
4
+ {
5
+ public $dup_type = 'lite';
6
+ public $created = '';
7
+ public $version_dup = '';
8
+ public $version_wp = '';
9
+ public $version_db = '';
10
+ public $version_php = '';
11
+ public $version_os = '';
12
+ public $blogname = '';
13
+ public $exportOnlyDB = false;
14
+ public $secure_on = false;
15
+ public $secure_pass = '';
16
+ public $dbhost = null;
17
+ public $dbname = null;
18
+ public $dbuser = null;
19
+ public $dbpass = null;
20
+ public $cpnl_host = null;
21
+ public $cpnl_user = null;
22
+ public $cpnl_pass = null;
23
+ public $cpnl_enable = null;
24
+ public $wp_tableprefix = '';
25
+ public $mu_mode = 0;
26
+ public $mu_generation = 0;
27
+ public $mu_siteadmins = array();
28
+ public $subsites = array();
29
+ public $main_site_id = 1;
30
+ public $mu_is_filtered = false;
31
+ public $license_limit = 0;
32
+ public $dbInfo = array();
33
+ public $packInfo = array();
34
+ public $fileInfo = array();
35
+ public $wpInfo = array();
36
+ public $opts_delete = array();
37
+ public $brand = array();
38
+ public $overwriteInstallerParams = array();
39
+ public $installer_base_name = '';
40
+ public $installer_backup_name = '';
41
+ public $package_name = '';
42
+ public $package_hash = '';
43
+ public $package_notes = '';
44
+ }
 
 
 
 
classes/class.constants.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  defined("ABSPATH") || exit;
3
  class DUP_Constants
4
  {
1
  <?php
2
+
3
  defined("ABSPATH") || exit;
4
  class DUP_Constants
5
  {
classes/class.db.php CHANGED
@@ -1,261 +1,316 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- /**
4
- * Lightweight abstraction layer for common simple database routines
5
- *
6
- * Standard: PSR-2
7
- * @link http://www.php-fig.org/psr/psr-2
8
- *
9
- * @package Duplicator
10
- * @subpackage classes/utilities
11
- * @copyright (c) 2017, Snapcreek LLC
12
- *
13
- */
14
- // Exit if accessed directly
15
- if (!defined('DUPLICATOR_VERSION')) exit;
16
-
17
- class DUP_DB extends wpdb
18
- {
19
- const MAX_TABLE_COUNT_IN_PACKET = 100;
20
-
21
- public static $remove_placeholder_escape_exists = null;
22
-
23
- public static function init()
24
- {
25
- global $wpdb;
26
- self::$remove_placeholder_escape_exists = method_exists($wpdb, 'remove_placeholder_escape');
27
- }
28
-
29
- /**
30
- * Get the requested MySQL system variable
31
- *
32
- * @param string $name The database variable name to lookup
33
- *
34
- * @return string the server variable to query for
35
- */
36
- public static function getVariable($name)
37
- {
38
- global $wpdb;
39
- if (strlen($name)) {
40
- $row = $wpdb->get_row("SHOW VARIABLES LIKE '{$name}'", ARRAY_N);
41
- return isset($row[1]) ? $row[1] : null;
42
- } else {
43
- return null;
44
- }
45
- }
46
-
47
- /**
48
- * Gets the MySQL database version number
49
- *
50
- * @param bool $full True: Gets the full version
51
- * False: Gets only the numeric portion i.e. 5.5.6 or 10.1.2 (for MariaDB)
52
- *
53
- * @return false|string 0 on failure, version number on success
54
- */
55
- public static function getVersion($full = false)
56
- {
57
- global $wpdb;
58
-
59
- if ($full) {
60
- $version = self::getVariable('version');
61
- } else {
62
- $version = preg_replace('/[^0-9.].*/', '', self::getVariable('version'));
63
- }
64
-
65
- //Fall-back for servers that have restricted SQL for SHOW statement
66
- if (empty($version)) {
67
- $version = $wpdb->db_version();
68
- }
69
-
70
- return empty($version) ? 0 : $version;
71
- }
72
-
73
- /**
74
- * Try to return the mysqldump path on Windows servers
75
- *
76
- * @return boolean|string
77
- */
78
- public static function getWindowsMySqlDumpRealPath()
79
- {
80
- if (function_exists('php_ini_loaded_file')) {
81
- $get_php_ini_path = php_ini_loaded_file();
82
- if (file_exists($get_php_ini_path)) {
83
- $search = array(
84
- dirname(dirname($get_php_ini_path)).'/mysql/bin/mysqldump.exe',
85
- dirname(dirname(dirname($get_php_ini_path))).'/mysql/bin/mysqldump.exe',
86
- dirname(dirname($get_php_ini_path)).'/mysql/bin/mysqldump',
87
- dirname(dirname(dirname($get_php_ini_path))).'/mysql/bin/mysqldump',
88
- );
89
-
90
- foreach ($search as $mysqldump) {
91
- if (file_exists($mysqldump)) {
92
- return str_replace("\\", "/", $mysqldump);
93
- }
94
- }
95
- }
96
- }
97
-
98
- unset($search);
99
- unset($get_php_ini_path);
100
-
101
- return false;
102
- }
103
-
104
- /**
105
- * Returns the correct database build mode PHP, MYSQLDUMP, PHPCHUNKING
106
- *
107
- * @return string Returns a string with one of theses three values PHP, MYSQLDUMP, PHPCHUNKING
108
- */
109
- public static function getBuildMode()
110
- {
111
- $package_mysqldump = DUP_Settings::Get('package_mysqldump');
112
- $mysqlDumpPath = DUP_DB::getMySqlDumpPath();
113
-
114
- return ($mysqlDumpPath && $package_mysqldump) ? 'MYSQLDUMP' : 'PHP';
115
- }
116
-
117
- /**
118
- * Returns the mysqldump path if the server is enabled to execute it otherwise false
119
- *
120
- * @return boolean|string
121
- */
122
- public static function getMySqlDumpPath()
123
- {
124
- //Is shell_exec possible
125
- if (!DUP_Util::hasShellExec()) {
126
- return false;
127
- }
128
-
129
- $custom_mysqldump_path = DUP_Settings::Get('package_mysqldump_path');
130
- $custom_mysqldump_path = (strlen($custom_mysqldump_path)) ? $custom_mysqldump_path : '';
131
-
132
- //Common Windows Paths
133
- if (DUP_Util::isWindows()) {
134
- $paths = array(
135
- $custom_mysqldump_path,
136
- 'mysqldump.exe',
137
- self::getWindowsMySqlDumpRealPath(),
138
- 'C:/xampp/mysql/bin/mysqldump.exe',
139
- 'C:/Program Files/xampp/mysql/bin/mysqldump',
140
- 'C:/Program Files/MySQL/MySQL Server 6.0/bin/mysqldump',
141
- 'C:/Program Files/MySQL/MySQL Server 5.5/bin/mysqldump',
142
- 'C:/Program Files/MySQL/MySQL Server 5.4/bin/mysqldump'
143
- );
144
-
145
- //Common Linux Paths
146
- } else {
147
- $paths = array(
148
- $custom_mysqldump_path,
149
- 'mysqldump',
150
- '/usr/local/bin/mysqldump',
151
- '/usr/local/mysql/bin/mysqldump',
152
- '/usr/mysql/bin/mysqldump',
153
- '/usr/bin/mysqldump',
154
- '/opt/local/lib/mysql6/bin/mysqldump',
155
- '/opt/local/lib/mysql5/bin/mysqldump',
156
- '/usr/bin/mysqldump',
157
- );
158
- }
159
-
160
- $exec_available = function_exists('exec');
161
- foreach ($paths as $path) {
162
- if (@file_exists($path)) {
163
- if (DUP_Util::isExecutable($path)) {
164
- return $path;
165
- }
166
- } elseif ($exec_available) {
167
- $out = array();
168
- $rc = -1;
169
- $cmd = $path . ' --help';
170
- @exec($cmd, $out, $rc);
171
- if ($rc === 0) {
172
- return $path;
173
- }
174
- }
175
- }
176
-
177
- return false;
178
- }
179
-
180
- /**
181
- * Returns all collation types that are assigned to the tables and columns table in
182
- * the current database. Each element in the array is unique
183
- *
184
- * @param array &$tablesToInclude A list of tables to include in the search.
185
- * Parameter does not change in the function, is passed by reference only to avoid copying.
186
- *
187
- * @return array Returns an array with all the collation types being used
188
- */
189
- public static function getTableCollationList(&$tablesToInclude)
190
- {
191
- global $wpdb;
192
- static $collations = null;
193
- if (is_null($collations)) {
194
- $collations = array();
195
- //use half the number of tables since we are using them twice
196
- foreach (array_chunk($tablesToInclude, self::MAX_TABLE_COUNT_IN_PACKET) as $tablesChunk) {
197
- $sqlTables = implode(",", array_map(array(__CLASS__, 'escValueToQueryString'), $tablesChunk));
198
-
199
- //UNION is by default DISTINCT
200
- $query = "SELECT `COLLATION_NAME` FROM `information_schema`.`columns` WHERE `COLLATION_NAME` IS NOT NULL AND `table_schema` = '{$wpdb->dbname}' "
201
- . "AND `table_name` in (" . $sqlTables . ")"
202
- . "UNION SELECT `TABLE_COLLATION` FROM `information_schema`.`tables` WHERE `TABLE_COLLATION` IS NOT NULL AND `table_schema` = '{$wpdb->dbname}' "
203
- . "AND `table_name` in (" . $sqlTables . ")";
204
-
205
- if (!$wpdb->query($query)) {
206
- DUP_Log::Info("GET TABLE COLLATION ERROR: " . $wpdb->last_error);
207
- continue;
208
- }
209
-
210
- $collations = array_unique(array_merge($collations, $wpdb->get_col()));
211
- }
212
- sort($collations);
213
- }
214
- return $collations;
215
- }
216
-
217
- /**
218
- * Returns an escaped SQL string
219
- *
220
- * @param string $sql The SQL to escape
221
- * @param bool $removePlaceholderEscape Patch for how the default WP function works.
222
- *
223
- * @return boolean|string
224
- * @also see: https://make.wordpress.org/core/2017/10/31/changed-behaviour-of-esc_sql-in-wordpress-4-8-3/
225
- */
226
- public static function escSQL($sql, $removePlaceholderEscape = false)
227
- {
228
- global $wpdb;
229
-
230
- $removePlaceholderEscape = $removePlaceholderEscape && self::$remove_placeholder_escape_exists;
231
-
232
- if ($removePlaceholderEscape) {
233
- return $wpdb->remove_placeholder_escape(@esc_sql($sql));
234
- } else {
235
- return @esc_sql($sql);
236
- }
237
- }
238
-
239
- /**
240
- * this function escape sql string without add and remove remove_placeholder_escape
241
- * doesn't work on array
242
- *
243
- * @global type $wpdb
244
- * @param mixed $sql
245
- * @return string
246
- */
247
- public static function escValueToQueryString($value)
248
- {
249
- global $wpdb;
250
-
251
- if (is_null($value)) {
252
- return 'NULL';
253
- }
254
-
255
- if ($wpdb->use_mysqli) {
256
- return '"'.mysqli_real_escape_string($wpdb->dbh, $value).'"';
257
- } else {
258
- return '"'.mysql_real_escape_string($value, $wpdb->dbh).'"';
259
- }
260
- }
261
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
4
+ /**
5
+ * Lightweight abstraction layer for common simple database routines
6
+ *
7
+ * Standard: PSR-2
8
+ * @link http://www.php-fig.org/psr/psr-2
9
+ *
10
+ * @package Duplicator
11
+ * @subpackage classes/utilities
12
+ * @copyright (c) 2017, Snapcreek LLC
13
+ *
14
+ */
15
+ // Exit if accessed directly
16
+ if (!defined('DUPLICATOR_VERSION')) {
17
+ exit;
18
+ }
19
+
20
+ class DUP_DB extends wpdb
21
+ {
22
+ const MAX_TABLE_COUNT_IN_PACKET = 100;
23
+ public static $remove_placeholder_escape_exists = null;
24
+ public static function init()
25
+ {
26
+ global $wpdb;
27
+ self::$remove_placeholder_escape_exists = method_exists($wpdb, 'remove_placeholder_escape');
28
+ }
29
+
30
+ /**
31
+ * Get the requested MySQL system variable
32
+ *
33
+ * @param string $name The database variable name to lookup
34
+ *
35
+ * @return string the server variable to query for
36
+ */
37
+ public static function getVariable($name)
38
+ {
39
+ global $wpdb;
40
+ if (strlen($name)) {
41
+ $row = $wpdb->get_row("SHOW VARIABLES LIKE '{$name}'", ARRAY_N);
42
+ return isset($row[1]) ? $row[1] : null;
43
+ } else {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Gets the MySQL database version number
50
+ *
51
+ * @param bool $full True: Gets the full version
52
+ * False: Gets only the numeric portion i.e. 5.5.6 or 10.1.2 (for MariaDB)
53
+ *
54
+ * @return false|string 0 on failure, version number on success
55
+ */
56
+ public static function getVersion($full = false)
57
+ {
58
+ global $wpdb;
59
+ if ($full) {
60
+ $version = self::getVariable('version');
61
+ } else {
62
+ $version = preg_replace('/[^0-9.].*/', '', self::getVariable('version'));
63
+ }
64
+
65
+ //Fall-back for servers that have restricted SQL for SHOW statement
66
+ if (empty($version)) {
67
+ $version = $wpdb->db_version();
68
+ }
69
+
70
+ return empty($version) ? 0 : $version;
71
+ }
72
+
73
+ /**
74
+ * Try to return the mysqldump path on Windows servers
75
+ *
76
+ * @return boolean|string
77
+ */
78
+ public static function getWindowsMySqlDumpRealPath()
79
+ {
80
+ if (function_exists('php_ini_loaded_file')) {
81
+ $get_php_ini_path = php_ini_loaded_file();
82
+ if (file_exists($get_php_ini_path)) {
83
+ $search = array(
84
+ dirname(dirname($get_php_ini_path)) . '/mysql/bin/mysqldump.exe',
85
+ dirname(dirname(dirname($get_php_ini_path))) . '/mysql/bin/mysqldump.exe',
86
+ dirname(dirname($get_php_ini_path)) . '/mysql/bin/mysqldump',
87
+ dirname(dirname(dirname($get_php_ini_path))) . '/mysql/bin/mysqldump',
88
+ );
89
+ foreach ($search as $mysqldump) {
90
+ if (file_exists($mysqldump)) {
91
+ return str_replace("\\", "/", $mysqldump);
92
+ }
93
+ }
94
+ }
95
+ }
96
+
97
+ unset($search);
98
+ unset($get_php_ini_path);
99
+ return false;
100
+ }
101
+
102
+ /**
103
+ * Returns the correct database build mode PHP, MYSQLDUMP, PHPCHUNKING
104
+ *
105
+ * @return string Returns a string with one of theses three values PHP, MYSQLDUMP, PHPCHUNKING
106
+ */
107
+ public static function getBuildMode()
108
+ {
109
+ $package_mysqldump = DUP_Settings::Get('package_mysqldump');
110
+ $mysqlDumpPath = DUP_DB::getMySqlDumpPath();
111
+ return ($mysqlDumpPath && $package_mysqldump) ? 'MYSQLDUMP' : 'PHP';
112
+ }
113
+
114
+ /**
115
+ * Returns the mysqldump path if the server is enabled to execute it otherwise false
116
+ *
117
+ * @return boolean|string
118
+ */
119
+ public static function getMySqlDumpPath()
120
+ {
121
+ //Is shell_exec possible
122
+ if (!DUP_Util::hasShellExec()) {
123
+ return false;
124
+ }
125
+
126
+ $custom_mysqldump_path = DUP_Settings::Get('package_mysqldump_path');
127
+ $custom_mysqldump_path = (strlen($custom_mysqldump_path)) ? $custom_mysqldump_path : '';
128
+ //Common Windows Paths
129
+ if (DUP_Util::isWindows()) {
130
+ $paths = array(
131
+ $custom_mysqldump_path,
132
+ 'mysqldump.exe',
133
+ self::getWindowsMySqlDumpRealPath(),
134
+ 'C:/xampp/mysql/bin/mysqldump.exe',
135
+ 'C:/Program Files/xampp/mysql/bin/mysqldump',
136
+ 'C:/Program Files/MySQL/MySQL Server 6.0/bin/mysqldump',
137
+ 'C:/Program Files/MySQL/MySQL Server 5.5/bin/mysqldump',
138
+ 'C:/Program Files/MySQL/MySQL Server 5.4/bin/mysqldump'
139
+ );
140
+ //Common Linux Paths
141
+ } else {
142
+ $paths = array(
143
+ $custom_mysqldump_path,
144
+ 'mysqldump',
145
+ '/usr/local/bin/mysqldump',
146
+ '/usr/local/mysql/bin/mysqldump',
147
+ '/usr/mysql/bin/mysqldump',
148
+ '/usr/bin/mysqldump',
149
+ '/opt/local/lib/mysql6/bin/mysqldump',
150
+ '/opt/local/lib/mysql5/bin/mysqldump',
151
+ '/usr/bin/mysqldump',
152
+ );
153
+ }
154
+
155
+ $exec_available = function_exists('exec');
156
+ foreach ($paths as $path) {
157
+ if (@file_exists($path)) {
158
+ if (DUP_Util::isExecutable($path)) {
159
+ return $path;
160
+ }
161
+ } elseif ($exec_available) {
162
+ $out = array();
163
+ $rc = -1;
164
+ $cmd = $path . ' --help';
165
+ @exec($cmd, $out, $rc);
166
+ if ($rc === 0) {
167
+ return $path;
168
+ }
169
+ }
170
+ }
171
+
172
+ return false;
173
+ }
174
+
175
+ /**
176
+ * Get Sql query to create table which is given.
177
+ *
178
+ * @param string $table Table name
179
+ * @return string mysql query create table
180
+ */
181
+ private static function getCreateTableQuery($table)
182
+ {
183
+ $row = $GLOBALS['wpdb']->get_row('SHOW CREATE TABLE `' . esc_sql($table) . '`', ARRAY_N);
184
+ return $row[1];
185
+ }
186
+
187
+ /**
188
+ * Returns all collation types that are assigned to the tables in
189
+ * the current database. Each element in the array is unique
190
+ *
191
+ * @param array $tables A list of tables to include from the search
192
+ *
193
+ * @return array Returns an array with all the character set being used
194
+ */
195
+ public static function getTableCharSetList($tables)
196
+ {
197
+ $charSets = array();
198
+ try {
199
+ foreach ($tables as $table) {
200
+ $createTableQuery = self::getCreateTableQuery($table);
201
+ if (preg_match('/ CHARSET=([^\s;]+)/i', $createTableQuery, $charsetMatch)) {
202
+ if (!in_array($charsetMatch[1], $charSets)) {
203
+ $charSets[] = $charsetMatch[1];
204
+ }
205
+ }
206
+ }
207
+ return $charSets;
208
+ } catch (Exception $ex) {
209
+ return $charSets;
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Returns all collation types that are assigned to the tables and columns table in
215
+ * the current database. Each element in the array is unique
216
+ *
217
+ * @param array &$tablesToInclude A list of tables to include in the search.
218
+ * Parameter does not change in the function, is passed by reference only to avoid copying.
219
+ *
220
+ * @return array Returns an array with all the collation types being used
221
+ */
222
+ public static function getTableCollationList($tablesToInclude)
223
+ {
224
+ global $wpdb;
225
+ static $collations = null;
226
+ if (is_null($collations)) {
227
+ $collations = array();
228
+ //use half the number of tables since we are using them twice
229
+ foreach (array_chunk($tablesToInclude, self::MAX_TABLE_COUNT_IN_PACKET) as $tablesChunk) {
230
+ $sqlTables = implode(",", array_map(array(__CLASS__, 'escValueToQueryString'), $tablesChunk));
231
+ //UNION is by default DISTINCT
232
+ $query = "SELECT `COLLATION_NAME` FROM `information_schema`.`columns` WHERE `COLLATION_NAME` IS NOT NULL AND `table_schema` = '{$wpdb->dbname}' "
233
+ . "AND `table_name` in (" . $sqlTables . ")"
234
+ . "UNION SELECT `TABLE_COLLATION` FROM `information_schema`.`tables` WHERE `TABLE_COLLATION` IS NOT NULL AND `table_schema` = '{$wpdb->dbname}' "
235
+ . "AND `table_name` in (" . $sqlTables . ")";
236
+ if (!$wpdb->query($query)) {
237
+ DUP_Log::Info("GET TABLE COLLATION ERROR: " . $wpdb->last_error);
238
+ continue;
239
+ }
240
+
241
+ $collations = array_unique(array_merge($collations, $wpdb->get_col()));
242
+ }
243
+ sort($collations);
244
+ }
245
+ return $collations;
246
+ }
247
+
248
+ /**
249
+ * Returns list of MySQL engines used by $tablesToInclude in the current DB
250
+ *
251
+ * @param string[] $tablesToInclude tables to check the engines for
252
+ * @return null
253
+ * @throws Exception
254
+ */
255
+ public static function getTableEngineList($tablesToInclude)
256
+ {
257
+ global $wpdb;
258
+ static $engines = null;
259
+ if (is_null($engines)) {
260
+ $engines = array();
261
+ foreach (array_chunk($tablesToInclude, self::MAX_TABLE_COUNT_IN_PACKET) as $tablesChunk) {
262
+ $query = "SELECT DISTINCT `ENGINE` FROM `information_schema`.`tables` WHERE `ENGINE` IS NOT NULL AND `table_schema` = '{$wpdb->dbname}' "
263
+ . "AND `table_name` in (" . implode(",", array_map(array(__CLASS__, 'escValueToQueryString'), $tablesChunk)) . ")";
264
+ if (!$wpdb->query($query)) {
265
+ DUP_Log::info("GET TABLE ENGINES ERROR: " . $wpdb->last_error);
266
+ }
267
+
268
+ $engines = array_unique(array_merge($engines, $wpdb->get_col($query)));
269
+ }
270
+ }
271
+
272
+ return $engines;
273
+ }
274
+
275
+ /**
276
+ * Returns an escaped SQL string
277
+ *
278
+ * @param string $sql The SQL to escape
279
+ * @param bool $removePlaceholderEscape Patch for how the default WP function works.
280
+ *
281
+ * @return boolean|string
282
+ * @also see: https://make.wordpress.org/core/2017/10/31/changed-behaviour-of-esc_sql-in-wordpress-4-8-3/
283
+ */
284
+ public static function escSQL($sql, $removePlaceholderEscape = false)
285
+ {
286
+ global $wpdb;
287
+ $removePlaceholderEscape = $removePlaceholderEscape && self::$remove_placeholder_escape_exists;
288
+ if ($removePlaceholderEscape) {
289
+ return $wpdb->remove_placeholder_escape(@esc_sql($sql));
290
+ } else {
291
+ return @esc_sql($sql);
292
+ }
293
+ }
294
+
295
+ /**
296
+ * this function escape sql string without add and remove remove_placeholder_escape
297
+ * doesn't work on array
298
+ *
299
+ * @global type $wpdb
300
+ * @param mixed $sql
301
+ * @return string
302
+ */
303
+ public static function escValueToQueryString($value)
304
+ {
305
+ global $wpdb;
306
+ if (is_null($value)) {
307
+ return 'NULL';
308
+ }
309
+
310
+ if ($wpdb->use_mysqli) {
311
+ return '"' . mysqli_real_escape_string($wpdb->dbh, $value) . '"';
312
+ } else {
313
+ return '"' . mysql_real_escape_string($value, $wpdb->dbh) . '"';
314
+ }
315
+ }
316
+ }
classes/class.io.php CHANGED
@@ -1,121 +1,18 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- /**
4
- * @copyright 2018 Snap Creek LLC
5
- * Class for all IO operations
6
- */
7
-
8
- // Exit if accessed directly
9
- if (! defined('DUPLICATOR_VERSION')) exit;
10
-
11
- class DUP_IO
12
- {
13
- /**
14
- * Safely deletes a file
15
- *
16
- * @param string $file The full filepath to the file
17
- *
18
- * @return TRUE on success or if file does not exist. FALSE on failure
19
- */
20
- public static function deleteFile($file)
21
- {
22
- if (file_exists($file)) {
23
- if (@unlink($file) === false) {
24
- DUP_Log::Info("Duplicator could not delete file: '{$file}'");
25
- return false;
26
- }
27
- }
28
- return true;
29
- }
30
-
31
- /**
32
- * Removes a directory recursively except for the root of a WP Site
33
- *
34
- * @param string $directory The full filepath to the directory to remove
35
- *
36
- * @return TRUE on success FALSE on failure
37
- */
38
- public static function deleteTree($directory)
39
- {
40
- $success = true;
41
-
42
- if(!file_exists("{$directory}/wp-config.php")) {
43
- $filenames = array_diff(scandir($directory), array('.', '..'));
44
-
45
- foreach ($filenames as $filename) {
46
- if (is_dir("$directory/$filename")) {
47
- $success = self::deleteTree("$directory/$filename");
48
- } else {
49
- $success = @unlink("$directory/$filename");
50
- }
51
-
52
- if ($success === false) {
53
- break;
54
- }
55
- }
56
- } else {
57
- return false;
58
- }
59
-
60
- return $success && @rmdir($directory);
61
- }
62
-
63
- /**
64
- * Safely copies a file to a directory
65
- *
66
- * @param string $source_file The full filepath to the file to copy
67
- * @param string $dest_dir The full path to the destination directory were the file will be copied
68
- * @param string $delete_first Delete file before copying the new one
69
- *
70
- * @return TRUE on success or if file does not exist. FALSE on failure
71
- */
72
- public static function copyFile($source_file, $dest_dir, $delete_first = false)
73
- {
74
- //Create directory
75
- if (file_exists($dest_dir) == false)
76
- {
77
- if (wp_mkdir_p($dest_dir) === false) {
78
- return false;
79
- }
80
- }
81
-
82
- //Remove file with same name before copy
83
- $filename = basename($source_file);
84
- $dest_filepath = $dest_dir . "/$filename";
85
- if($delete_first)
86
- {
87
- self::deleteFile($dest_filepath);
88
- }
89
-
90
- return copy($source_file, $dest_filepath);
91
- }
92
-
93
- /**
94
- * Prepends data to an existing file
95
- *
96
- * @param string $file The full file path to the file
97
- * @param string $content The content to prepend to the file
98
- *
99
- * @return TRUE on success or if file does not exist. FALSE on failure
100
- */
101
- public static function fwritePrepend($file, $prepend)
102
- {
103
- if (!file_exists($file) || !is_writable($file)) {
104
- return false;
105
- }
106
-
107
- $handle = fopen($file, "r+");
108
- $len = strlen($prepend);
109
- $final_len = filesize($file) + $len;
110
- $cache_old = fread($handle, $len);
111
- rewind($handle);
112
- $i = 1;
113
- while (ftell($handle) < $final_len) {
114
- fwrite($handle, $prepend);
115
- $prepend = $cache_old;
116
- $cache_old = fread($handle, $len);
117
- fseek($handle, $i * $len);
118
- $i++;
119
- }
120
- }
121
- }
1
+ <?php
2
+ /**
3
+ * @copyright 2022 Snap Creek LLC
4
+ * Class for all IO operations
5
+ */
6
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
7
+ if (! defined('DUPLICATOR_VERSION')) {
8
+ exit;
9
+ }
10
+
11
+ /**
12
+ * This class is deprecated. Please use:
13
+ * Duplicator\Libs\Snap\SnapIO
14
+ */
15
+ class DUP_IO
16
+ {
17
+ /* !!DO NOT IMPLMENT HERE!! */
18
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classes/class.logging.php CHANGED
@@ -1,7 +1,12 @@
1
  <?php
 
 
 
2
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
  // Exit if accessed directly
4
- if (!defined('DUPLICATOR_VERSION')) exit;
 
 
5
 
6
  /**
7
  * Helper Class for logging
@@ -9,9 +14,9 @@ if (!defined('DUPLICATOR_VERSION')) exit;
9
  */
10
  abstract class Dup_ErrorBehavior
11
  {
12
- const LogOnly = 0;
13
- const ThrowException = 1;
14
- const Quit = 2;
15
  }
16
 
17
  class DUP_Log
@@ -27,14 +32,14 @@ class DUP_Log
27
  private static $traceEnabled = false;
28
 
29
  public static $profileLogs = null;
30
-
31
- /**
32
- * Init this static object
33
- */
34
- public static function Init()
35
- {
36
- self::$traceEnabled = (DUP_Settings::Get('trace_log_enabled') == 1);
37
- }
38
 
39
  /**
40
  * Open a log file connection for writing to the package log file
@@ -49,7 +54,7 @@ class DUP_Log
49
  throw new Exception("A name value is required to open a file log.");
50
  }
51
  self::Close();
52
- if ((self::$logFileHandle = @fopen(DUP_Settings::getSsdirPath()."/{$nameHash}.log", "a+")) === false) {
53
  self::$logFileHandle = null;
54
  return false;
55
  } else {
@@ -69,7 +74,7 @@ class DUP_Log
69
  public static function Close()
70
  {
71
  $result = true;
72
-
73
  if (!is_null(self::$logFileHandle)) {
74
  $result = @fclose(self::$logFileHandle);
75
  self::$logFileHandle = null;
@@ -81,13 +86,13 @@ class DUP_Log
81
 
82
  /**
83
  * General information send to the package log if opened
84
- * @param string $msg The message to log
85
  *
86
  * REPLACE TO DEBUG: Memory consumption as script runs
87
- * $results = DUP_Util::byteSize(memory_get_peak_usage(true)) . "\t" . $msg;
88
- * @fwrite(self::$logFileHandle, "{$results} \n");
89
  *
90
- * @param string $msg The message to log
91
  *
92
  * @return null
93
  */
@@ -97,160 +102,172 @@ class DUP_Log
97
  self::Trace($msg);
98
  }
99
  if (!is_null(self::$logFileHandle)) {
100
- @fwrite(self::$logFileHandle, $msg."\n");
101
  }
102
  }
103
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  public static function print_r_info($val, $name = '')
105
  {
106
- $msg = empty($name) ? '' : 'VALUE '.$name.': ';
107
  $msg .= print_r($val, true);
108
  self::info($msg);
109
  }
110
 
111
- /**
112
- * Does the trace file exists
113
- *
114
- * @return bool Returns true if an active trace file exists
115
- */
116
- public static function TraceFileExists()
117
- {
118
- $file_path = self::getTraceFilepath();
119
- return file_exists($file_path);
120
- }
121
-
122
- /**
123
- * Gets the current file size of the active trace file
124
- *
125
- * @return string Returns a human readable file size of the active trace file
126
- */
127
- public static function getTraceStatus()
128
- {
129
- $file_path = DUP_Log::getTraceFilepath();
130
- $backup_path = DUP_Log::getBackupTraceFilepath();
131
-
132
- if (file_exists($file_path)) {
133
 
 
134
  $filesize = is_file($file_path) ? @filesize($file_path) : 0;
135
 
136
  //Its possible mulitple trace log files exist due to size
137
- if (is_file($backup_path)) {
138
- $filesize += @filesize($backup_path);
139
- }
140
-
141
- $message = sprintf('%1$s', DUP_Util::byteSize($filesize));
142
- } else {
143
- $message = esc_html__('No Log', 'duplicator');
144
- }
145
-
146
- return $message;
147
- }
148
-
149
- // RSR TODO: Swap trace logic out for real trace later
150
- public static function Trace($message, $calling_function_override = null)
151
- {
152
-
153
- if (self::$traceEnabled) {
154
- $unique_id = sprintf("%08x", abs(crc32($_SERVER['REMOTE_ADDR'].$_SERVER['REQUEST_TIME'].$_SERVER['REMOTE_PORT'])));
155
-
156
- if ($calling_function_override == null) {
157
- $calling_function = DupLiteSnapLibUtil::getCallingFunctionName();
158
- } else {
159
- $calling_function = $calling_function_override;
160
- }
161
-
162
- if (is_object($message)) {
163
- $ov = get_object_vars($message);
164
- $message = print_r($ov, true);
165
- } else if (is_array($message)) {
166
- $message = print_r($message, true);
167
- }
168
-
169
- $logging_message = "{$unique_id}|{$calling_function} | {$message}";
170
- $ticks = time() + ((int) get_option('gmt_offset') * 3600);
171
- $formatted_time = date('d-m-H:i:s', $ticks);
172
- $formatted_logging_message = "{$formatted_time}|DUP|{$logging_message} \r\n";
173
-
174
- // Always write to error log - if they don't want the info they can turn off WordPress error logging or tracing
175
- self::ErrLog($logging_message);
176
-
177
- // Everything goes to the plugin log, whether it's part of package generation or not.
178
- self::WriteToTrace($formatted_logging_message);
179
- }
180
- }
181
 
182
  public static function print_r_trace($val, $name = '', $calling_function_override = null)
183
  {
184
- $msg = empty($name) ? '' : 'VALUE '.$name.': ';
185
  $msg .= print_r($val, true);
186
  self::trace($msg, $calling_function_override);
187
  }
188
 
189
- public static function errLog($message)
190
- {
191
- $message = 'DUP:'.$message;
192
- error_log($message);
193
- }
194
-
195
- public static function TraceObject($msg, $o, $log_private_members = true)
196
- {
197
- if (self::$traceEnabled) {
198
- if (!$log_private_members) {
199
- $o = get_object_vars($o);
200
- }
201
- self::Trace($msg.':'.print_r($o, true));
202
- }
203
- }
204
-
205
- public static function GetDefaultKey()
206
- {
207
- $auth_key = defined('AUTH_KEY') ? AUTH_KEY : 'atk';
208
- $auth_key .= defined('DB_HOST') ? DB_HOST : 'dbh';
209
- $auth_key .= defined('DB_NAME') ? DB_NAME : 'dbn';
210
- $auth_key .= defined('DB_USER') ? DB_USER : 'dbu';
211
- return hash('md5', $auth_key);
212
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
213
 
214
  /**
215
- * Gets the current file size of the old trace file "1"
216
- *
217
- * @return string Returns a human readable file size of the active trace file
218
- */
219
- public static function GetBackupTraceFilepath()
220
- {
221
- $default_key = self::getDefaultKey();
222
- $backup_log_filename = "dup_$default_key.log1";
223
- $backup_path = DUP_Settings::getSsdirPath()."/".$backup_log_filename;
224
- return $backup_path;
225
- }
226
-
227
- /**
228
- * Gets the active trace file path
229
- *
230
- * @return string Returns the full path to the active trace file (i.e. dup-pro_hash.log)
231
- */
232
- public static function GetTraceFilepath()
233
- {
234
- $default_key = self::getDefaultKey();
235
- $log_filename = "dup_$default_key.log";
236
- $file_path = DUP_Settings::getSsdirPath()."/".$log_filename;
237
- return $file_path;
238
- }
239
-
240
- /**
241
- * Deletes the trace log and backup trace log files
242
- *
243
- * @return null
244
- */
245
- public static function DeleteTraceLog()
246
- {
247
- $file_path = self::GetTraceFilepath();
248
- $backup_path = self::GetBackupTraceFilepath();
249
- self::trace("deleting $file_path");
250
- @unlink($file_path);
251
- self::trace("deleting $backup_path");
252
- @unlink($backup_path);
253
- }
254
 
255
  /**
256
  * Called when an error is detected and no further processing should occur
@@ -259,84 +276,83 @@ class DUP_Log
259
  * @param int $behavior
260
  * @throws Exception
261
  */
262
- public static function error($msg, $detail = '', $behavior = Dup_ErrorBehavior::Quit)
263
- {
264
-
265
- error_log($msg.' DETAIL:'.$detail);
266
- $source = self::getStack(debug_backtrace());
267
-
268
- $err_msg = "\n==================================================================================\n";
269
- $err_msg .= "DUPLICATOR ERROR\n";
270
- $err_msg .= "Please try again! If the error persists see the Duplicator 'Help' menu.\n";
271
- $err_msg .= "---------------------------------------------------------------------------------\n";
272
- $err_msg .= "MESSAGE:\n\t{$msg}\n";
273
- if (strlen($detail)) {
274
- $err_msg .= "DETAILS:\n\t{$detail}\n";
275
- }
276
- $err_msg .= "TRACE:\n{$source}";
277
- $err_msg .= "==================================================================================\n\n";
278
- @fwrite(self::$logFileHandle, "{$err_msg}");
279
-
280
- switch ($behavior) {
281
-
282
- case Dup_ErrorBehavior::ThrowException:
283
- DUP_LOG::trace("throwing exception");
284
  throw new Exception($msg);
285
- break;
286
-
287
- case Dup_ErrorBehavior::Quit:
288
- DUP_LOG::trace("quitting");
289
- die("DUPLICATOR ERROR: Please see the 'Package Log' file link below.");
290
- break;
291
-
292
- default:
293
- // Nothing
294
- }
295
- }
296
-
297
- /**
298
- * The current stack trace of a PHP call
299
- * @param $stacktrace The current debug stack
300
- * @return string
301
- */
302
- public static function getStack($stacktrace)
303
- {
304
- $output = "";
305
- $i = 1;
306
- foreach ($stacktrace as $node) {
307
- $output .= "\t $i. ".basename($node['file'])." : ".$node['function']." (".$node['line'].")\n";
308
- $i++;
309
- }
310
- return $output;
311
- }
312
-
313
- /**
314
- * Manages writing the active or backup log based on the size setting
315
- *
316
- * @return null
317
- */
318
- private static function WriteToTrace($formatted_logging_message)
319
- {
320
- $log_filepath = self::GetTraceFilepath();
321
-
322
- if (@filesize($log_filepath) > DUPLICATOR_MAX_LOG_SIZE) {
323
- $backup_log_filepath = self::GetBackupTraceFilepath();
324
-
325
- if (file_exists($backup_log_filepath)) {
326
- if (@unlink($backup_log_filepath) === false) {
327
- self::errLog("Couldn't delete backup log $backup_log_filepath");
328
- }
329
- }
330
-
331
- if (@rename($log_filepath, $backup_log_filepath) === false) {
332
- self::errLog("Couldn't rename log $log_filepath to $backup_log_filepath");
333
- }
334
- }
335
-
336
- if (@file_put_contents($log_filepath, $formatted_logging_message, FILE_APPEND) === false) {
337
- // Not en error worth reporting
338
- }
339
- }
340
  }
341
 
342
  class DUP_Handler
@@ -415,18 +431,18 @@ class DUP_Handler
415
  }
416
  break;
417
  case self::MODE_VAR:
418
- self::$varModeLog .= self::getMessage($errno, $errstr, $errfile, $errline)."\n";
419
  break;
420
  case self::MODE_LOG:
421
  default:
422
  switch ($errno) {
423
- case E_ERROR :
424
  $log_message = self::getMessage($errno, $errstr, $errfile, $errline);
425
  DUP_Log::error($log_message);
426
  break;
427
- case E_NOTICE :
428
- case E_WARNING :
429
- default :
430
  $log_message = self::getMessage($errno, $errstr, $errfile, $errline);
431
  DUP_Log::Info($log_message);
432
  break;
@@ -441,16 +457,16 @@ class DUP_Handler
441
  if (self::$errPrefix) {
442
  $result = '[PHP ERR]';
443
  switch ($errno) {
444
- case E_ERROR :
445
  $result .= '[FATAL]';
446
  break;
447
- case E_WARNING :
448
  $result .= '[WARN]';
449
  break;
450
- case E_NOTICE :
451
  $result .= '[NOTICE]';
452
  break;
453
- default :
454
  $result .= '[ISSUE]';
455
  break;
456
  }
@@ -460,8 +476,8 @@ class DUP_Handler
460
  $result .= $errstr;
461
 
462
  if (self::$codeReference) {
463
- $result .= ' [CODE:'.$errno.'|FILE:'.$errfile.'|LINE:'.$errline.']';
464
- $result .= "\n".wp_debug_backtrace_summary();
465
  }
466
 
467
  return $result;
1
  <?php
2
+
3
+ use Duplicator\Libs\Snap\SnapUtil;
4
+
5
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
6
  // Exit if accessed directly
7
+ if (!defined('DUPLICATOR_VERSION')) {
8
+ exit;
9
+ }
10
 
11
  /**
12
  * Helper Class for logging
14
  */
15
  abstract class Dup_ErrorBehavior
16
  {
17
+ const LogOnly = 0;
18
+ const ThrowException = 1;
19
+ const Quit = 2;
20
  }
21
 
22
  class DUP_Log
32
  private static $traceEnabled = false;
33
 
34
  public static $profileLogs = null;
35
+
36
+ /**
37
+ * Init this static object
38
+ */
39
+ public static function Init()
40
+ {
41
+ self::$traceEnabled = (DUP_Settings::Get('trace_log_enabled') == 1);
42
+ }
43
 
44
  /**
45
  * Open a log file connection for writing to the package log file
54
  throw new Exception("A name value is required to open a file log.");
55
  }
56
  self::Close();
57
+ if ((self::$logFileHandle = @fopen(DUP_Settings::getSsdirPath() . "/{$nameHash}.log", "a+")) === false) {
58
  self::$logFileHandle = null;
59
  return false;
60
  } else {
74
  public static function Close()
75
  {
76
  $result = true;
77
+
78
  if (!is_null(self::$logFileHandle)) {
79
  $result = @fclose(self::$logFileHandle);
80
  self::$logFileHandle = null;
86
 
87
  /**
88
  * General information send to the package log if opened
89
+ * @param string $msg The message to log
90
  *
91
  * REPLACE TO DEBUG: Memory consumption as script runs
92
+ * $results = DUP_Util::byteSize(memory_get_peak_usage(true)) . "\t" . $msg;
93
+ * @fwrite(self::$logFileHandle, "{$results} \n");
94
  *
95
+ * @param string $msg The message to log
96
  *
97
  * @return null
98
  */
102
  self::Trace($msg);
103
  }
104
  if (!is_null(self::$logFileHandle)) {
105
+ @fwrite(self::$logFileHandle, $msg . "\n");
106
  }
107
  }
108
 
109
+ /**
110
+ * General information send to the package log and trace log
111
+ *
112
+ * @param string $msg The message to log
113
+ *
114
+ * @return null
115
+ */
116
+ public static function infoTrace($msg, $calling_function_override = null)
117
+ {
118
+ self::Info($msg);
119
+ self::Trace($msg, $calling_function_override);
120
+ }
121
+
122
  public static function print_r_info($val, $name = '')
123
  {
124
+ $msg = empty($name) ? '' : 'VALUE ' . $name . ': ';
125
  $msg .= print_r($val, true);
126
  self::info($msg);
127
  }
128
 
129
+ /**
130
+ * Does the trace file exists
131
+ *
132
+ * @return bool Returns true if an active trace file exists
133
+ */
134
+ public static function TraceFileExists()
135
+ {
136
+ $file_path = self::getTraceFilepath();
137
+ return file_exists($file_path);
138
+ }
139
+
140
+ /**
141
+ * Gets the current file size of the active trace file
142
+ *
143
+ * @return string Returns a human readable file size of the active trace file
144
+ */
145
+ public static function getTraceStatus()
146
+ {
147
+ $file_path = DUP_Log::getTraceFilepath();
148
+ $backup_path = DUP_Log::getBackupTraceFilepath();
 
 
149
 
150
+ if (file_exists($file_path)) {
151
  $filesize = is_file($file_path) ? @filesize($file_path) : 0;
152
 
153
  //Its possible mulitple trace log files exist due to size
154
+ if (is_file($backup_path)) {
155
+ $filesize += @filesize($backup_path);
156
+ }
157
+
158
+ $message = sprintf('%1$s', DUP_Util::byteSize($filesize));
159
+ } else {
160
+ $message = esc_html__('No Log', 'duplicator');
161
+ }
162
+
163
+ return $message;
164
+ }
165
+
166
+ // RSR TODO: Swap trace logic out for real trace later
167
+ public static function Trace($message, $calling_function_override = null)
168
+ {
169
+
170
+ if (self::$traceEnabled) {
171
+ $unique_id = sprintf("%08x", abs(crc32($_SERVER['REMOTE_ADDR'] . $_SERVER['REQUEST_TIME'] . $_SERVER['REMOTE_PORT'])));
172
+
173
+ if ($calling_function_override == null) {
174
+ $calling_function = SnapUtil::getCallingFunctionName();
175
+ } else {
176
+ $calling_function = $calling_function_override;
177
+ }
178
+
179
+ if (is_object($message)) {
180
+ $ov = get_object_vars($message);
181
+ $message = print_r($ov, true);
182
+ } elseif (is_array($message)) {
183
+ $message = print_r($message, true);
184
+ }
185
+
186
+ $logging_message = "{$unique_id}|{$calling_function} | {$message}";
187
+ $ticks = time() + ((int) get_option('gmt_offset') * 3600);
188
+ $formatted_time = date('d-m-H:i:s', $ticks);
189
+ $formatted_logging_message = "{$formatted_time}|DUP|{$logging_message} \r\n";
190
+
191
+ // Always write to error log - if they don't want the info they can turn off WordPress error logging or tracing
192
+ self::ErrLog($logging_message);
193
+
194
+ // Everything goes to the plugin log, whether it's part of package generation or not.
195
+ self::WriteToTrace($formatted_logging_message);
196
+ }
197
+ }
198
 
199
  public static function print_r_trace($val, $name = '', $calling_function_override = null)
200
  {
201
+ $msg = empty($name) ? '' : 'VALUE ' . $name . ': ';
202
  $msg .= print_r($val, true);
203
  self::trace($msg, $calling_function_override);
204
  }
205
 
206
+ public static function errLog($message)
207
+ {
208
+ $message = 'DUP:' . $message;
209
+ error_log($message);
210
+ }
211
+
212
+ public static function TraceObject($msg, $o, $log_private_members = true)
213
+ {
214
+ if (self::$traceEnabled) {
215
+ if (!$log_private_members) {
216
+ $o = get_object_vars($o);
217
+ }
218
+ self::Trace($msg . ':' . print_r($o, true));
219
+ }
220
+ }
221
+
222
+ public static function GetDefaultKey()
223
+ {
224
+ $auth_key = defined('AUTH_KEY') ? AUTH_KEY : 'atk';
225
+ $auth_key .= defined('DB_HOST') ? DB_HOST : 'dbh';
226
+ $auth_key .= defined('DB_NAME') ? DB_NAME : 'dbn';
227
+ $auth_key .= defined('DB_USER') ? DB_USER : 'dbu';
228
+ return hash('md5', $auth_key);
229
+ }
230
+
231
+ /**
232
+ * Gets the current file size of the old trace file "1"
233
+ *
234
+ * @return string Returns a human readable file size of the active trace file
235
+ */
236
+ public static function GetBackupTraceFilepath()
237
+ {
238
+ $default_key = self::getDefaultKey();
239
+ $backup_log_filename = "dup_$default_key.log1";
240
+ $backup_path = DUP_Settings::getSsdirPath() . "/" . $backup_log_filename;
241
+ return $backup_path;
242
+ }
243
 
244
  /**
245
+ * Gets the active trace file path
246
+ *
247
+ * @return string Returns the full path to the active trace file (i.e. dup-pro_hash.log)
248
+ */
249
+ public static function GetTraceFilepath()
250
+ {
251
+ $default_key = self::getDefaultKey();
252
+ $log_filename = "dup_$default_key.log";
253
+ $file_path = DUP_Settings::getSsdirPath() . "/" . $log_filename;
254
+ return $file_path;
255
+ }
256
+
257
+ /**
258
+ * Deletes the trace log and backup trace log files
259
+ *
260
+ * @return null
261
+ */
262
+ public static function DeleteTraceLog()
263
+ {
264
+ $file_path = self::GetTraceFilepath();
265
+ $backup_path = self::GetBackupTraceFilepath();
266
+ self::trace("deleting $file_path");
267
+ @unlink($file_path);
268
+ self::trace("deleting $backup_path");
269
+ @unlink($backup_path);
270
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
  /**
273
  * Called when an error is detected and no further processing should occur
276
  * @param int $behavior
277
  * @throws Exception
278
  */
279
+ public static function error($msg, $detail = '', $behavior = Dup_ErrorBehavior::Quit)
280
+ {
281
+
282
+ error_log($msg . ' DETAIL:' . $detail);
283
+ $source = self::getStack(debug_backtrace());
284
+
285
+ $err_msg = "\n==================================================================================\n";
286
+ $err_msg .= "DUPLICATOR ERROR\n";
287
+ $err_msg .= "Please try again! If the error persists see the Duplicator 'Help' menu.\n";
288
+ $err_msg .= "---------------------------------------------------------------------------------\n";
289
+ $err_msg .= "MESSAGE:\n\t{$msg}\n";
290
+ if (strlen($detail)) {
291
+ $err_msg .= "DETAILS:\n\t{$detail}\n";
292
+ }
293
+ $err_msg .= "TRACE:\n{$source}";
294
+ $err_msg .= "==================================================================================\n\n";
295
+ @fwrite(self::$logFileHandle, "{$err_msg}");
296
+
297
+ switch ($behavior) {
298
+ case Dup_ErrorBehavior::ThrowException:
299
+ DUP_LOG::trace("throwing exception");
 
300
  throw new Exception($msg);
301
+ break;
302
+
303
+ case Dup_ErrorBehavior::Quit:
304
+ DUP_LOG::trace("quitting");
305
+ die("DUPLICATOR ERROR: Please see the 'Package Log' file link below.");
306
+ break;
307
+
308
+ default:
309
+ // Nothing
310
+ }
311
+ }
312
+
313
+ /**
314
+ * The current stack trace of a PHP call
315
+ * @param $stacktrace The current debug stack
316
+ * @return string
317
+ */
318
+ public static function getStack($stacktrace)
319
+ {
320
+ $output = "";
321
+ $i = 1;
322
+ foreach ($stacktrace as $node) {
323
+ $output .= "\t $i. " . basename($node['file']) . " : " . $node['function'] . " (" . $node['line'] . ")\n";
324
+ $i++;
325
+ }
326
+ return $output;
327
+ }
328
+
329
+ /**
330
+ * Manages writing the active or backup log based on the size setting
331
+ *
332
+ * @return null
333
+ */
334
+ private static function WriteToTrace($formatted_logging_message)
335
+ {
336
+ $log_filepath = self::GetTraceFilepath();
337
+
338
+ if (@filesize($log_filepath) > DUPLICATOR_MAX_LOG_SIZE) {
339
+ $backup_log_filepath = self::GetBackupTraceFilepath();
340
+
341
+ if (file_exists($backup_log_filepath)) {
342
+ if (@unlink($backup_log_filepath) === false) {
343
+ self::errLog("Couldn't delete backup log $backup_log_filepath");
344
+ }
345
+ }
346
+
347
+ if (@rename($log_filepath, $backup_log_filepath) === false) {
348
+ self::errLog("Couldn't rename log $log_filepath to $backup_log_filepath");
349
+ }
350
+ }
351
+
352
+ if (@file_put_contents($log_filepath, $formatted_logging_message, FILE_APPEND) === false) {
353
+ // Not en error worth reporting
354
+ }
355
+ }
356
  }
357
 
358
  class DUP_Handler
431
  }
432
  break;
433
  case self::MODE_VAR:
434
+ self::$varModeLog .= self::getMessage($errno, $errstr, $errfile, $errline) . "\n";
435
  break;
436
  case self::MODE_LOG:
437
  default:
438
  switch ($errno) {
439
+ case E_ERROR:
440
  $log_message = self::getMessage($errno, $errstr, $errfile, $errline);
441
  DUP_Log::error($log_message);
442
  break;
443
+ case E_NOTICE:
444
+ case E_WARNING:
445
+ default:
446
  $log_message = self::getMessage($errno, $errstr, $errfile, $errline);
447
  DUP_Log::Info($log_message);
448
  break;
457
  if (self::$errPrefix) {
458
  $result = '[PHP ERR]';
459
  switch ($errno) {
460
+ case E_ERROR:
461
  $result .= '[FATAL]';
462
  break;
463
+ case E_WARNING:
464
  $result .= '[WARN]';
465
  break;
466
+ case E_NOTICE:
467
  $result .= '[NOTICE]';
468
  break;
469
+ default:
470
  $result .= '[ISSUE]';
471
  break;
472
  }
476
  $result .= $errstr;
477
 
478
  if (self::$codeReference) {
479
+ $result .= ' [CODE:' . $errno . '|FILE:' . $errfile . '|LINE:' . $errline . ']';
480
+ $result .= "\n" . wp_debug_backtrace_summary();
481
  }
482
 
483
  return $result;
classes/class.password.php CHANGED
@@ -1,229 +1,235 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- #
4
- # Portable PHP password hashing framework.
5
- #
6
- # Version 0.5 / genuine.
7
- #
8
- # Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
9
- # the public domain. Revised in subsequent years, still public domain.
10
- #
11
- # There's absolutely no warranty.
12
- #
13
- # The homepage URL for this framework is:
14
- #
15
- # http://www.openwall.com/phpass/
16
- #
17
- # Please be sure to update the Version line if you edit this file in any way.
18
- # It is suggested that you leave the main version number intact, but indicate
19
- # your project name (after the slash) and add your own revision information.
20
- #
21
- # Please do not change the "private" password hashing method implemented in
22
- # here, thereby making your hashes incompatible. However, if you must, please
23
- # change the hash type identifier (the "$P$") to something different.
24
- #
25
- # Obviously, since this code is in the public domain, the above are not
26
- # requirements (there can be none), but merely suggestions.
27
- #
28
-
29
- // Exit if accessed directly
30
- if (! defined('DUPLICATOR_VERSION')) exit;
31
-
32
- class DUP_PasswordHash
33
- {
34
-
35
- var $itoa64;
36
- var $iteration_count_log2;
37
- var $portable_hashes;
38
- var $random_state;
39
-
40
- function __construct($iteration_count_log2, $portable_hashes)
41
- {
42
- $this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
43
-
44
- if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
45
- $iteration_count_log2 = 8;
46
- $this->iteration_count_log2 = $iteration_count_log2;
47
-
48
- $this->portable_hashes = $portable_hashes;
49
-
50
- $this->random_state = microtime();
51
- if (function_exists('getmypid'))
52
- $this->random_state .= getmypid();
53
- }
54
-
55
- function PasswordHash($iteration_count_log2, $portable_hashes)
56
- {
57
- self::__construct($iteration_count_log2, $portable_hashes);
58
- }
59
-
60
- function get_random_bytes($count)
61
- {
62
- $output = '';
63
- if (@is_readable('/dev/urandom') &&
64
- ($fh = @fopen('/dev/urandom', 'rb'))) {
65
- $output = fread($fh, $count);
66
- fclose($fh);
67
- }
68
-
69
- if (strlen($output) < $count) {
70
- $output = '';
71
- for ($i = 0; $i < $count; $i += 16) {
72
- $this->random_state =
73
- md5(microtime() . $this->random_state);
74
- $output .= md5($this->random_state, TRUE);
75
- }
76
- $output = substr($output, 0, $count);
77
- }
78
-
79
- return $output;
80
- }
81
-
82
- function encode64($input, $count)
83
- {
84
- $output = '';
85
- $i = 0;
86
- do {
87
- $value = ord($input[$i++]);
88
- $output .= $this->itoa64[$value & 0x3f];
89
- if ($i < $count)
90
- $value |= ord($input[$i]) << 8;
91
- $output .= $this->itoa64[($value >> 6) & 0x3f];
92
- if ($i++ >= $count)
93
- break;
94
- if ($i < $count)
95
- $value |= ord($input[$i]) << 16;
96
- $output .= $this->itoa64[($value >> 12) & 0x3f];
97
- if ($i++ >= $count)
98
- break;
99
- $output .= $this->itoa64[($value >> 18) & 0x3f];
100
- } while ($i < $count);
101
-
102
- return $output;
103
- }
104
-
105
- function gensalt_private($input)
106
- {
107
- $output = '$P$';
108
- $output .= $this->itoa64[min($this->iteration_count_log2 +
109
- ((PHP_VERSION >= '5') ? 5 : 3), 30)];
110
- $output .= $this->encode64($input, 6);
111
-
112
- return $output;
113
- }
114
-
115
- function crypt_private($password, $setting)
116
- {
117
- $output = '*0';
118
- if (substr($setting, 0, 2) === $output)
119
- $output = '*1';
120
-
121
- $id = substr($setting, 0, 3);
122
- # We use "$P$", phpBB3 uses "$H$" for the same thing
123
- if ($id !== '$P$' && $id !== '$H$')
124
- return $output;
125
-
126
- $count_log2 = strpos($this->itoa64, $setting[3]);
127
- if ($count_log2 < 7 || $count_log2 > 30)
128
- return $output;
129
-
130
- $count = 1 << $count_log2;
131
-
132
- $salt = substr($setting, 4, 8);
133
- if (strlen($salt) !== 8)
134
- return $output;
135
-
136
- # We were kind of forced to use MD5 here since it's the only
137
- # cryptographic primitive that was available in all versions
138
- # of PHP in use. To implement our own low-level crypto in PHP
139
- # would have resulted in much worse performance and
140
- # consequently in lower iteration counts and hashes that are
141
- # quicker to crack (by non-PHP code).
142
- $hash = md5($salt . $password, TRUE);
143
- do {
144
- $hash = md5($hash . $password, TRUE);
145
- } while (--$count);
146
-
147
- $output = substr($setting, 0, 12);
148
- $output .= $this->encode64($hash, 16);
149
-
150
- return $output;
151
- }
152
-
153
- function gensalt_blowfish($input)
154
- {
155
- # This one needs to use a different order of characters and a
156
- # different encoding scheme from the one in encode64() above.
157
- # We care because the last character in our encoded string will
158
- # only represent 2 bits. While two known implementations of
159
- # bcrypt will happily accept and correct a salt string which
160
- # has the 4 unused bits set to non-zero, we do not want to take
161
- # chances and we also do not want to waste an additional byte
162
- # of entropy.
163
- $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
164
-
165
- $output = '$2a$';
166
- $output .= chr(ord('0') + (int)($this->iteration_count_log2 / 10));
167
- $output .= chr(ord('0') + $this->iteration_count_log2 % 10);
168
- $output .= '$';
169
-
170
- $i = 0;
171
- do {
172
- $c1 = ord($input[$i++]);
173
- $output .= $itoa64[$c1 >> 2];
174
- $c1 = ($c1 & 0x03) << 4;
175
- if ($i >= 16) {
176
- $output .= $itoa64[$c1];
177
- break;
178
- }
179
-
180
- $c2 = ord($input[$i++]);
181
- $c1 |= $c2 >> 4;
182
- $output .= $itoa64[$c1];
183
- $c1 = ($c2 & 0x0f) << 2;
184
-
185
- $c2 = ord($input[$i++]);
186
- $c1 |= $c2 >> 6;
187
- $output .= $itoa64[$c1];
188
- $output .= $itoa64[$c2 & 0x3f];
189
- } while (1);
190
-
191
- return $output;
192
- }
193
-
194
- function HashPassword($password)
195
- {
196
- $random = '';
197
-
198
- if (CRYPT_BLOWFISH === 1 && !$this->portable_hashes) {
199
- $random = $this->get_random_bytes(16);
200
- $hash =
201
- crypt($password, $this->gensalt_blowfish($random));
202
- if (strlen($hash) === 60)
203
- return $hash;
204
- }
205
-
206
- if (strlen($random) < 6)
207
- $random = $this->get_random_bytes(6);
208
- $hash =
209
- $this->crypt_private($password,
210
- $this->gensalt_private($random));
211
- if (strlen($hash) === 34)
212
- return $hash;
213
-
214
- # Returning '*' on error is safe here, but would _not_ be safe
215
- # in a crypt(3)-like function used _both_ for generating new
216
- # hashes and for validating passwords against existing hashes.
217
- return '*';
218
- }
219
-
220
- function CheckPassword($password, $stored_hash)
221
- {
222
- $hash = $this->crypt_private($password, $stored_hash);
223
- if ($hash[0] === '*')
224
- $hash = crypt($password, $stored_hash);
225
-
226
- return $hash === $stored_hash;
227
- }
228
- }
229
-
 
 
 
 
 
 
1
+ <?php
2
+
3
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
4
+ #
5
+ # Portable PHP password hashing framework.
6
+ #
7
+ # Version 0.5 / genuine.
8
+ #
9
+ # Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
10
+ # the public domain. Revised in subsequent years, still public domain.
11
+ #
12
+ # There's absolutely no warranty.
13
+ #
14
+ # The homepage URL for this framework is:
15
+ #
16
+ # http://www.openwall.com/phpass/
17
+ #
18
+ # Please be sure to update the Version line if you edit this file in any way.
19
+ # It is suggested that you leave the main version number intact, but indicate
20
+ # your project name (after the slash) and add your own revision information.
21
+ #
22
+ # Please do not change the "private" password hashing method implemented in
23
+ # here, thereby making your hashes incompatible. However, if you must, please
24
+ # change the hash type identifier (the "$P$") to something different.
25
+ #
26
+ # Obviously, since this code is in the public domain, the above are not
27
+ # requirements (there can be none), but merely suggestions.
28
+ #
29
+
30
+ // Exit if accessed directly
31
+ if (! defined('DUPLICATOR_VERSION')) {
32
+ exit;
33
+ }
34
+
35
+ class DUP_PasswordHash
36
+ {
37
+ public $itoa64;
38
+ public $iteration_count_log2;
39
+ public $portable_hashes;
40
+ public $random_state;
41
+
42
+ public function __construct($iteration_count_log2, $portable_hashes)
43
+ {
44
+ $this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
45
+ if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) {
46
+ $iteration_count_log2 = 8;
47
+ }
48
+ $this->iteration_count_log2 = $iteration_count_log2;
49
+ $this->portable_hashes = $portable_hashes;
50
+ $this->random_state = microtime();
51
+ if (function_exists('getmypid')) {
52
+ $this->random_state .= getmypid();
53
+ }
54
+ }
55
+
56
+ public function PasswordHash($iteration_count_log2, $portable_hashes)
57
+ {
58
+ self::__construct($iteration_count_log2, $portable_hashes);
59
+ }
60
+
61
+ public function get_random_bytes($count)
62
+ {
63
+ $output = '';
64
+ if (
65
+ @is_readable('/dev/urandom') &&
66
+ ($fh = @fopen('/dev/urandom', 'rb'))
67
+ ) {
68
+ $output = fread($fh, $count);
69
+ fclose($fh);
70
+ }
71
+
72
+ if (strlen($output) < $count) {
73
+ $output = '';
74
+ for ($i = 0; $i < $count; $i += 16) {
75
+ $this->random_state =
76
+ md5(microtime() . $this->random_state);
77
+ $output .= md5($this->random_state, true);
78
+ }
79
+ $output = substr($output, 0, $count);
80
+ }
81
+
82
+ return $output;
83
+ }
84
+
85
+ public function encode64($input, $count)
86
+ {
87
+ $output = '';
88
+ $i = 0;
89
+ do {
90
+ $value = ord($input[$i++]);
91
+ $output .= $this->itoa64[$value & 0x3f];
92
+ if ($i < $count) {
93
+ $value |= ord($input[$i]) << 8;
94
+ }
95
+ $output .= $this->itoa64[($value >> 6) & 0x3f];
96
+ if ($i++ >= $count) {
97
+ break;
98
+ }
99
+ if ($i < $count) {
100
+ $value |= ord($input[$i]) << 16;
101
+ }
102
+ $output .= $this->itoa64[($value >> 12) & 0x3f];
103
+ if ($i++ >= $count) {
104
+ break;
105
+ }
106
+ $output .= $this->itoa64[($value >> 18) & 0x3f];
107
+ } while ($i < $count);
108
+ return $output;
109
+ }
110
+
111
+ public function gensalt_private($input)
112
+ {
113
+ $output = '$P$';
114
+ $output .= $this->itoa64[min($this->iteration_count_log2 +
115
+ ((PHP_VERSION >= '5') ? 5 : 3), 30)];
116
+ $output .= $this->encode64($input, 6);
117
+ return $output;
118
+ }
119
+
120
+ public function crypt_private($password, $setting)
121
+ {
122
+ $output = '*0';
123
+ if (substr($setting, 0, 2) === $output) {
124
+ $output = '*1';
125
+ }
126
+
127
+ $id = substr($setting, 0, 3);
128
+ # We use "$P$", phpBB3 uses "$H$" for the same thing
129
+ if ($id !== '$P$' && $id !== '$H$') {
130
+ return $output;
131
+ }
132
+
133
+ $count_log2 = strpos($this->itoa64, $setting[3]);
134
+ if ($count_log2 < 7 || $count_log2 > 30) {
135
+ return $output;
136
+ }
137
+
138
+ $count = 1 << $count_log2;
139
+ $salt = substr($setting, 4, 8);
140
+ if (strlen($salt) !== 8) {
141
+ return $output;
142
+ }
143
+
144
+ # We were kind of forced to use MD5 here since it's the only
145
+ # cryptographic primitive that was available in all versions
146
+ # of PHP in use. To implement our own low-level crypto in PHP
147
+ # would have resulted in much worse performance and
148
+ # consequently in lower iteration counts and hashes that are
149
+ # quicker to crack (by non-PHP code).
150
+ $hash = md5($salt . $password, true);
151
+ do {
152
+ $hash = md5($hash . $password, true);
153
+ } while (--$count);
154
+ $output = substr($setting, 0, 12);
155
+ $output .= $this->encode64($hash, 16);
156
+ return $output;
157
+ }
158
+
159
+ public function gensalt_blowfish($input)
160
+ {
161
+ # This one needs to use a different order of characters and a
162
+ # different encoding scheme from the one in encode64() above.
163
+ # We care because the last character in our encoded string will
164
+ # only represent 2 bits. While two known implementations of
165
+ # bcrypt will happily accept and correct a salt string which
166
+ # has the 4 unused bits set to non-zero, we do not want to take
167
+ # chances and we also do not want to waste an additional byte
168
+ # of entropy.
169
+ $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
170
+ $output = '$2a$';
171
+ $output .= chr(ord('0') + (int)($this->iteration_count_log2 / 10));
172
+ $output .= chr(ord('0') + $this->iteration_count_log2 % 10);
173
+ $output .= '$';
174
+ $i = 0;
175
+ do {
176
+ $c1 = ord($input[$i++]);
177
+ $output .= $itoa64[$c1 >> 2];
178
+ $c1 = ($c1 & 0x03) << 4;
179
+ if ($i >= 16) {
180
+ $output .= $itoa64[$c1];
181
+ break;
182
+ }
183
+
184
+ $c2 = ord($input[$i++]);
185
+ $c1 |= $c2 >> 4;
186
+ $output .= $itoa64[$c1];
187
+ $c1 = ($c2 & 0x0f) << 2;
188
+ $c2 = ord($input[$i++]);
189
+ $c1 |= $c2 >> 6;
190
+ $output .= $itoa64[$c1];
191
+ $output .= $itoa64[$c2 & 0x3f];
192
+ } while (1);
193
+ return $output;
194
+ }
195
+
196
+ public function HashPassword($password)
197
+ {
198
+ $random = '';
199
+ if (CRYPT_BLOWFISH === 1 && !$this->portable_hashes) {
200
+ $random = $this->get_random_bytes(16);
201
+ $hash =
202
+ crypt($password, $this->gensalt_blowfish($random));
203
+ if (strlen($hash) === 60) {
204
+ return $hash;
205
+ }
206
+ }
207
+
208
+ if (strlen($random) < 6) {
209
+ $random = $this->get_random_bytes(6);
210
+ }
211
+ $hash =
212
+ $this->crypt_private(
213
+ $password,
214
+ $this->gensalt_private($random)
215
+ );
216
+ if (strlen($hash) === 34) {
217
+ return $hash;
218
+ }
219
+
220
+ # Returning '*' on error is safe here, but would _not_ be safe
221
+ # in a crypt(3)-like function used _both_ for generating new
222
+ # hashes and for validating passwords against existing hashes.
223
+ return '*';
224
+ }
225
+
226
+ public function CheckPassword($password, $stored_hash)
227
+ {
228
+ $hash = $this->crypt_private($password, $stored_hash);
229
+ if ($hash[0] === '*') {
230
+ $hash = crypt($password, $stored_hash);
231
+ }
232
+
233
+ return $hash === $stored_hash;
234
+ }
235
+ }
classes/class.plugin.upgrade.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
 
4
  /**
@@ -7,7 +8,6 @@ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
7
  */
8
  class DUP_LITE_Plugin_Upgrade
9
  {
10
-
11
  const DUP_VERSION_OPT_KEY = 'duplicator_version_plugin';
12
 
13
  /**
@@ -24,7 +24,7 @@ class DUP_LITE_Plugin_Upgrade
24
  self::updateInstallation($oldDupVersion);
25
  }
26
 
27
- //Initilize Backup Directories
28
  self::updateDatabase();
29
  DUP_Util::initSnapshotDirectory();
30
  }
@@ -52,15 +52,15 @@ class DUP_LITE_Plugin_Upgrade
52
  //PRE 1.3.35
53
  //Do not update to new wp-content storage till after
54
  if (version_compare($oldVersion, '1.3.35', '<')) {
55
- DUP_Settings::Set('storage_position', DUP_Settings::STORAGE_POSITION_LECAGY);
56
  DUP_Settings::Save();
57
  }
58
-
59
  //WordPress Options Hooks
60
  update_option(self::DUP_VERSION_OPT_KEY, DUPLICATOR_VERSION);
61
  }
62
 
63
- /**
64
  * Runs for both new and update installs and creates the database tables
65
  *
66
  * @return void
@@ -69,7 +69,7 @@ class DUP_LITE_Plugin_Upgrade
69
  {
70
  global $wpdb;
71
 
72
- $table_name = $wpdb->prefix."duplicator_packages";
73
 
74
  //PRIMARY KEY must have 2 spaces before for dbDelta to work
75
  //see: https://codex.wordpress.org/Creating_Tables_with_Plugins
@@ -85,7 +85,7 @@ class DUP_LITE_Plugin_Upgrade
85
  KEY hash (hash))";
86
 
87
  $abs_path = duplicator_get_abs_path();
88
- require_once($abs_path.'/wp-admin/includes/upgrade.php');
89
  @dbDelta($sql);
90
  }
91
- }
1
  <?php
2
+
3
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
4
 
5
  /**
8
  */
9
  class DUP_LITE_Plugin_Upgrade
10
  {
 
11
  const DUP_VERSION_OPT_KEY = 'duplicator_version_plugin';
12
 
13
  /**
24
  self::updateInstallation($oldDupVersion);
25
  }
26
 
27
+ //Init Database & Backup Directories
28
  self::updateDatabase();
29
  DUP_Util::initSnapshotDirectory();
30
  }
52
  //PRE 1.3.35
53
  //Do not update to new wp-content storage till after
54
  if (version_compare($oldVersion, '1.3.35', '<')) {
55
+ DUP_Settings::Set('storage_position', DUP_Settings::STORAGE_POSITION_LEGACY);
56
  DUP_Settings::Save();
57
  }
58
+
59
  //WordPress Options Hooks
60
  update_option(self::DUP_VERSION_OPT_KEY, DUPLICATOR_VERSION);
61
  }
62
 
63
+ /**
64
  * Runs for both new and update installs and creates the database tables
65
  *
66
  * @return void
69
  {
70
  global $wpdb;
71
 
72
+ $table_name = $wpdb->prefix . "duplicator_packages";
73
 
74
  //PRIMARY KEY must have 2 spaces before for dbDelta to work
75
  //see: https://codex.wordpress.org/Creating_Tables_with_Plugins
85
  KEY hash (hash))";
86
 
87
  $abs_path = duplicator_get_abs_path();
88
+ require_once($abs_path . '/wp-admin/includes/upgrade.php');
89
  @dbDelta($sql);
90
  }
91
+ }
classes/class.server.php CHANGED
@@ -1,6 +1,9 @@
1
  <?php
 
 
 
2
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- require_once (DUPLICATOR_PLUGIN_PATH.'classes/utilities/class.u.php');
4
 
5
  /**
6
  * Used to get various pieces of information about the server environment
@@ -16,356 +19,356 @@ require_once (DUPLICATOR_PLUGIN_PATH.'classes/utilities/class.u.php');
16
 
17
  class DUP_Server
18
  {
 
19
 
20
- const LockFileName = 'lockfile.txt';
21
-
22
- // Possibly use in the future if we want to prevent double building
23
- public static function isEngineLocked()
24
- {
25
- if (self::setEngineLock(true)) {
26
- self::setEngineLock(false);
27
- $locked = false;
28
- } else {
29
- $locked = true;
30
- }
31
- }
32
-
33
- // Possibly use in the future if we want to prevent double building
34
- public static function setEngineLock($shouldLock)
35
- {
36
- $success = false;
37
- $locking_file = @fopen(self::LockFileName, 'c+');
38
- if ($locking_file != false) {
39
- if ($shouldLock) {
40
- $success = @flock($locking_file, LOCK_EX | LOCK_NB);
41
- } else {
42
- $success = @flock($locking_file, LOCK_UN);
43
- }
44
-
45
- @fclose($locking_file);
46
- }
47
- return $success;
48
- }
49
 
50
  public static function mysqlEscapeIsOk()
51
  {
52
- $escape_test_string = chr(0).chr(26)."\r\n'\"\\";
53
- $escape_expected_result = "\"\\0\Z\\r\\n\\'\\\"\\\\\"";
54
- $escape_actual_result = DUP_DB::escValueToQueryString($escape_test_string);
55
- $result = $escape_expected_result === $escape_actual_result;
56
 
57
  if (!$result) {
58
- $msg = "mysqli_real_escape_string test results\n".
59
- "Expected escape result: ".$escape_expected_result."\n".
60
- "Actual escape result: ".$escape_actual_result;
61
  DUP_Log::trace($msg);
62
-
63
  }
64
 
65
  return $result;
66
  }
67
 
68
- /**
69
- * Gets the system requirements which must pass to build a package
70
- *
71
- * @return array An array of requirements
72
- */
73
- public static function getRequirements()
74
- {
75
- $dup_tests = array();
76
 
77
- //PHP SUPPORT
78
- $safe_ini = strtolower(ini_get('safe_mode'));
79
- $dup_tests['PHP']['SAFE_MODE'] = $safe_ini != 'on' || $safe_ini != 'yes' || $safe_ini != 'true' || ini_get("safe_mode") != 1 ? 'Pass' : 'Fail';
80
- self::logRequirementFail($dup_tests['PHP']['SAFE_MODE'], 'SAFE_MODE is on.');
81
 
82
- $dup_tests['PHP']['VERSION'] = DUP_Util::$on_php_529_plus ? 'Pass' : 'Fail';
83
- $phpversion = phpversion();
84
- self::logRequirementFail($dup_tests['PHP']['VERSION'], 'PHP version('.$phpversion.') is lower than 5.2.9');
85
 
86
- if (DUP_Settings::Get('archive_build_mode') == DUP_Archive_Build_Mode::ZipArchive) {
87
- $dup_tests['PHP']['ZIP'] = class_exists('ZipArchive') ? 'Pass' : 'Fail';
88
- self::logRequirementFail($dup_tests['PHP']['ZIP'], 'ZipArchive class doesn\'t exist.');
89
- }
90
 
91
- $dup_tests['PHP']['FUNC_1'] = function_exists("file_get_contents") ? 'Pass' : 'Fail';
92
- self::logRequirementFail($dup_tests['PHP']['FUNC_1'], 'file_get_contents function doesn\'t exist.');
93
 
94
- $dup_tests['PHP']['FUNC_2'] = function_exists("file_put_contents") ? 'Pass' : 'Fail';
95
- self::logRequirementFail($dup_tests['PHP']['FUNC_2'], 'file_put_contents function doesn\'t exist.');
96
 
97
- $dup_tests['PHP']['FUNC_3'] = function_exists("mb_strlen") ? 'Pass' : 'Fail';
98
- self::logRequirementFail($dup_tests['PHP']['FUNC_3'], 'mb_strlen function doesn\'t exist.');
99
 
100
- $dup_tests['PHP']['ALL'] = !in_array('Fail', $dup_tests['PHP']) ? 'Pass' : 'Fail';
101
 
102
- //REQUIRED PATHS
103
- $abs_path = duplicator_get_abs_path();
104
- $handle_test = @opendir($abs_path);
105
- $dup_tests['IO']['WPROOT'] = is_writeable($abs_path) && $handle_test ? 'Pass' : 'Warn';
106
- @closedir($handle_test);
107
- self::logRequirementFail($dup_tests['IO']['WPROOT'], $abs_path.' (abs path) can\'t be opened.');
108
 
109
- $dup_tests['IO']['SSDIR'] = is_writeable(DUP_Settings::getSsdirPath()) ? 'Pass' : 'Fail';
110
- self::logRequirementFail($dup_tests['IO']['SSDIR'], DUP_Settings::getSsdirPath().' (DUPLICATOR_SSDIR_PATH) can\'t be writeable.');
111
 
112
- $dup_tests['IO']['SSTMP'] = is_writeable(DUP_Settings::getSsdirTmpPath()) ? 'Pass' : 'Fail';
113
- self::logRequirementFail($dup_tests['IO']['SSTMP'], DUP_Settings::getSsdirTmpPath().' (DUPLICATOR_SSDIR_PATH_TMP) can\'t be writeable.');
114
 
115
- $dup_tests['IO']['ALL'] = !in_array('Fail', $dup_tests['IO']) ? 'Pass' : 'Fail';
116
 
117
- //SERVER SUPPORT
118
- $dup_tests['SRV']['MYSQLi'] = function_exists('mysqli_connect') ? 'Pass' : 'Fail';
119
- self::logRequirementFail($dup_tests['SRV']['MYSQLi'], 'mysqli_connect function doesn\'t exist.');
120
 
121
  //mysqli_real_escape_string test
122
  $dup_tests['SRV']['MYSQL_ESC'] = self::mysqlEscapeIsOk() ? 'Pass' : 'Fail';
123
  self::logRequirementFail($dup_tests['SRV']['MYSQL_ESC'], "The function mysqli_real_escape_string is not escaping strings as expected.");
124
 
125
- $db_version = DUP_DB::getVersion();
126
- $dup_tests['SRV']['MYSQL_VER'] = version_compare($db_version, '5.0', '>=') ? 'Pass' : 'Fail';
127
- self::logRequirementFail($dup_tests['SRV']['MYSQL_VER'], 'MySQL version '.$db_version.' is lower than 5.0.');
128
-
129
- $dup_tests['SRV']['ALL'] = !in_array('Fail', $dup_tests['SRV']) ? 'Pass' : 'Fail';
130
-
131
- //RESERVED FILES
132
- $dup_tests['RES']['INSTALL'] = !(self::hasInstallerFiles()) ? 'Pass' : 'Fail';
133
- self::logRequirementFail($dup_tests['RES']['INSTALL'], 'Installer file(s) are exist on the server.');
134
- $dup_tests['Success'] = $dup_tests['PHP']['ALL'] == 'Pass' && $dup_tests['IO']['ALL'] == 'Pass' && $dup_tests['SRV']['ALL'] == 'Pass' && $dup_tests['RES']['INSTALL'] == 'Pass';
135
-
136
- $dup_tests['Warning'] = $dup_tests['IO']['WPROOT'] == 'Warn';
137
-
138
- return $dup_tests;
139
- }
140
-
141
- /**
142
- * Logs requirement fail status informative message
143
- *
144
- * @param string $testStatus Either it is Pass or Fail
145
- * @param string $errorMessage Error message which should be logged
146
- * @return void
147
- */
148
- private static function logRequirementFail($testStatus, $errorMessage)
149
- {
150
- if (empty($testStatus)) {
151
- throw new Exception('Exception: Empty $testStatus [File: '.__FILE__.', Ln: '.__LINE__);
152
- }
153
-
154
- if (empty($errorMessage)) {
155
- throw new Exception('Exception: Empty $errorMessage [File: '.__FILE__.', Ln: '.__LINE__);
156
- }
157
-
158
- $validTestStatuses = array('Pass', 'Fail', 'Warn');
159
-
160
- if (!in_array($testStatus, $validTestStatuses)) {
161
- throw new Exception('Exception: Invalid $testStatus value: '.$testStatus.' [File: '.__FILE__.', Ln: '.__LINE__);
162
- }
163
-
164
- if ('Fail' == $testStatus) {
165
- DUP_LOG::trace($errorMessage);
166
- }
167
- }
168
-
169
- /**
170
- * Gets the system checks which are not required
171
- *
172
- * @return array An array of system checks
173
- */
174
- public static function getChecks()
175
- {
176
- $checks = array();
177
-
178
- //PHP/SYSTEM SETTINGS
179
- //Web Server
180
- $php_test0 = false;
181
- foreach ($GLOBALS['DUPLICATOR_SERVER_LIST'] as $value) {
182
- if (stristr($_SERVER['SERVER_SOFTWARE'], $value)) {
183
- $php_test0 = true;
184
- break;
185
- }
186
- }
187
- self::logCheckFalse($php_test0, 'Any out of server software ('.implode(', ', $GLOBALS['DUPLICATOR_SERVER_LIST']).') doesn\'t exist.');
188
-
189
- $php_test1 = ini_get("open_basedir");
190
- $php_test1 = empty($php_test1) ? true : false;
191
- self::logCheckFalse($php_test1, 'open_basedir is enabled.');
192
-
193
- $max_execution_time = ini_get("max_execution_time");
194
- $php_test2 = ($max_execution_time > DUPLICATOR_SCAN_TIMEOUT) || (strcmp($max_execution_time, 'Off') == 0 || $max_execution_time == 0) ? true : false;
195
- if (strcmp($max_execution_time, 'Off') == 0) {
196
- $max_execution_time_error_message = '$max_execution_time should not be'.$max_execution_time;
197
- } else {
198
- $max_execution_time_error_message = '$max_execution_time ('.$max_execution_time.') should not be lower than the DUPLICATOR_SCAN_TIMEOUT'.DUPLICATOR_SCAN_TIMEOUT;
199
- }
200
- self::logCheckFalse($php_test2, $max_execution_time_error_message);
201
-
202
- $php_test3 = function_exists('mysqli_connect');
203
- self::logCheckFalse($php_test3, 'mysqli_connect function doesn\'t exist.');
204
-
205
- $php_test4 = DUP_Util::$on_php_53_plus ? true : false;
206
- self::logCheckFalse($php_test4, 'PHP Version is lower than 5.3.');
207
-
208
- $checks['SRV']['PHP']['websrv'] = $php_test0;
209
- $checks['SRV']['PHP']['openbase'] = $php_test1;
210
- $checks['SRV']['PHP']['maxtime'] = $php_test2;
211
- $checks['SRV']['PHP']['mysqli'] = $php_test3;
212
- $checks['SRV']['PHP']['version'] = $php_test4;
213
  //MANAGED HOST
214
  $checks['SRV']['SYS']['managedHost'] = !DUP_Custom_Host_Manager::getInstance()->isManaged();
215
- $checks['SRV']['SYS']['ALL'] = ($php_test0 && $php_test1 && $php_test2 && $php_test3 && $php_test4 && $checks['SRV']['SYS']['managedHost']) ? 'Good' : 'Warn';
216
-
217
- //WORDPRESS SETTINGS
218
- global $wp_version;
219
- $wp_test1 = version_compare($wp_version, DUPLICATOR_SCAN_MIN_WP) >= 0 ? true : false;
220
- self::logCheckFalse($wp_test1, 'WP version ('.$wp_version.') is lower than the DUPLICATOR_SCAN_MIN_WP ('.DUPLICATOR_SCAN_MIN_WP.').');
221
-
222
- //Core Files
223
- $files = array();
224
- $proper_wp_config_file_path = duplicator_get_abs_path().'/wp-config.php';
225
- $files['wp-config.php'] = file_exists($proper_wp_config_file_path);
226
- self::logCheckFalse($files['wp-config.php'], 'The wp-config.php file doesn\'t exist on the '.$proper_wp_config_file_path);
227
-
228
- /** searching wp-config in working word press is not worthy
229
- * if this script is executing that means wp-config.php exists :)
230
- * we need to know the core folders and files added by the user at this point
231
- * retaining old logic as else for the case if its used some where else
232
- */
233
- //Core dir and files logic
234
- if (isset($_POST['file_notice']) && isset($_POST['dir_notice'])) {
235
- //means if there are core directories excluded or core files excluded return false
236
- if ((bool) $_POST['file_notice'] || (bool) $_POST['dir_notice'])
237
- $wp_test2 = false;
238
- else
239
- $wp_test2 = true;
240
- } else {
241
- $wp_test2 = $files['wp-config.php'];
242
- }
243
-
244
- //Cache
245
- /*
246
- $Package = DUP_Package::getActive();
247
- $cache_path = DUP_Util::safePath(WP_CONTENT_DIR) . '/cache';
248
- $dirEmpty = DUP_Util::isDirectoryEmpty($cache_path);
249
- $dirSize = DUP_Util::getDirectorySize($cache_path);
250
- $cach_filtered = in_array($cache_path, explode(';', $Package->Archive->FilterDirs));
251
- $wp_test3 = ($cach_filtered || $dirEmpty || $dirSize < DUPLICATOR_SCAN_CACHESIZE ) ? true : false;
252
- */
253
- $wp_test3 = is_multisite();
254
- self::logCheckFalse($wp_test3, 'WP is multi-site setup.');
255
-
256
- $checks['SRV']['WP']['version'] = $wp_test1;
257
- $checks['SRV']['WP']['core'] = $wp_test2;
258
- $checks['SRV']['WP']['ismu'] = $wp_test3;
259
- $checks['SRV']['WP']['ALL'] = $wp_test1 && $wp_test2 && !$wp_test3 ? 'Good' : 'Warn';
260
-
261
- return $checks;
262
- }
263
-
264
- /**
265
- * Logs checks false informative message
266
- *
267
- * @param boolean $check Either it is true or false
268
- * @param string $errorMessage Error message which should be logged when check is false
269
- * @return void
270
- */
271
- private static function logCheckFalse($check, $errorMessage)
272
- {
273
- if (empty($errorMessage)) {
274
- throw new Exception('Exception: Empty $errorMessage variable [File: '.__FILE__.', Ln: '.__LINE__);
275
- }
276
-
277
- if (filter_var($check, FILTER_VALIDATE_BOOLEAN) === false) {
278
- DUP_LOG::trace($errorMessage);
279
- }
280
- }
281
-
282
- /**
283
- * Check to see if duplicator installer files are present
284
- *
285
- * @return bool True if any reserved files are found
286
- */
287
- public static function hasInstallerFiles()
288
- {
289
- $files = self::getInstallerFiles();
290
- foreach ($files as $file => $path) {
291
- if (false !== strpos($path, '*')) {
292
- $glob_files = glob($path);
293
- if (!empty($glob_files)) {
294
- return true;
295
- }
296
- } elseif (file_exists($path))
297
- return true;
298
- }
299
- return false;
300
- }
301
-
302
- /**
303
- * Gets a list of all the installer files by name and full path
304
- *
305
- * @remarks
306
- * FILES: installer.php, installer-backup.php, dup-installer-bootlog__[HASH].txt
307
- * DIRS: dup-installer
308
- * DEV FILES: wp-config.orig
309
- * Last set is for lazy developer cleanup files that a developer may have
310
- * accidently left around lets be proactive for the user just in case.
311
- *
312
- * @return array [file_name, file_path]
313
- */
314
- public static function getInstallerFiles()
315
- {
316
- // alphanumeric 7 time, then -(dash), then 8 digits
317
- $abs_path = duplicator_get_abs_path();
318
- $four_digit_glob_pattern = '[0-9][0-9][0-9][0-9]';
319
- $retArr = array(
320
- basename(DUPLICATOR_INSTALLER_DIRECTORY).' '.esc_html__('(directory)', 'duplicator') => DUPLICATOR_INSTALLER_DIRECTORY,
321
- DUPLICATOR_INSTALL_PHP => $abs_path.'/'.DUPLICATOR_INSTALL_PHP,
322
- '[HASH]'.'_'.DUPLICATOR_INSTALL_PHP => $abs_path.'/*_*'.$four_digit_glob_pattern.'_'.DUPLICATOR_INSTALL_PHP,
323
- DUPLICATOR_INSTALL_BAK => $abs_path.'/'.DUPLICATOR_INSTALL_BAK,
324
- '[HASH]'.'_'.DUPLICATOR_INSTALL_BAK => $abs_path.'/*_*'.$four_digit_glob_pattern.'_'.DUPLICATOR_INSTALL_BAK,
325
- '[HASH]_archive.zip|daf' => $abs_path.'/*_*'.$four_digit_glob_pattern.'_archive.[zd][ia][pf]',
326
- 'dup-installer-bootlog__[HASH].txt' => $abs_path.'/dup-installer-bootlog__'.DUPLICATOR_INSTALLER_HASH_PATTERN.'.txt',
327
- );
328
- if (DUPLICATOR_INSTALL_SITE_OVERWRITE_ON) {
329
- $retArr['dup-wp-config-arc__[HASH].txt'] = $abs_path.'/dup-wp-config-arc__'.DUPLICATOR_INSTALLER_HASH_PATTERN.'.txt';
330
- }
331
- return $retArr;
332
- }
333
-
334
- /**
335
- * Get the IP of a client machine
336
- *
337
- * @return string IP of the client machine
338
- */
339
- public static function getClientIP()
340
- {
341
- if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) {
342
- return $_SERVER["HTTP_X_FORWARDED_FOR"];
343
- } else if (array_key_exists('REMOTE_ADDR', $_SERVER)) {
344
- return $_SERVER["REMOTE_ADDR"];
345
- } else if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) {
346
- return $_SERVER["HTTP_CLIENT_IP"];
347
- }
348
- return '';
349
- }
350
-
351
- /**
352
- * Get PHP memory usage
353
- *
354
- * @return string Returns human readable memory usage.
355
- */
356
- public static function getPHPMemory($peak = false)
357
- {
358
- if ($peak) {
359
- $result = 'Unable to read PHP peak memory usage';
360
- if (function_exists('memory_get_peak_usage')) {
361
- $result = DUP_Util::byteSize(memory_get_peak_usage(true));
362
- }
363
- } else {
364
- $result = 'Unable to read PHP memory usage';
365
- if (function_exists('memory_get_usage')) {
366
- $result = DUP_Util::byteSize(memory_get_usage(true));
367
- }
368
- }
369
- return $result;
370
- }
371
- }
 
 
1
  <?php
2
+
3
+ use Duplicator\Libs\Snap\SnapWP;
4
+
5
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
6
+ require_once(DUPLICATOR_PLUGIN_PATH . 'classes/utilities/class.u.php');
7
 
8
  /**
9
  * Used to get various pieces of information about the server environment
19
 
20
  class DUP_Server
21
  {
22
+ const LockFileName = 'lockfile.txt';
23
 
24
+ // Possibly use in the future if we want to prevent double building
25
+ public static function isEngineLocked()
26
+ {
27
+ if (self::setEngineLock(true)) {
28
+ self::setEngineLock(false);
29
+ $locked = false;
30
+ } else {
31
+ $locked = true;
32
+ }
33
+ }
34
+
35
+ // Possibly use in the future if we want to prevent double building
36
+ public static function setEngineLock($shouldLock)
37
+ {
38
+ $success = false;
39
+ $locking_file = @fopen(self::LockFileName, 'c+');
40
+ if ($locking_file != false) {
41
+ if ($shouldLock) {
42
+ $success = @flock($locking_file, LOCK_EX | LOCK_NB);
43
+ } else {
44
+ $success = @flock($locking_file, LOCK_UN);
45
+ }
46
+
47
+ @fclose($locking_file);
48
+ }
49
+ return $success;
50
+ }
 
 
51
 
52
  public static function mysqlEscapeIsOk()
53
  {
54
+ $escape_test_string = chr(0) . chr(26) . "\r\n'\"\\";
55
+ $escape_expected_result = "\"\\0\Z\\r\\n\\'\\\"\\\\\"";
56
+ $escape_actual_result = DUP_DB::escValueToQueryString($escape_test_string);
57
+ $result = $escape_expected_result === $escape_actual_result;
58
 
59
  if (!$result) {
60
+ $msg = "mysqli_real_escape_string test results\n" .
61
+ "Expected escape result: " . $escape_expected_result . "\n" .
62
+ "Actual escape result: " . $escape_actual_result;
63
  DUP_Log::trace($msg);
 
64
  }
65
 
66
  return $result;
67
  }
68
 
69
+ /**
70
+ * Gets the system requirements which must pass to build a package
71
+ *
72
+ * @return array An array of requirements
73
+ */
74
+ public static function getRequirements()
75
+ {
76
+ $dup_tests = array();
77
 
78
+ //PHP SUPPORT
79
+ $safe_ini = strtolower(ini_get('safe_mode'));
80
+ $dup_tests['PHP']['SAFE_MODE'] = $safe_ini != 'on' || $safe_ini != 'yes' || $safe_ini != 'true' || ini_get("safe_mode") != 1 ? 'Pass' : 'Fail';
81
+ self::logRequirementFail($dup_tests['PHP']['SAFE_MODE'], 'SAFE_MODE is on.');
82
 
83
+ $dup_tests['PHP']['VERSION'] = DUP_Util::$on_php_529_plus ? 'Pass' : 'Fail';
84
+ $phpversion = phpversion();
85
+ self::logRequirementFail($dup_tests['PHP']['VERSION'], 'PHP version(' . $phpversion . ') is lower than 5.2.9');
86
 
87
+ if (DUP_Settings::Get('archive_build_mode') == DUP_Archive_Build_Mode::ZipArchive) {
88
+ $dup_tests['PHP']['ZIP'] = class_exists('ZipArchive') ? 'Pass' : 'Fail';
89
+ self::logRequirementFail($dup_tests['PHP']['ZIP'], 'ZipArchive class doesn\'t exist.');
90
+ }
91
 
92
+ $dup_tests['PHP']['FUNC_1'] = function_exists("file_get_contents") ? 'Pass' : 'Fail';
93
+ self::logRequirementFail($dup_tests['PHP']['FUNC_1'], 'file_get_contents function doesn\'t exist.');
94
 
95
+ $dup_tests['PHP']['FUNC_2'] = function_exists("file_put_contents") ? 'Pass' : 'Fail';
96
+ self::logRequirementFail($dup_tests['PHP']['FUNC_2'], 'file_put_contents function doesn\'t exist.');
97
 
98
+ $dup_tests['PHP']['FUNC_3'] = function_exists("mb_strlen") ? 'Pass' : 'Fail';
99
+ self::logRequirementFail($dup_tests['PHP']['FUNC_3'], 'mb_strlen function doesn\'t exist.');
100
 
101
+ $dup_tests['PHP']['ALL'] = !in_array('Fail', $dup_tests['PHP']) ? 'Pass' : 'Fail';
102
 
103
+ //REQUIRED PATHS
104
+ $abs_path = duplicator_get_abs_path();
105
+ $handle_test = @opendir($abs_path);
106
+ $dup_tests['IO']['WPROOT'] = is_writeable($abs_path) && $handle_test ? 'Pass' : 'Warn';
107
+ @closedir($handle_test);
108
+ self::logRequirementFail($dup_tests['IO']['WPROOT'], $abs_path . ' (abs path) can\'t be opened.');
109
 
110
+ $dup_tests['IO']['SSDIR'] = is_writeable(DUP_Settings::getSsdirPath()) ? 'Pass' : 'Fail';
111
+ self::logRequirementFail($dup_tests['IO']['SSDIR'], DUP_Settings::getSsdirPath() . ' (DUPLICATOR_SSDIR_PATH) can\'t be writeable.');
112
 
113
+ $dup_tests['IO']['SSTMP'] = is_writeable(DUP_Settings::getSsdirTmpPath()) ? 'Pass' : 'Fail';
114
+ self::logRequirementFail($dup_tests['IO']['SSTMP'], DUP_Settings::getSsdirTmpPath() . ' (DUPLICATOR_SSDIR_PATH_TMP) can\'t be writeable.');
115
 
116
+ $dup_tests['IO']['ALL'] = !in_array('Fail', $dup_tests['IO']) ? 'Pass' : 'Fail';
117
 
118
+ //SERVER SUPPORT
119
+ $dup_tests['SRV']['MYSQLi'] = function_exists('mysqli_connect') ? 'Pass' : 'Fail';
120
+ self::logRequirementFail($dup_tests['SRV']['MYSQLi'], 'mysqli_connect function doesn\'t exist.');
121
 
122
  //mysqli_real_escape_string test
123
  $dup_tests['SRV']['MYSQL_ESC'] = self::mysqlEscapeIsOk() ? 'Pass' : 'Fail';
124
  self::logRequirementFail($dup_tests['SRV']['MYSQL_ESC'], "The function mysqli_real_escape_string is not escaping strings as expected.");
125
 
126
+ $db_version = DUP_DB::getVersion();
127
+ $dup_tests['SRV']['MYSQL_VER'] = version_compare($db_version, '5.0', '>=') ? 'Pass' : 'Fail';
128
+ self::logRequirementFail($dup_tests['SRV']['MYSQL_VER'], 'MySQL version ' . $db_version . ' is lower than 5.0.');
129
+
130
+ $dup_tests['SRV']['ALL'] = !in_array('Fail', $dup_tests['SRV']) ? 'Pass' : 'Fail';
131
+
132
+ //RESERVED FILES
133
+ $dup_tests['RES']['INSTALL'] = !(self::hasInstallerFiles()) ? 'Pass' : 'Fail';
134
+ self::logRequirementFail($dup_tests['RES']['INSTALL'], 'Installer file(s) are exist on the server.');
135
+ $dup_tests['Success'] = $dup_tests['PHP']['ALL'] == 'Pass' && $dup_tests['IO']['ALL'] == 'Pass' && $dup_tests['SRV']['ALL'] == 'Pass' && $dup_tests['RES']['INSTALL'] == 'Pass';
136
+
137
+ $dup_tests['Warning'] = $dup_tests['IO']['WPROOT'] == 'Warn';
138
+
139
+ return $dup_tests;
140
+ }
141
+
142
+ /**
143
+ * Logs requirement fail status informative message
144
+ *
145
+ * @param string $testStatus Either it is Pass or Fail
146
+ * @param string $errorMessage Error message which should be logged
147
+ * @return void
148
+ */
149
+ private static function logRequirementFail($testStatus, $errorMessage)
150
+ {
151
+ if (empty($testStatus)) {
152
+ throw new Exception('Exception: Empty $testStatus [File: ' . __FILE__ . ', Ln: ' . __LINE__);
153
+ }
154
+
155
+ if (empty($errorMessage)) {
156
+ throw new Exception('Exception: Empty $errorMessage [File: ' . __FILE__ . ', Ln: ' . __LINE__);
157
+ }
158
+
159
+ $validTestStatuses = array('Pass', 'Fail', 'Warn');
160
+
161
+ if (!in_array($testStatus, $validTestStatuses)) {
162
+ throw new Exception('Exception: Invalid $testStatus value: ' . $testStatus . ' [File: ' . __FILE__ . ', Ln: ' . __LINE__);
163
+ }
164
+
165
+ if ('Fail' == $testStatus) {
166
+ DUP_LOG::trace($errorMessage);
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Gets the system checks which are not required
172
+ *
173
+ * @return array An array of system checks
174
+ */
175
+ public static function getChecks()
176
+ {
177
+ $checks = array();
178
+
179
+ //PHP/SYSTEM SETTINGS
180
+ //Web Server
181
+ $php_test0 = false;
182
+ foreach ($GLOBALS['DUPLICATOR_SERVER_LIST'] as $value) {
183
+ if (stristr($_SERVER['SERVER_SOFTWARE'], $value)) {
184
+ $php_test0 = true;
185
+ break;
186
+ }
187
+ }
188
+ self::logCheckFalse($php_test0, 'Any out of server software (' . implode(', ', $GLOBALS['DUPLICATOR_SERVER_LIST']) . ') doesn\'t exist.');
189
+
190
+ $php_test1 = ini_get("open_basedir");
191
+ $php_test1 = empty($php_test1) ? true : false;
192
+ self::logCheckFalse($php_test1, 'open_basedir is enabled.');
193
+
194
+ $max_execution_time = ini_get("max_execution_time");
195
+ $php_test2 = ($max_execution_time > DUPLICATOR_SCAN_TIMEOUT) || (strcmp($max_execution_time, 'Off') == 0 || $max_execution_time == 0) ? true : false;
196
+ if (strcmp($max_execution_time, 'Off') == 0) {
197
+ $max_execution_time_error_message = '$max_execution_time should not be' . $max_execution_time;
198
+ } else {
199
+ $max_execution_time_error_message = '$max_execution_time (' . $max_execution_time . ') should not be lower than the DUPLICATOR_SCAN_TIMEOUT' . DUPLICATOR_SCAN_TIMEOUT;
200
+ }
201
+ self::logCheckFalse($php_test2, $max_execution_time_error_message);
202
+
203
+ $php_test3 = function_exists('mysqli_connect');
204
+ self::logCheckFalse($php_test3, 'mysqli_connect function doesn\'t exist.');
205
+
206
+ $php_test4 = DUP_Util::$on_php_53_plus ? true : false;
207
+ self::logCheckFalse($php_test4, 'PHP Version is lower than 5.3.');
208
+
209
+ $checks['SRV']['PHP']['websrv'] = $php_test0;
210
+ $checks['SRV']['PHP']['openbase'] = $php_test1;
211
+ $checks['SRV']['PHP']['maxtime'] = $php_test2;
212
+ $checks['SRV']['PHP']['mysqli'] = $php_test3;
213
+ $checks['SRV']['PHP']['version'] = $php_test4;
214
  //MANAGED HOST
215
  $checks['SRV']['SYS']['managedHost'] = !DUP_Custom_Host_Manager::getInstance()->isManaged();
216
+ $checks['SRV']['SYS']['ALL'] = ($php_test0 && $php_test1 && $php_test2 && $php_test3 && $php_test4 && $checks['SRV']['SYS']['managedHost']) ? 'Good' : 'Warn';
217
+
218
+ //WORDPRESS SETTINGS
219
+ global $wp_version;
220
+ $wp_test1 = version_compare($wp_version, DUPLICATOR_SCAN_MIN_WP) >= 0 ? true : false;
221
+ self::logCheckFalse($wp_test1, 'WP version (' . $wp_version . ') is lower than the DUPLICATOR_SCAN_MIN_WP (' . DUPLICATOR_SCAN_MIN_WP . ').');
222
+
223
+ //Core Files
224
+ $files = array();
225
+ $proper_wp_config_file_path = SnapWP::getWPConfigPath();
226
+ $files['wp-config.php'] = file_exists($proper_wp_config_file_path);
227
+ self::logCheckFalse($files['wp-config.php'], 'The wp-config.php file doesn\'t exist on the ' . $proper_wp_config_file_path);
228
+
229
+ /** searching wp-config in working word press is not worthy
230
+ * if this script is executing that means wp-config.php exists :)
231
+ * we need to know the core folders and files added by the user at this point
232
+ * retaining old logic as else for the case if its used some where else
233
+ */
234
+ //Core dir and files logic
235
+ if (isset($_POST['file_notice']) && isset($_POST['dir_notice'])) {
236
+ //means if there are core directories excluded or core files excluded return false
237
+ if ((bool) $_POST['file_notice'] || (bool) $_POST['dir_notice']) {
238
+ $wp_test2 = false;
239
+ } else {
240
+ $wp_test2 = true;
241
+ }
242
+ } else {
243
+ $wp_test2 = $files['wp-config.php'];
244
+ }
245
+
246
+ //Cache
247
+ /*
248
+ $Package = DUP_Package::getActive();
249
+ $cache_path = DUP_Util::safePath(WP_CONTENT_DIR) . '/cache';
250
+ $dirEmpty = DUP_Util::isDirectoryEmpty($cache_path);
251
+ $dirSize = DUP_Util::getDirectorySize($cache_path);
252
+ $cach_filtered = in_array($cache_path, explode(';', $Package->Archive->FilterDirs));
253
+ $wp_test3 = ($cach_filtered || $dirEmpty || $dirSize < DUPLICATOR_SCAN_CACHESIZE ) ? true : false;
254
+ */
255
+ $wp_test3 = is_multisite();
256
+ self::logCheckFalse($wp_test3, 'WP is multi-site setup.');
257
+
258
+ $checks['SRV']['WP']['version'] = $wp_test1;
259
+ $checks['SRV']['WP']['core'] = $wp_test2;
260
+ $checks['SRV']['WP']['ismu'] = $wp_test3;
261
+ $checks['SRV']['WP']['ALL'] = $wp_test1 && $wp_test2 && !$wp_test3 ? 'Good' : 'Warn';
262
+
263
+ return $checks;
264
+ }
265
+
266
+ /**
267
+ * Logs checks false informative message
268
+ *
269
+ * @param boolean $check Either it is true or false
270
+ * @param string $errorMessage Error message which should be logged when check is false
271
+ * @return void
272
+ */
273
+ private static function logCheckFalse($check, $errorMessage)
274
+ {
275
+ if (empty($errorMessage)) {
276
+ throw new Exception('Exception: Empty $errorMessage variable [File: ' . __FILE__ . ', Ln: ' . __LINE__);
277
+ }
278
+
279
+ if (filter_var($check, FILTER_VALIDATE_BOOLEAN) === false) {
280
+ DUP_LOG::trace($errorMessage);
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Check to see if duplicator installer files are present
286
+ *
287
+ * @return bool True if any reserved files are found
288
+ */
289
+ public static function hasInstallerFiles()
290
+ {
291
+ $files = self::getInstallerFiles();
292
+ foreach ($files as $file => $path) {
293
+ if (false !== strpos($path, '*')) {
294
+ $glob_files = glob($path);
295
+ if (!empty($glob_files)) {
296
+ return true;
297
+ }
298
+ } elseif (file_exists($path)) {
299
+ return true;
300
+ }
301
+ }
302
+ return false;
303
+ }
304
+
305
+ /**
306
+ * Gets a list of all the installer files by name and full path
307
+ *
308
+ * @remarks
309
+ * FILES: installer.php, installer-backup.php, dup-installer-bootlog__[HASH].txt
310
+ * DIRS: dup-installer
311
+ * DEV FILES: wp-config.orig
312
+ * Last set is for lazy developer cleanup files that a developer may have
313
+ * accidently left around lets be proactive for the user just in case.
314
+ *
315
+ * @return array [file_name, file_path]
316
+ */
317
+ public static function getInstallerFiles()
318
+ {
319
+ // alphanumeric 7 time, then -(dash), then 8 digits
320
+ $abs_path = duplicator_get_abs_path();
321
+ $four_digit_glob_pattern = '[0-9][0-9][0-9][0-9]';
322
+ $retArr = array(
323
+ basename(DUPLICATOR_INSTALLER_DIRECTORY) . ' ' . esc_html__('(directory)', 'duplicator') => DUPLICATOR_INSTALLER_DIRECTORY,
324
+ DUPLICATOR_INSTALL_PHP => $abs_path . '/' . DUPLICATOR_INSTALL_PHP,
325
+ '[HASH]' . '_' . DUPLICATOR_INSTALL_PHP => $abs_path . '/*_*' . $four_digit_glob_pattern . '_' . DUPLICATOR_INSTALL_PHP,
326
+ DUPLICATOR_INSTALL_BAK => $abs_path . '/' . DUPLICATOR_INSTALL_BAK,
327
+ '[HASH]' . '_' . DUPLICATOR_INSTALL_BAK => $abs_path . '/*_*' . $four_digit_glob_pattern . '_' . DUPLICATOR_INSTALL_BAK,
328
+ '[HASH]_archive.zip|daf' => $abs_path . '/*_*' . $four_digit_glob_pattern . '_archive.[zd][ia][pf]',
329
+ 'dup-installer-bootlog__[HASH].txt' => $abs_path . '/dup-installer-bootlog__' . DUPLICATOR_INSTALLER_HASH_PATTERN . '.txt',
330
+ );
331
+
332
+ // legacy package
333
+ $retArr['dup-wp-config-arc__[HASH].txt'] = $abs_path . '/dup-wp-config-arc__' . DUPLICATOR_INSTALLER_HASH_PATTERN . '.txt';
334
+ return $retArr;
335
+ }
336
+
337
+ /**
338
+ * Get the IP of a client machine
339
+ *
340
+ * @return string IP of the client machine
341
+ */
342
+ public static function getClientIP()
343
+ {
344
+ if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) {
345
+ return $_SERVER["HTTP_X_FORWARDED_FOR"];
346
+ } elseif (array_key_exists('REMOTE_ADDR', $_SERVER)) {
347
+ return $_SERVER["REMOTE_ADDR"];
348
+ } elseif (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) {
349
+ return $_SERVER["HTTP_CLIENT_IP"];
350
+ }
351
+ return '';
352
+ }
353
+
354
+ /**
355
+ * Get PHP memory usage
356
+ *
357
+ * @return string Returns human readable memory usage.
358
+ */
359
+ public static function getPHPMemory($peak = false)
360
+ {
361
+ if ($peak) {
362
+ $result = 'Unable to read PHP peak memory usage';
363
+ if (function_exists('memory_get_peak_usage')) {
364
+ $result = DUP_Util::byteSize(memory_get_peak_usage(true));
365
+ }
366
+ } else {
367
+ $result = 'Unable to read PHP memory usage';
368
+ if (function_exists('memory_get_usage')) {
369
+ $result = DUP_Util::byteSize(memory_get_usage(true));
370
+ }
371
+ }
372
+ return $result;
373
+ }
374
+ }
classes/class.settings.php CHANGED
@@ -1,268 +1,254 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- // Exit if accessed directly
4
- if (!defined('DUPLICATOR_VERSION'))
5
- exit;
6
-
7
- abstract class DUP_Archive_Build_Mode
8
- {
9
-
10
- const Unconfigured = -1;
11
- const Auto = 0; // should no longer be used
12
- // const Shell_Exec = 1;
13
- const ZipArchive = 2;
14
- const DupArchive = 3;
15
-
16
- }
17
-
18
- class DUP_Settings
19
- {
20
-
21
- const OPT_SETTINGS = 'duplicator_settings';
22
- const INSTALLER_NAME_MODE_WITH_HASH = 'withhash';
23
- const INSTALLER_NAME_MODE_SIMPLE = 'simple';
24
- const STORAGE_POSITION_LECAGY = 'legacy';
25
- const STORAGE_POSITION_WP_CONTENT = 'wpcont';
26
- const SSDIR_NAME_LEGACY = 'wp-snapshots';
27
- const SSDIR_NAME_NEW = 'backups-dup-lite';
28
-
29
- protected static $Data;
30
- protected static $ssDirPath = null;
31
- protected static $ssDirUrl = null;
32
-
33
- /**
34
- * Class used to manage all the settings for the plugin
35
- */
36
- public static function init()
37
- {
38
- self::$Data = get_option(self::OPT_SETTINGS);
39
- //when the plugin updated, this will be true
40
- if (empty(self::$Data) || empty(self::$Data['version']) || version_compare(DUPLICATOR_VERSION, self::$Data['version'], '>')) {
41
- self::SetDefaults();
42
- }
43
- }
44
-
45
- /**
46
- * Find the setting value
47
- * @param string $key The name of the key to find
48
- * @return The value stored in the key returns null if key does not exist
49
- */
50
- public static function Get($key = '')
51
- {
52
- $result = null;
53
- if (isset(self::$Data[$key])) {
54
- $result = self::$Data[$key];
55
- } else {
56
- $defaults = self::GetAllDefaults();
57
- if (isset($defaults[$key])) {
58
- $result = $defaults[$key];
59
- }
60
- }
61
- return $result;
62
- }
63
-
64
- /**
65
- * Set the settings value in memory only
66
- * @param string $key The name of the key to find
67
- * @param string $value The value to set
68
- * remarks: The Save() method must be called to write the Settings object to the DB
69
- */
70
- public static function Set($key, $value)
71
- {
72
- if (isset(self::$Data[$key])) {
73
- self::$Data[$key] = ($value == null) ? '' : $value;
74
- } elseif (!empty($key)) {
75
- self::$Data[$key] = ($value == null) ? '' : $value;
76
- }
77
- }
78
-
79
- public static function setStoragePosition($newPosition)
80
- {
81
- $legacyPath = self::getSsdirPathLegacy();
82
- $wpContPath = self::getSsdirPathWpCont();
83
-
84
- $oldStoragePost = self::Get('storage_position');
85
-
86
- self::resetPositionVars();
87
-
88
- switch ($newPosition) {
89
- case self::STORAGE_POSITION_LECAGY:
90
- self::$Data['storage_position'] = self::STORAGE_POSITION_LECAGY;
91
- if (!DUP_Util::initSnapshotDirectory()) {
92
- self::resetPositionVars();
93
- self::$Data['storage_position'] = $oldStoragePost;
94
- return false;
95
- }
96
- if (is_dir($wpContPath)) {
97
- if (DupLiteSnapLibIOU::moveContentDirToTarget($wpContPath, $legacyPath, true)) {
98
- DupLiteSnapLibIOU::rrmdir($wpContPath);
99
- }
100
- }
101
- break;
102
- case self::STORAGE_POSITION_WP_CONTENT:
103
- self::$Data['storage_position'] = self::STORAGE_POSITION_WP_CONTENT;
104
- if (!DUP_Util::initSnapshotDirectory()) {
105
- self::resetPositionVars();
106
- self::$Data['storage_position'] = $oldStoragePost;
107
- return false;
108
- }
109
- if (is_dir($legacyPath)) {
110
- if (DupLiteSnapLibIOU::moveContentDirToTarget($legacyPath, $wpContPath, true)) {
111
- DupLiteSnapLibIOU::rrmdir($legacyPath);
112
- }
113
- }
114
- break;
115
- default:
116
- throw new Exception('Unknown storage position');
117
- }
118
-
119
- return true;
120
- }
121
-
122
- protected static function resetPositionVars()
123
- {
124
- self::$ssDirPath = null;
125
- self::$ssDirUrl = null;
126
- }
127
-
128
- /**
129
- * Saves all the setting values to the database
130
- * @return True if option value has changed, false if not or if update failed.
131
- */
132
- public static function Save()
133
- {
134
- return update_option(self::OPT_SETTINGS, self::$Data);
135
- }
136
-
137
- /**
138
- * Deletes all the setting values to the database
139
- * @return True if option value has changed, false if not or if update failed.
140
- */
141
- public static function Delete()
142
- {
143
- return delete_option(self::OPT_SETTINGS);
144
- }
145
-
146
- /**
147
- * Sets the defaults if they have not been set
148
- * @return True if option value has changed, false if not or if update failed.
149
- */
150
- public static function SetDefaults()
151
- {
152
- $defaults = self::GetAllDefaults();
153
- self::$Data = apply_filters('duplicator_defaults_settings', $defaults);
154
- return self::Save();
155
- }
156
-
157
- /**
158
- * DeleteWPOption: Cleans up legacy data
159
- */
160
- public static function DeleteWPOption($optionName)
161
- {
162
- if (in_array($optionName, $GLOBALS['DUPLICATOR_OPTS_DELETE'])) {
163
- return delete_option($optionName);
164
- }
165
- return false;
166
- }
167
-
168
- public static function GetAllDefaults()
169
- {
170
- $default = array();
171
- $default['version'] = DUPLICATOR_VERSION;
172
-
173
- //Flag used to remove the wp_options value duplicator_settings which are all the settings in this class
174
- $default['uninstall_settings'] = isset(self::$Data['uninstall_settings']) ? self::$Data['uninstall_settings'] : true;
175
- //Flag used to remove entire wp-snapshot directory
176
- $default['uninstall_files'] = isset(self::$Data['uninstall_files']) ? self::$Data['uninstall_files'] : true;
177
- //Flag used to remove all tables
178
- $default['uninstall_tables'] = isset(self::$Data['uninstall_tables']) ? self::$Data['uninstall_tables'] : true;
179
-
180
- //Flag used to show debug info
181
- $default['package_debug'] = isset(self::$Data['package_debug']) ? self::$Data['package_debug'] : false;
182
- //Flag used to enable mysqldump
183
- $default['package_mysqldump'] = isset(self::$Data['package_mysqldump']) ? self::$Data['package_mysqldump'] : true;
184
- //Optional mysqldump search path
185
- $default['package_mysqldump_path'] = isset(self::$Data['package_mysqldump_path']) ? self::$Data['package_mysqldump_path'] : '';
186
- //Optional mysql limit size
187
- $default['package_phpdump_qrylimit'] = isset(self::$Data['package_phpdump_qrylimit']) ? self::$Data['package_phpdump_qrylimit'] : "100";
188
- //Optional mysqldump search path
189
- $default['package_zip_flush'] = isset(self::$Data['package_zip_flush']) ? self::$Data['package_zip_flush'] : false;
190
- //Optional mysqldump search path
191
- $default['installer_name_mode'] = isset(self::$Data['installer_name_mode']) ? self::$Data['installer_name_mode'] : self::INSTALLER_NAME_MODE_SIMPLE;
192
- // storage position
193
- $default['storage_position'] = isset(self::$Data['storage_position']) ? self::$Data['storage_position'] : self::STORAGE_POSITION_WP_CONTENT;
194
-
195
- //Flag for .htaccess file
196
- $default['storage_htaccess_off'] = isset(self::$Data['storage_htaccess_off']) ? self::$Data['storage_htaccess_off'] : false;
197
- // Initial archive build mode
198
- if (isset(self::$Data['archive_build_mode'])) {
199
- $default['archive_build_mode'] = self::$Data['archive_build_mode'];
200
- } else {
201
- $is_ziparchive_available = apply_filters('duplicator_is_ziparchive_available', class_exists('ZipArchive'));
202
- $default['archive_build_mode'] = $is_ziparchive_available ? DUP_Archive_Build_Mode::ZipArchive : DUP_Archive_Build_Mode::DupArchive;
203
- }
204
-
205
- // $default['package_zip_flush'] = apply_filters('duplicator_package_zip_flush_default_setting', '0');
206
- //Skip scan archive
207
- $default['skip_archive_scan'] = isset(self::$Data['skip_archive_scan']) ? self::$Data['skip_archive_scan'] : false;
208
- $default['unhook_third_party_js'] = isset(self::$Data['unhook_third_party_js']) ? self::$Data['unhook_third_party_js'] : false;
209
- $default['unhook_third_party_css'] = isset(self::$Data['unhook_third_party_css']) ? self::$Data['unhook_third_party_css'] : false;
210
-
211
- $default['active_package_id'] = -1;
212
-
213
- return $default;
214
- }
215
-
216
- public static function get_create_date_format()
217
- {
218
- static $ui_create_frmt = null;
219
- if (is_null($ui_create_frmt)) {
220
- $ui_create_frmt = is_numeric(self::Get('package_ui_created')) ? self::Get('package_ui_created') : 1;
221
- }
222
- return $ui_create_frmt;
223
- }
224
-
225
- public static function getSsdirPathLegacy()
226
- {
227
- return DupLiteSnapLibIOU::safePathTrailingslashit(duplicator_get_abs_path()).self::SSDIR_NAME_LEGACY;
228
- }
229
-
230
- public static function getSsdirPathWpCont()
231
- {
232
- return DupLiteSnapLibIOU::safePathTrailingslashit(WP_CONTENT_DIR).self::SSDIR_NAME_NEW;
233
- }
234
-
235
- public static function getSsdirPath()
236
- {
237
- if (is_null(self::$ssDirPath)) {
238
- if (self::Get('storage_position') === self::STORAGE_POSITION_LECAGY) {
239
- self::$ssDirPath = self::getSsdirPathLegacy();
240
- } else {
241
- self::$ssDirPath = self::getSsdirPathWpCont();
242
- }
243
- }
244
- return self::$ssDirPath;
245
- }
246
-
247
- public static function getSsdirUrl()
248
- {
249
- if (is_null(self::$ssDirUrl)) {
250
- if (self::Get('storage_position') === self::STORAGE_POSITION_LECAGY) {
251
- self::$ssDirUrl = DupLiteSnapLibIOU::trailingslashit(DUPLICATOR_SITE_URL).self::SSDIR_NAME_LEGACY;
252
- } else {
253
- self::$ssDirUrl = DupLiteSnapLibIOU::trailingslashit(content_url()).self::SSDIR_NAME_NEW;
254
- }
255
- }
256
- return self::$ssDirUrl;
257
- }
258
-
259
- public static function getSsdirTmpPath()
260
- {
261
- return self::getSsdirPath().'/tmp';
262
- }
263
-
264
- public static function getSsdirInstallerPath()
265
- {
266
- return self::getSsdirPath().'/installer';
267
- }
268
- }
1
+ <?php
2
+
3
+ use Duplicator\Libs\Snap\SnapIO;
4
+
5
+ abstract class DUP_Archive_Build_Mode
6
+ {
7
+ const Unconfigured = -1;
8
+ const Auto = 0;
9
+ // should no longer be used
10
+ // const Shell_Exec = 1;
11
+ const ZipArchive = 2;
12
+ const DupArchive = 3;
13
+ }
14
+
15
+ class DUP_Settings
16
+ {
17
+ const OPT_SETTINGS = 'duplicator_settings';
18
+ const INSTALLER_NAME_MODE_WITH_HASH = 'withhash';
19
+ const INSTALLER_NAME_MODE_SIMPLE = 'simple';
20
+ const STORAGE_POSITION_LEGACY = 'legacy';
21
+ const STORAGE_POSITION_WP_CONTENT = 'wpcont';
22
+ const SSDIR_NAME_LEGACY = 'wp-snapshots';
23
+ const SSDIR_NAME_NEW = 'backups-dup-lite';
24
+ protected static $Data;
25
+ protected static $ssDirPath = null;
26
+ protected static $ssDirUrl = null;
27
+ /**
28
+ * Class used to manage all the settings for the plugin
29
+ */
30
+ public static function init()
31
+ {
32
+ self::$Data = get_option(self::OPT_SETTINGS);
33
+ //when the plugin updated, this will be true
34
+ if (empty(self::$Data) || empty(self::$Data['version']) || version_compare(DUPLICATOR_VERSION, self::$Data['version'], '>')) {
35
+ self::SetDefaults();
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Find the setting value
41
+ * @param string $key The name of the key to find
42
+ * @return string The value stored in the key returns null if key does not exist
43
+ */
44
+ public static function Get($key = '')
45
+ {
46
+ $result = null;
47
+ if (isset(self::$Data[$key])) {
48
+ $result = self::$Data[$key];
49
+ } else {
50
+ $defaults = self::GetAllDefaults();
51
+ if (isset($defaults[$key])) {
52
+ $result = $defaults[$key];
53
+ }
54
+ }
55
+ return $result;
56
+ }
57
+
58
+ /**
59
+ * Set the settings value in memory only
60
+ * @param string $key The name of the key to find
61
+ * @param string $value The value to set
62
+ * remarks: The Save() method must be called to write the Settings object to the DB
63
+ */
64
+ public static function Set($key, $value)
65
+ {
66
+ if (isset(self::$Data[$key])) {
67
+ self::$Data[$key] = ($value == null) ? '' : $value;
68
+ } elseif (!empty($key)) {
69
+ self::$Data[$key] = ($value == null) ? '' : $value;
70
+ }
71
+ }
72
+
73
+ public static function setStoragePosition($newPosition)
74
+ {
75
+ $legacyPath = self::getSsdirPathLegacy();
76
+ $wpContPath = self::getSsdirPathWpCont();
77
+ $oldStoragePost = self::Get('storage_position');
78
+ self::resetPositionVars();
79
+ switch ($newPosition) {
80
+ case self::STORAGE_POSITION_LEGACY:
81
+ self::$Data['storage_position'] = self::STORAGE_POSITION_LEGACY;
82
+ if (!DUP_Util::initSnapshotDirectory()) {
83
+ self::resetPositionVars();
84
+ self::$Data['storage_position'] = $oldStoragePost;
85
+ return false;
86
+ }
87
+ if (is_dir($wpContPath)) {
88
+ if (SnapIO::rcopy($wpContPath, $legacyPath)) {
89
+ SnapIO::rrmdir($wpContPath);
90
+ }
91
+ }
92
+ break;
93
+ case self::STORAGE_POSITION_WP_CONTENT:
94
+ self::$Data['storage_position'] = self::STORAGE_POSITION_WP_CONTENT;
95
+ if (!DUP_Util::initSnapshotDirectory()) {
96
+ self::resetPositionVars();
97
+ self::$Data['storage_position'] = $oldStoragePost;
98
+ return false;
99
+ }
100
+ if (is_dir($legacyPath)) {
101
+ if (SnapIO::rcopy($legacyPath, $wpContPath)) {
102
+ SnapIO::rrmdir($legacyPath);
103
+ }
104
+ }
105
+ break;
106
+ default:
107
+ throw new Exception('Unknown storage position');
108
+ }
109
+
110
+ return true;
111
+ }
112
+
113
+ protected static function resetPositionVars()
114
+ {
115
+ self::$ssDirPath = null;
116
+ self::$ssDirUrl = null;
117
+ }
118
+
119
+ /**
120
+ * Saves all the setting values to the database
121
+ * @return True if option value has changed, false if not or if update failed.
122
+ */
123
+ public static function Save()
124
+ {
125
+ return update_option(self::OPT_SETTINGS, self::$Data);
126
+ }
127
+
128
+ /**
129
+ * Deletes all the setting values to the database
130
+ * @return True if option value has changed, false if not or if update failed.
131
+ */
132
+ public static function Delete()
133
+ {
134
+ return delete_option(self::OPT_SETTINGS);
135
+ }
136
+
137
+ /**
138
+ * Sets the defaults if they have not been set
139
+ * @return True if option value has changed, false if not or if update failed.
140
+ */
141
+ public static function SetDefaults()
142
+ {
143
+ $defaults = self::GetAllDefaults();
144
+ self::$Data = apply_filters('duplicator_defaults_settings', $defaults);
145
+ return self::Save();
146
+ }
147
+
148
+ /**
149
+ * DeleteWPOption: Cleans up legacy data
150
+ */
151
+ public static function DeleteWPOption($optionName)
152
+ {
153
+ if (in_array($optionName, $GLOBALS['DUPLICATOR_OPTS_DELETE'])) {
154
+ return delete_option($optionName);
155
+ }
156
+ return false;
157
+ }
158
+
159
+ public static function GetAllDefaults()
160
+ {
161
+ $default = array();
162
+ $default['version'] = DUPLICATOR_VERSION;
163
+ //Flag used to remove the wp_options value duplicator_settings which are all the settings in this class
164
+ $default['uninstall_settings'] = isset(self::$Data['uninstall_settings']) ? self::$Data['uninstall_settings'] : true;
165
+ //Flag used to remove entire wp-snapshot directory
166
+ $default['uninstall_files'] = isset(self::$Data['uninstall_files']) ? self::$Data['uninstall_files'] : true;
167
+ //Flag used to remove all tables
168
+ $default['uninstall_tables'] = isset(self::$Data['uninstall_tables']) ? self::$Data['uninstall_tables'] : true;
169
+ //Flag used to show debug info
170
+ $default['package_debug'] = isset(self::$Data['package_debug']) ? self::$Data['package_debug'] : false;
171
+ //Flag used to enable mysqldump
172
+ $default['package_mysqldump'] = isset(self::$Data['package_mysqldump']) ? self::$Data['package_mysqldump'] : true;
173
+ //Optional mysqldump search path
174
+ $default['package_mysqldump_path'] = isset(self::$Data['package_mysqldump_path']) ? self::$Data['package_mysqldump_path'] : '';
175
+ //Optional mysql limit size
176
+ $default['package_phpdump_qrylimit'] = isset(self::$Data['package_phpdump_qrylimit']) ? self::$Data['package_phpdump_qrylimit'] : "100";
177
+ //Optional mysqldump search path
178
+ $default['package_zip_flush'] = isset(self::$Data['package_zip_flush']) ? self::$Data['package_zip_flush'] : false;
179
+ //Optional mysqldump search path
180
+ $default['installer_name_mode'] = isset(self::$Data['installer_name_mode']) ? self::$Data['installer_name_mode'] : self::INSTALLER_NAME_MODE_SIMPLE;
181
+ // storage position
182
+ $default['storage_position'] = isset(self::$Data['storage_position']) ? self::$Data['storage_position'] : self::STORAGE_POSITION_WP_CONTENT;
183
+ //Flag for .htaccess file
184
+ $default['storage_htaccess_off'] = isset(self::$Data['storage_htaccess_off']) ? self::$Data['storage_htaccess_off'] : false;
185
+ // Initial archive build mode
186
+ if (isset(self::$Data['archive_build_mode'])) {
187
+ $default['archive_build_mode'] = self::$Data['archive_build_mode'];
188
+ } else {
189
+ $is_ziparchive_available = apply_filters('duplicator_is_ziparchive_available', class_exists('ZipArchive'));
190
+ $default['archive_build_mode'] = $is_ziparchive_available ? DUP_Archive_Build_Mode::ZipArchive : DUP_Archive_Build_Mode::DupArchive;
191
+ }
192
+
193
+ // $default['package_zip_flush'] = apply_filters('duplicator_package_zip_flush_default_setting', '0');
194
+ //Skip scan archive
195
+ $default['skip_archive_scan'] = isset(self::$Data['skip_archive_scan']) ? self::$Data['skip_archive_scan'] : false;
196
+ $default['unhook_third_party_js'] = isset(self::$Data['unhook_third_party_js']) ? self::$Data['unhook_third_party_js'] : false;
197
+ $default['unhook_third_party_css'] = isset(self::$Data['unhook_third_party_css']) ? self::$Data['unhook_third_party_css'] : false;
198
+ $default['active_package_id'] = -1;
199
+ return $default;
200
+ }
201
+
202
+ public static function get_create_date_format()
203
+ {
204
+ static $ui_create_frmt = null;
205
+ if (is_null($ui_create_frmt)) {
206
+ $ui_create_frmt = is_numeric(self::Get('package_ui_created')) ? self::Get('package_ui_created') : 1;
207
+ }
208
+ return $ui_create_frmt;
209
+ }
210
+
211
+ public static function getSsdirPathLegacy()
212
+ {
213
+ return SnapIO::safePathTrailingslashit(duplicator_get_abs_path()) . self::SSDIR_NAME_LEGACY;
214
+ }
215
+
216
+ public static function getSsdirPathWpCont()
217
+ {
218
+ return SnapIO::safePathTrailingslashit(WP_CONTENT_DIR) . self::SSDIR_NAME_NEW;
219
+ }
220
+
221
+ public static function getSsdirPath()
222
+ {
223
+ if (is_null(self::$ssDirPath)) {
224
+ if (self::Get('storage_position') === self::STORAGE_POSITION_LEGACY) {
225
+ self::$ssDirPath = self::getSsdirPathLegacy();
226
+ } else {
227
+ self::$ssDirPath = self::getSsdirPathWpCont();
228
+ }
229
+ }
230
+ return self::$ssDirPath;
231
+ }
232
+
233
+ public static function getSsdirUrl()
234
+ {
235
+ if (is_null(self::$ssDirUrl)) {
236
+ if (self::Get('storage_position') === self::STORAGE_POSITION_LEGACY) {
237
+ self::$ssDirUrl = SnapIO::trailingslashit(DUPLICATOR_SITE_URL) . self::SSDIR_NAME_LEGACY;
238
+ } else {
239
+ self::$ssDirUrl = SnapIO::trailingslashit(content_url()) . self::SSDIR_NAME_NEW;
240
+ }
241
+ }
242
+ return self::$ssDirUrl;
243
+ }
244
+
245
+ public static function getSsdirTmpPath()
246
+ {
247
+ return self::getSsdirPath() . '/tmp';
248
+ }
249
+
250
+ public static function getSsdirInstallerPath()
251
+ {
252
+ return self::getSsdirPath() . '/installer';
253
+ }
254
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classes/host/class.custom.host.manager.php CHANGED
@@ -10,15 +10,16 @@
10
  * @link http://www.php-fig.org/psr/psr-2/
11
  *
12
  */
 
13
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
14
 
15
- require_once (DUPLICATOR_PLUGIN_PATH . '/classes/host/interface.host.php');
16
- require_once (DUPLICATOR_PLUGIN_PATH . '/classes/host/class.godaddy.host.php');
17
- require_once (DUPLICATOR_PLUGIN_PATH . '/classes/host/class.wpengine.host.php');
18
- require_once (DUPLICATOR_PLUGIN_PATH . '/classes/host/class.wordpresscom.host.php');
19
- require_once (DUPLICATOR_PLUGIN_PATH . '/classes/host/class.liquidweb.host.php');
20
- require_once (DUPLICATOR_PLUGIN_PATH . '/classes/host/class.pantheon.host.php');
21
- require_once (DUPLICATOR_PLUGIN_PATH . '/classes/host/class.flywheel.host.php');
22
 
23
  class DUP_Custom_Host_Manager
24
  {
@@ -60,7 +61,7 @@ class DUP_Custom_Host_Manager
60
  public static function getInstance()
61
  {
62
  if (is_null(self::$instance)) {
63
- self::$instance = new self;
64
  }
65
  return self::$instance;
66
  }
@@ -142,17 +143,17 @@ class DUP_Custom_Host_Manager
142
 
143
  public function WPConfigIsNotWriteable()
144
  {
145
- $wpConfigPath = duplicator_get_abs_path()."/wp-config.php";
146
 
147
  return file_exists($wpConfigPath) && !is_writeable($wpConfigPath);
148
  }
149
 
150
  public function notAccessibleCoreDirPresent()
151
  {
152
- $absPath = duplicator_get_abs_path();
153
  $coreDirs = array(
154
- $absPath.'/wp-admin',
155
- $absPath.'/wp-includes',
156
  WP_CONTENT_DIR
157
  );
158
 
10
  * @link http://www.php-fig.org/psr/psr-2/
11
  *
12
  */
13
+
14
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
15
 
16
+ require_once(DUPLICATOR_PLUGIN_PATH . '/classes/host/interface.host.php');
17
+ require_once(DUPLICATOR_PLUGIN_PATH . '/classes/host/class.godaddy.host.php');
18
+ require_once(DUPLICATOR_PLUGIN_PATH . '/classes/host/class.wpengine.host.php');
19
+ require_once(DUPLICATOR_PLUGIN_PATH . '/classes/host/class.wordpresscom.host.php');
20
+ require_once(DUPLICATOR_PLUGIN_PATH . '/classes/host/class.liquidweb.host.php');
21
+ require_once(DUPLICATOR_PLUGIN_PATH . '/classes/host/class.pantheon.host.php');
22
+ require_once(DUPLICATOR_PLUGIN_PATH . '/classes/host/class.flywheel.host.php');
23
 
24
  class DUP_Custom_Host_Manager
25
  {
61
  public static function getInstance()
62
  {
63
  if (is_null(self::$instance)) {
64
+ self::$instance = new self();
65
  }
66
  return self::$instance;
67
  }
143
 
144
  public function WPConfigIsNotWriteable()
145
  {
146
+ $wpConfigPath = duplicator_get_abs_path() . "/wp-config.php";
147
 
148
  return file_exists($wpConfigPath) && !is_writeable($wpConfigPath);
149
  }
150
 
151
  public function notAccessibleCoreDirPresent()
152
  {
153
+ $absPath = duplicator_get_abs_path();
154
  $coreDirs = array(
155
+ $absPath . '/wp-admin',
156
+ $absPath . '/wp-includes',
157
  WP_CONTENT_DIR
158
  );
159
 
classes/host/class.flywheel.host.php CHANGED
@@ -9,11 +9,11 @@
9
  * @link http://www.php-fig.org/psr/psr-2/
10
  *
11
  */
 
12
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
13
 
14
  class DUP_Flywheel_Host implements DUP_Host_interface
15
  {
16
-
17
  public static function getIdentifier()
18
  {
19
  return DUP_Custom_Host_Manager::HOST_FLYWHEEL;
@@ -21,12 +21,11 @@ class DUP_Flywheel_Host implements DUP_Host_interface
21
 
22
  public function isHosting()
23
  {
24
- $path = duplicator_get_home_path().'/.fw-config.php';
25
  return apply_filters('duplicator_host_check', file_exists($path), self::getIdentifier());
26
  }
27
 
28
  public function init()
29
  {
30
-
31
  }
32
  }
9
  * @link http://www.php-fig.org/psr/psr-2/
10
  *
11
  */
12
+
13
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
14
 
15
  class DUP_Flywheel_Host implements DUP_Host_interface
16
  {
 
17
  public static function getIdentifier()
18
  {
19
  return DUP_Custom_Host_Manager::HOST_FLYWHEEL;
21
 
22
  public function isHosting()
23
  {
24
+ $path = duplicator_get_home_path() . '/.fw-config.php';
25
  return apply_filters('duplicator_host_check', file_exists($path), self::getIdentifier());
26
  }
27
 
28
  public function init()
29
  {
 
30
  }
31
  }
classes/host/class.godaddy.host.php CHANGED
@@ -1,9 +1,9 @@
1
  <?php
2
- defined("ABSPATH") or die("");
 
3
 
4
  class DUP_GoDaddy_Host implements DUP_Host_interface
5
  {
6
-
7
  public static function getIdentifier()
8
  {
9
  return DUP_Custom_Host_Manager::HOST_GODADDY;
@@ -11,7 +11,10 @@ class DUP_GoDaddy_Host implements DUP_Host_interface
11
 
12
  public function isHosting()
13
  {
14
- return apply_filters('duplicator_godaddy_host_check', file_exists(DupLiteSnapLibIOU::safePathUntrailingslashit(WPMU_PLUGIN_DIR).'/gd-system-plugin.php'));
 
 
 
15
  }
16
 
17
  public function init()
@@ -24,4 +27,4 @@ class DUP_GoDaddy_Host implements DUP_Host_interface
24
  $defaults['archive_build_mode'] = DUP_Archive_Build_Mode::DupArchive;
25
  return $defaults;
26
  }
27
- }
1
  <?php
2
+
3
+ use Duplicator\Libs\Snap\SnapIO;
4
 
5
  class DUP_GoDaddy_Host implements DUP_Host_interface
6
  {
 
7
  public static function getIdentifier()
8
  {
9
  return DUP_Custom_Host_Manager::HOST_GODADDY;
11
 
12
  public function isHosting()
13
  {
14
+ return apply_filters(
15
+ 'duplicator_godaddy_host_check',
16
+ file_exists(SnapIO::safePathUntrailingslashit(WPMU_PLUGIN_DIR) . '/gd-system-plugin.php')
17
+ );
18
  }
19
 
20
  public function init()
27
  $defaults['archive_build_mode'] = DUP_Archive_Build_Mode::DupArchive;
28
  return $defaults;
29
  }
30
+ }
classes/host/class.liquidweb.host.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * wpengine custom hosting class
4
  *
@@ -8,10 +9,14 @@
8
  * @link http://www.php-fig.org/psr/psr-2/
9
  *
10
  */
 
 
 
11
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
12
 
13
  class DUP_Liquidweb_Host implements DUP_Host_interface
14
  {
 
15
 
16
  public static function getIdentifier()
17
  {
@@ -20,11 +25,13 @@ class DUP_Liquidweb_Host implements DUP_Host_interface
20
 
21
  public function isHosting()
22
  {
23
- return apply_filters('duplicator_liquidweb_host_check', file_exists(DupLiteSnapLibIOU::safePathUntrailingslashit(WPMU_PLUGIN_DIR).'/liquid-web.php'));
 
 
 
24
  }
25
 
26
  public function init()
27
  {
28
-
29
  }
30
- }
1
  <?php
2
+
3
  /**
4
  * wpengine custom hosting class
5
  *
9
  * @link http://www.php-fig.org/psr/psr-2/
10
  *
11
  */
12
+
13
+ use Duplicator\Libs\Snap\SnapIO;
14
+
15
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
16
 
17
  class DUP_Liquidweb_Host implements DUP_Host_interface
18
  {
19
+ const TEST = 0;
20
 
21
  public static function getIdentifier()
22
  {
25
 
26
  public function isHosting()
27
  {
28
+ return apply_filters(
29
+ 'duplicator_liquidweb_host_check',
30
+ file_exists(SnapIO::safePathUntrailingslashit(WPMU_PLUGIN_DIR) . '/liquid-web.php')
31
+ );
32
  }
33
 
34
  public function init()
35
  {
 
36
  }
37
+ }
classes/host/class.pantheon.host.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * godaddy custom hosting class
4
  *
@@ -8,11 +9,13 @@
8
  * @link http://www.php-fig.org/psr/psr-2/
9
  *
10
  */
 
 
 
11
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
12
 
13
  class DUP_Pantheon_Host implements DUP_Host_interface
14
  {
15
-
16
  public static function getIdentifier()
17
  {
18
  return DUP_Custom_Host_Manager::HOST_PANTHEON;
@@ -20,11 +23,13 @@ class DUP_Pantheon_Host implements DUP_Host_interface
20
 
21
  public function isHosting()
22
  {
23
- return apply_filters('duplicator_pantheon_host_check', file_exists(DupLiteSnapLibIOU::safePathUntrailingslashit(WPMU_PLUGIN_DIR).'/pantheon.php'));
 
 
 
24
  }
25
 
26
  public function init()
27
  {
28
-
29
  }
30
- }
1
  <?php
2
+
3
  /**
4
  * godaddy custom hosting class
5
  *
9
  * @link http://www.php-fig.org/psr/psr-2/
10
  *
11
  */
12
+
13
+ use Duplicator\Libs\Snap\SnapIO;
14
+
15
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
16
 
17
  class DUP_Pantheon_Host implements DUP_Host_interface
18
  {
 
19
  public static function getIdentifier()
20
  {
21
  return DUP_Custom_Host_Manager::HOST_PANTHEON;
23
 
24
  public function isHosting()
25
  {
26
+ return apply_filters(
27
+ 'duplicator_pantheon_host_check',
28
+ file_exists(SnapIO::safePathUntrailingslashit(WPMU_PLUGIN_DIR) . '/pantheon.php')
29
+ );
30
  }
31
 
32
  public function init()
33
  {
 
34
  }
35
+ }
classes/host/class.wordpresscom.host.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * godaddy custom hosting class
4
  *
@@ -8,11 +9,13 @@
8
  * @link http://www.php-fig.org/psr/psr-2/
9
  *
10
  */
 
 
 
11
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
12
 
13
  class DUP_WordpressCom_Host implements DUP_Host_interface
14
  {
15
-
16
  public static function getIdentifier()
17
  {
18
  return DUP_Custom_Host_Manager::HOST_WORDPRESSCOM;
@@ -20,11 +23,13 @@ class DUP_WordpressCom_Host implements DUP_Host_interface
20
 
21
  public function isHosting()
22
  {
23
- return apply_filters('duplicator_pro_wordpress_host_check', file_exists(DupLiteSnapLibIOU::safePathUntrailingslashit(WPMU_PLUGIN_DIR).'/wpcomsh-loader.php'));
 
 
 
24
  }
25
 
26
  public function init()
27
  {
28
-
29
  }
30
- }
1
  <?php
2
+
3
  /**
4
  * godaddy custom hosting class
5
  *
9
  * @link http://www.php-fig.org/psr/psr-2/
10
  *
11
  */
12
+
13
+ use Duplicator\Libs\Snap\SnapIO;
14
+
15
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
16
 
17
  class DUP_WordpressCom_Host implements DUP_Host_interface
18
  {
 
19
  public static function getIdentifier()
20
  {
21
  return DUP_Custom_Host_Manager::HOST_WORDPRESSCOM;
23
 
24
  public function isHosting()
25
  {
26
+ return apply_filters(
27
+ 'duplicator_pro_wordpress_host_check',
28
+ file_exists(SnapIO::safePathUntrailingslashit(WPMU_PLUGIN_DIR) . '/wpcomsh-loader.php')
29
+ );
30
  }
31
 
32
  public function init()
33
  {
 
34
  }
35
+ }
classes/host/class.wpengine.host.php CHANGED
@@ -1,5 +1,6 @@
1
  <?php
2
- defined("ABSPATH") or die("");
 
3
 
4
  // New encryption class
5
 
@@ -21,7 +22,10 @@ class DUP_WPEngine_Host implements DUP_Host_interface
21
 
22
  public function isHosting()
23
  {
24
- return apply_filters('duplicator_wp_engine_host_check', file_exists(DupLiteSnapLibIOU::safePathUntrailingslashit(WPMU_PLUGIN_DIR).'/wpengine-security-auditor.php'));
 
 
 
25
  }
26
 
27
  public static function installerFilePath($path)
@@ -36,7 +40,7 @@ class DUP_WPEngine_Host implements DUP_Host_interface
36
 
37
  public static function globalFileFilters($files)
38
  {
39
- $files[] = wp_normalize_path(WP_CONTENT_DIR).'/mysql.sql';
40
  return $files;
41
  }
42
 
@@ -45,5 +49,4 @@ class DUP_WPEngine_Host implements DUP_Host_interface
45
  $defaults['package_zip_flush'] = '1';
46
  return $defaults;
47
  }
48
-
49
- }
1
  <?php
2
+
3
+ use Duplicator\Libs\Snap\SnapIO;
4
 
5
  // New encryption class
6
 
22
 
23
  public function isHosting()
24
  {
25
+ return apply_filters(
26
+ 'duplicator_wp_engine_host_check',
27
+ file_exists(SnapIO::safePathUntrailingslashit(WPMU_PLUGIN_DIR) . '/wpengine-security-auditor.php')
28
+ );
29
  }
30
 
31
  public static function installerFilePath($path)
40
 
41
  public static function globalFileFilters($files)
42
  {
43
+ $files[] = wp_normalize_path(WP_CONTENT_DIR) . '/mysql.sql';
44
  return $files;
45
  }
46
 
49
  $defaults['package_zip_flush'] = '1';
50
  return $defaults;
51
  }
52
+ }
 
classes/host/interface.host.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  /**
3
  * interface for specific hostings class
4
  *
@@ -8,11 +9,11 @@
8
  * @link http://www.php-fig.org/psr/psr-2/
9
  *
10
  */
 
11
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
12
 
13
  interface DUP_Host_interface
14
  {
15
-
16
  /**
17
  * return the current host itentifier
18
  *
@@ -32,4 +33,4 @@ interface DUP_Host_interface
32
  * @return void
33
  */
34
  public function init();
35
- }
1
  <?php
2
+
3
  /**
4
  * interface for specific hostings class
5
  *
9
  * @link http://www.php-fig.org/psr/psr-2/
10
  *
11
  */
12
+
13
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
14
 
15
  interface DUP_Host_interface
16
  {
 
17
  /**
18
  * return the current host itentifier
19
  *
33
  * @return void
34
  */
35
  public function init();
36
+ }
classes/index.php CHANGED
@@ -1,2 +1,3 @@
1
  <?php
2
- //silent
 
1
  <?php
2
+
3
+ //silent
classes/package/class.pack.archive.file.list.php ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * archive path file list object
5
+ *
6
+ * Standard: PSR-2
7
+ * @link http://www.php-fig.org/psr/psr-2
8
+ *
9
+ * @package name
10
+ * @copyright (c) 2019, Snapcreek LLC
11
+ * @license https://opensource.org/licenses/GPL-3.0 GNU Public License
12
+ *
13
+ */
14
+
15
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
16
+
17
+ use Duplicator\Libs\Snap\SnapIO;
18
+ use Duplicator\Libs\Snap\SnapJson;
19
+
20
+ class DUP_Archive_File_List
21
+ {
22
+ protected $path = null;
23
+ protected $handle = null;
24
+ protected $cache = null;
25
+
26
+ public function __construct($path)
27
+ {
28
+ if (empty($path)) {
29
+ throw new Exception('path can\'t be empty');
30
+ }
31
+
32
+ $this->path = SnapIO::safePath($path);
33
+ }
34
+
35
+ public function __destruct()
36
+ {
37
+ $this->close();
38
+ }
39
+
40
+ public function getPath()
41
+ {
42
+ return $this->path;
43
+ }
44
+
45
+ public function open($truncate = false)
46
+ {
47
+ if (is_null($this->handle)) {
48
+ if (($this->handle = fopen($this->path, 'a+')) === false) {
49
+ DUP_Log::trace('Can\'t open ' . $this->path);
50
+ $this->handle = null;
51
+ return false;
52
+ }
53
+ }
54
+ if ($truncate) {
55
+ $this->emptyFile();
56
+ }
57
+ return true;
58
+ }
59
+
60
+ public function emptyFile()
61
+ {
62
+ if (!$this->open(false)) {
63
+ return false;
64
+ }
65
+ if (($res = ftruncate($this->handle, 0)) === false) {
66
+ DUP_Log::trace('Can\'t truncate file ' . $this->path);
67
+ return false;
68
+ }
69
+ return true;
70
+ }
71
+
72
+ public function close()
73
+ {
74
+ if (!is_null($this->handle)) {
75
+ if (($res = @fclose($this->handle)) === false) {
76
+ DUP_Log::trace('Can\'t close ' . $this->path);
77
+ return false;
78
+ }
79
+ $this->handle = null;
80
+ }
81
+ return true;
82
+ }
83
+
84
+ public function addEntry($path, $size, $nodes)
85
+ {
86
+
87
+ if (is_null($this->handle)) { // check to generate less overhead
88
+ if (!$this->open()) {
89
+ return false;
90
+ }
91
+ }
92
+ $entry = array('p' => $path, 's' => $size, 'n' => $nodes);
93
+ fwrite($this->handle, SnapJson::jsonEncode($entry) . "\n");
94
+ }
95
+
96
+ /**
97
+ *
98
+ * @param bool $pathOnly if true return only payth
99
+ * @return boolean|array|string return false if is end of filer.
100
+ */
101
+ public function getEntry($pathOnly = false)
102
+ {
103
+ if (is_null($this->handle)) { // check to generate less overhead
104
+ if (!$this->open()) {
105
+ return false;
106
+ }
107
+ }
108
+
109
+ if (($json = fgets($this->handle, 4196)) === false) {
110
+ // end of file return false
111
+ return false;
112
+ }
113
+
114
+ $result = json_decode($json, true);
115
+ if ($pathOnly) {
116
+ return $result['p'];
117
+ } else {
118
+ return $result;
119
+ }
120
+ }
121
+
122
+ protected function cleanCache()
123
+ {
124
+ $this->cache = null;
125
+ return true;
126
+ }
127
+
128
+ protected function loadCache($refreshCache = false)
129
+ {
130
+ if ($refreshCache || is_null($this->cache)) {
131
+ if (!$this->open()) {
132
+ return false;
133
+ }
134
+ $this->cache = array();
135
+ if (@fseek($this->handle, 0) === -1) {
136
+ DUP_Log::trace('Can\'t seek at 0 pos for file ' . $this->path);
137
+ $this->cleanCache();
138
+ return false;
139
+ }
140
+ while (($entry = $this->getEntry()) !== false) {
141
+ $this->cache[$entry['p']] = $entry;
142
+ }
143
+ if (!feof($this->handle)) {
144
+ DUP_Log::trace('Error: unexpected fgets() fail', '', false);
145
+ }
146
+ }
147
+ return true;
148
+ }
149
+
150
+ public function getEntryFromPath($path, $refreshCache = false)
151
+ {
152
+ if (!$this->loadCache($refreshCache)) {
153
+ return false;
154
+ }
155
+
156
+ if (array_key_exists($path, $this->cache)) {
157
+ return $this->cache[$path];
158
+ } else {
159
+ return false;
160
+ }
161
+ }
162
+
163
+ public function getEntriesFormPath($path, $refreshCache = false)
164
+ {
165
+ if (!$this->loadCache($refreshCache)) {
166
+ return false;
167
+ }
168
+
169
+ if (array_key_exists($path, $this->cache)) {
170
+ $result = array();
171
+ foreach ($this->cache as $current => $entry) {
172
+ if (preg_match('/^' . preg_quote($path, '/') . '\/[^\/]+$/', $current)) {
173
+ $result[] = $entry;
174
+ }
175
+ }
176
+ return $result;
177
+ } else {
178
+ return false;
179
+ }
180
+ }
181
+
182
+ public function getArrayPaths($pathPrefix = '')
183
+ {
184
+ if (!$this->open()) {
185
+ return false;
186
+ }
187
+
188
+ $result = array();
189
+ if (@fseek($this->handle, 0) === -1) {
190
+ DUP_Log::trace('Can\'t seek at 0 pos for file ' . $this->path);
191
+ return false;
192
+ }
193
+ $safePrefix = SnapIO::safePathUntrailingslashit($pathPrefix);
194
+ while (($path = $this->getEntry(true)) !== false) {
195
+ $result[] = $safePrefix . '/' . $path;
196
+ }
197
+ if (!feof($this->handle)) {
198
+ DUP_Log::trace('Error: unexpected fgets() fail', '', false);
199
+ }
200
+ return $result;
201
+ }
202
+ }
classes/package/class.pack.archive.filters.php CHANGED
@@ -1,96 +1,96 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- /**
4
- * The base class for all filter types Directories/Files/Extentions
5
- *
6
- * @package Duplicator
7
- * @subpackage classes/package
8
- *
9
- */
10
-
11
- // Exit if accessed directly
12
- if (! defined('DUPLICATOR_VERSION')) exit;
13
-
14
- class DUP_Archive_Filter_Scope_Base
15
- {
16
- //All internal storage items that duplicator decides to filter
17
- public $Core = array();
18
- //Global filter items added from settings
19
- public $Global = array();
20
- //Items when creating a package or template that a user decides to filter
21
- public $Instance = array();
22
- }
23
-
24
- /**
25
- * The filter types that belong to directories
26
- *
27
- * @package Duplicator
28
- * @subpackage classes/package
29
- *
30
- */
31
- class DUP_Archive_Filter_Scope_Directory extends DUP_Archive_Filter_Scope_Base
32
- {
33
- //Items that are not readable
34
- public $Warning = array();
35
- //Items that are not readable
36
- public $Unreadable = array();
37
- public $AddonSites = array();
38
- }
39
-
40
- /**
41
- * The filter types that belong to files
42
- *
43
- * @package Duplicator
44
- * @subpackage classes/package
45
- *
46
- */
47
- class DUP_Archive_Filter_Scope_File extends DUP_Archive_Filter_Scope_Directory
48
- {
49
- //Items that are too large
50
- public $Size = array();
51
-
52
- }
53
-
54
- /**
55
- * The filter information object which store all information about the filtered
56
- * data that is gathered to the execution of a scan process
57
- *
58
- * @package Duplicator
59
- * @subpackage classes/package
60
- *
61
- */
62
- class DUP_Archive_Filter_Info
63
- {
64
- //Contains all folder filter info
65
- public $Dirs = array();
66
- //Contains all file filter info
67
- public $Files = array();
68
- //Contains all extensions filter info
69
- public $Exts = array();
70
- public $UDirCount = 0;
71
- public $UFileCount = 0;
72
- public $UExtCount = 0;
73
- public $TreeSize;
74
- public $TreeWarning;
75
-
76
- /**
77
- * Init this object
78
- */
79
- public function __construct()
80
- {
81
- $this->reset();
82
- }
83
-
84
- /**
85
- * reset and clean all object
86
- */
87
- public function reset()
88
- {
89
- $this->Dirs = new DUP_Archive_Filter_Scope_Directory();
90
- $this->Files = new DUP_Archive_Filter_Scope_File();
91
- $this->Exts = new DUP_Archive_Filter_Scope_Base();
92
- $this->TreeSize = array();
93
- $this->TreeWarning = array();
94
- }
95
- }
96
-
1
+ <?php
2
+
3
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
4
+ /**
5
+ * The base class for all filter types Directories/Files/Extentions
6
+ *
7
+ * @package Duplicator
8
+ * @subpackage classes/package
9
+ *
10
+ */
11
+
12
+ // Exit if accessed directly
13
+ if (! defined('DUPLICATOR_VERSION')) {
14
+ exit;
15
+ }
16
+
17
+ class DUP_Archive_Filter_Scope_Base
18
+ {
19
+ //All internal storage items that duplicator decides to filter
20
+ public $Core = array();
21
+ //Global filter items added from settings
22
+ public $Global = array();
23
+ //Items when creating a package or template that a user decides to filter
24
+ public $Instance = array();
25
+ }
26
+
27
+ /**
28
+ * The filter types that belong to directories
29
+ *
30
+ * @package Duplicator
31
+ * @subpackage classes/package
32
+ *
33
+ */
34
+ class DUP_Archive_Filter_Scope_Directory extends DUP_Archive_Filter_Scope_Base
35
+ {
36
+ //Items that are not readable
37
+ public $Warning = array();
38
+ //Items that are not readable
39
+ public $Unreadable = array();
40
+ public $AddonSites = array();
41
+ }
42
+
43
+ /**
44
+ * The filter types that belong to files
45
+ *
46
+ * @package Duplicator
47
+ * @subpackage classes/package
48
+ *
49
+ */
50
+ class DUP_Archive_Filter_Scope_File extends DUP_Archive_Filter_Scope_Directory
51
+ {
52
+ //Items that are too large
53
+ public $Size = array();
54
+ }
55
+
56
+ /**
57
+ * The filter information object which store all information about the filtered
58
+ * data that is gathered to the execution of a scan process
59
+ *
60
+ * @package Duplicator
61
+ * @subpackage classes/package
62
+ *
63
+ */
64
+ class DUP_Archive_Filter_Info
65
+ {
66
+ /** @var DUP_Archive_Filter_Scope_Directory Contains all folder filter info */
67
+ public $Dirs = null;
68
+ /** @var DUP_Archive_Filter_Scope_File Contains all file filter info */
69
+ public $Files = null;
70
+ /** @var DUP_Archive_Filter_Scope_Base Contains all extensions filter info */
71
+ public $Exts = null;
72
+ public $UDirCount = 0;
73
+ public $UFileCount = 0;
74
+ public $UExtCount = 0;
75
+ public $TreeSize;
76
+ public $TreeWarning;
77
+ /**
78
+ * Init this object
79
+ */
80
+ public function __construct()
81
+ {
82
+ $this->reset();
83
+ }
84
+
85
+ /**
86
+ * reset and clean all object
87
+ */
88
+ public function reset()
89
+ {
90
+ $this->Dirs = new DUP_Archive_Filter_Scope_Directory();
91
+ $this->Files = new DUP_Archive_Filter_Scope_File();
92
+ $this->Exts = new DUP_Archive_Filter_Scope_Base();
93
+ $this->TreeSize = array();
94
+ $this->TreeWarning = array();
95
+ }
96
+ }
classes/package/class.pack.archive.php CHANGED
@@ -1,805 +1,1014 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- // Exit if accessed directly
4
- if (!defined('DUPLICATOR_VERSION')) exit;
5
-
6
- require_once (DUPLICATOR_PLUGIN_PATH.'classes/package/duparchive/class.pack.archive.duparchive.php');
7
- require_once (DUPLICATOR_PLUGIN_PATH.'classes/package/class.pack.archive.filters.php');
8
- require_once (DUPLICATOR_PLUGIN_PATH.'classes/package/class.pack.archive.zip.php');
9
- require_once (DUPLICATOR_PLUGIN_PATH.'lib/forceutf8/Encoding.php');
10
-
11
- /**
12
- * Class for handling archive setup and build process
13
- *
14
- * Standard: PSR-2 (almost)
15
- * @link http://www.php-fig.org/psr/psr-2
16
- *
17
- * @package DUP
18
- * @subpackage classes/package
19
- * @copyright (c) 2017, Snapcreek LLC
20
- * @license https://opensource.org/licenses/GPL-3.0 GNU Public License
21
- *
22
- */
23
- class DUP_Archive
24
- {
25
- //PUBLIC
26
- public $FilterDirs;
27
- public $FilterFiles;
28
- public $FilterExts;
29
- public $FilterDirsAll = array();
30
- public $FilterFilesAll = array();
31
- public $FilterExtsAll = array();
32
- public $FilterOn;
33
- public $ExportOnlyDB;
34
- public $File;
35
- public $Format;
36
- public $PackDir;
37
- public $Size = 0;
38
- public $Dirs = array();
39
- public $Files = array();
40
-
41
- /**
42
- *
43
- * @var DUP_Archive_Filter_Info
44
- */
45
- public $FilterInfo = null;
46
- public $RecursiveLinks = array();
47
- public $file_count = -1;
48
- //PROTECTED
49
- protected $Package;
50
- private $tmpFilterDirsAll = array();
51
- private $wpCorePaths = array();
52
- private $wpCoreExactPaths = array();
53
-
54
- /**
55
- * Init this object
56
- */
57
- public function __construct($package)
58
- {
59
- $this->Package = $package;
60
- $this->FilterOn = false;
61
- $this->ExportOnlyDB = false;
62
- $this->FilterInfo = new DUP_Archive_Filter_Info();
63
-
64
- $homePath = duplicator_get_home_path();
65
-
66
- $this->wpCorePaths[] = DUP_Util::safePath("{$homePath}/wp-admin");
67
- $this->wpCorePaths[] = DUP_Util::safePath(WP_CONTENT_DIR."/languages");
68
- $this->wpCorePaths[] = DUP_Util::safePath("{$homePath}/wp-includes");
69
-
70
- $this->wpCoreExactPaths[] = DUP_Util::safePath("{$homePath}");
71
- $this->wpCoreExactPaths[] = DUP_Util::safePath(WP_CONTENT_DIR);
72
- $this->wpCoreExactPaths[] = DUP_Util::safePath(WP_CONTENT_DIR."/uploads");
73
- $this->wpCoreExactPaths[] = DUP_Util::safePath(WP_CONTENT_DIR."/plugins");
74
- $this->wpCoreExactPaths[] = DUP_Util::safePath(get_theme_root());
75
- }
76
-
77
- /**
78
- * Builds the archive based on the archive type
79
- *
80
- * @param obj $package The package object that started this process
81
- *
82
- * @return null
83
- */
84
- public function build($package, $rethrow_exception = false)
85
- {
86
- DUP_LOG::trace("b1");
87
- $this->Package = $package;
88
- if (!isset($this->PackDir) && !is_dir($this->PackDir)) throw new Exception("The 'PackDir' property must be a valid directory.");
89
- if (!isset($this->File)) throw new Exception("A 'File' property must be set.");
90
-
91
- DUP_LOG::trace("b2");
92
- $completed = false;
93
-
94
- switch ($this->Format) {
95
- case 'TAR': break;
96
- case 'TAR-GZIP': break;
97
- case 'DAF':
98
- $completed = DUP_DupArchive::create($this, $this->Package->BuildProgress, $this->Package);
99
-
100
- $this->Package->Update();
101
- break;
102
-
103
- default:
104
- if (class_exists('ZipArchive')) {
105
- $this->Format = 'ZIP';
106
- DUP_Zip::create($this, $this->Package->BuildProgress);
107
- $completed = true;
108
- }
109
- break;
110
- }
111
-
112
- DUP_LOG::Trace("Completed build or build thread");
113
-
114
- if ($this->Package->BuildProgress === null) {
115
- // Zip path
116
- DUP_LOG::Trace("Completed Zip");
117
- $storePath = DUP_Settings::getSsdirTmpPath()."/{$this->File}";
118
- $this->Size = @filesize($storePath);
119
- $this->Package->setStatus(DUP_PackageStatus::ARCDONE);
120
- } else if ($completed) {
121
- // Completed DupArchive path
122
- DUP_LOG::Trace("Completed DupArchive build");
123
- if ($this->Package->BuildProgress->failed) {
124
- DUP_LOG::Trace("Error building DupArchive");
125
- $this->Package->setStatus(DUP_PackageStatus::ERROR);
126
- } else {
127
- $filepath = DUP_Settings::getSsdirTmpPath()."/{$this->File}";
128
- $this->Size = @filesize($filepath);
129
- $this->Package->setStatus(DUP_PackageStatus::ARCDONE);
130
- DUP_LOG::Trace("Done building archive");
131
- }
132
- } else {
133
- DUP_Log::trace("DupArchive chunk done but package not completed yet");
134
- }
135
- }
136
-
137
- /**
138
- *
139
- * @return int return DUP_Archive_Build_Mode
140
- */
141
- public function getBuildMode()
142
- {
143
- switch ($this->Format) {
144
- case 'TAR': break;
145
- case 'TAR-GZIP': break;
146
- case 'DAF':
147
- return DUP_Archive_Build_Mode::DupArchive;
148
- default:
149
- if (class_exists('ZipArchive')) {
150
- return DUP_Archive_Build_Mode::ZipArchive;
151
- } else {
152
- return DUP_Archive_Build_Mode::Unconfigured;
153
- }
154
- break;
155
- }
156
- }
157
-
158
- /**
159
- * Builds a list of files and directories to be included in the archive
160
- *
161
- * Get the directory size recursively, but don't calc the snapshot directory, exclusion directories
162
- * @link http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx Windows filename restrictions
163
- *
164
- * @return obj Returns a DUP_Archive object
165
- */
166
- public function getScannerData()
167
- {
168
- $this->createFilterInfo();
169
- $rootPath = duplicator_get_abs_path();
170
-
171
- $this->RecursiveLinks = array();
172
- //If the root directory is a filter then skip it all
173
- if (in_array($this->PackDir, $this->FilterDirsAll) || $this->Package->Archive->ExportOnlyDB) {
174
- $this->Dirs = array();
175
- } else {
176
- $this->Dirs[] = $this->PackDir;
177
-
178
- $this->getFileLists($rootPath);
179
-
180
- if ($this->isOuterWPContentDir()) {
181
- $this->Dirs[] = WP_CONTENT_DIR;
182
- $this->getFileLists(WP_CONTENT_DIR);
183
- }
184
-
185
- $this->setDirFilters();
186
- $this->setFileFilters();
187
- $this->setTreeFilters();
188
- }
189
-
190
- $this->FilterDirsAll = array_merge($this->FilterDirsAll, $this->FilterInfo->Dirs->Unreadable);
191
- $this->FilterFilesAll = array_merge($this->FilterFilesAll, $this->FilterInfo->Files->Unreadable);
192
- sort($this->FilterDirsAll);
193
-
194
- return $this;
195
- }
196
-
197
- /**
198
- * Save any property of this class through reflection
199
- *
200
- * @param $property A valid public property in this class
201
- * @param $value The value for the new dynamic property
202
- *
203
- * @return bool Returns true if the value has changed.
204
- */
205
- public function saveActiveItem($package, $property, $value)
206
- {
207
- $package = DUP_Package::getActive();
208
- $reflectionClass = new ReflectionClass($package->Archive);
209
- $reflectionClass->getProperty($property)->setValue($package->Archive, $value);
210
- return update_option(DUP_Package::OPT_ACTIVE, $package);
211
- }
212
-
213
- /**
214
- * Properly creates the directory filter list that is used for filtering directories
215
- *
216
- * @param string $dirs A semi-colon list of dir paths
217
- * /path1_/path/;/path1_/path2/;
218
- *
219
- * @returns string A cleaned up list of directory filters
220
- * @return string
221
- */
222
- public function parseDirectoryFilter($dirs = "")
223
- {
224
- $filters = "";
225
- $dir_array = array_unique(explode(";", $dirs));
226
- $clean_array = array();
227
- foreach ($dir_array as $val) {
228
- $val = DupLiteSnapLibIOU::safePathUntrailingslashit(DupLiteSnapLibUtil::sanitize_non_stamp_chars_newline_and_trim($val));
229
- if (strlen($val) >= 2 && is_dir($val)) {
230
- $clean_array[] = $val;
231
- }
232
- }
233
-
234
- if (count($clean_array)) {
235
- $clean_array = array_unique($clean_array);
236
- sort($clean_array);
237
- $filters = implode(';', $clean_array).';';
238
- }
239
- return $filters;
240
- }
241
-
242
- /**
243
- * Properly creates the file filter list that is used for filtering files
244
- *
245
- * @param string $files A semi-colon list of file paths
246
- * /path1_/path/file1.ext;/path1_/path2/file2.ext;
247
- *
248
- * @returns string A cleaned up list of file filters
249
- * @return string
250
- */
251
- public function parseFileFilter($files = "")
252
- {
253
- $filters = "";
254
- $file_array = array_unique(explode(";", $files));
255
- $clean_array = array();
256
- foreach ($file_array as $val) {
257
- $val = DupLiteSnapLibIOU::safePathUntrailingslashit(DupLiteSnapLibUtil::sanitize_non_stamp_chars_newline_and_trim($val));
258
- if (strlen($val) >= 2 && file_exists($val)) {
259
- $clean_array[] = $val;
260
- }
261
- }
262
-
263
- if (count($clean_array)) {
264
- $clean_array = array_unique($clean_array);
265
- sort($clean_array);
266
- $filters = implode(';', $clean_array).';';
267
- }
268
- return $filters;
269
- }
270
-
271
- /**
272
- * Properly creates the extension filter list that is used for filtering extensions
273
- *
274
- * @param string $dirs A semi-colon list of dir paths
275
- * .jpg;.zip;.gif;
276
- *
277
- * @returns string A cleaned up list of extension filters
278
- */
279
- public function parseExtensionFilter($extensions = "")
280
- {
281
- $filter_exts = "";
282
- if (strlen($extensions) >= 1 && $extensions != ";") {
283
- $filter_exts = str_replace(array(' ', '.'), '', $extensions);
284
- $filter_exts = str_replace(",", ";", $filter_exts);
285
- $filter_exts = DUP_Util::appendOnce($extensions, ";");
286
- }
287
- return $filter_exts;
288
- }
289
-
290
- /**
291
- * Creates the filter info setup data used for filtering the archive
292
- *
293
- * @return null
294
- */
295
- private function createFilterInfo()
296
- {
297
- //FILTER: INSTANCE ITEMS
298
- //Add the items generated at create time
299
- if ($this->FilterOn) {
300
- $this->FilterInfo->Dirs->Instance = array_map('DUP_Util::safePath', explode(";", $this->FilterDirs, -1));
301
- $this->FilterInfo->Files->Instance = array_map('DUP_Util::safePath', explode(";", $this->FilterFiles, -1));
302
- $this->FilterInfo->Exts->Instance = explode(";", $this->FilterExts, -1);
303
- }
304
-
305
- //FILTER: CORE ITMES
306
- //Filters Duplicator free packages & All pro local directories
307
- $wp_root = duplicator_get_abs_path();
308
- $upload_dir = wp_upload_dir();
309
- $upload_dir = isset($upload_dir['basedir']) ? basename($upload_dir['basedir']) : 'uploads';
310
- $wp_content = str_replace("\\", "/", WP_CONTENT_DIR);
311
- $wp_content_upload = "{$wp_content}/{$upload_dir}";
312
- $this->FilterInfo->Dirs->Core = array(
313
- //WP-ROOT
314
- DUP_Settings::getSsdirPathLegacy(),
315
- DUP_Settings::getSsdirPathWpCont(),
316
- $wp_root.'/.opcache',
317
- //WP-CONTENT
318
- $wp_content.'/backups-dup-pro',
319
- $wp_content.'/ai1wm-backups',
320
- $wp_content.'/backupwordpress',
321
- $wp_content.'/content/cache',
322
- $wp_content.'/contents/cache',
323
- $wp_content.'/infinitewp/backups',
324
- $wp_content.'/managewp/backups',
325
- $wp_content.'/old-cache',
326
- $wp_content.'/plugins/all-in-one-wp-migration/storage',
327
- $wp_content.'/updraft',
328
- $wp_content.'/wishlist-backup',
329
- $wp_content.'/wfcache',
330
- $wp_content.'/bps-backup', // BulletProof Security backup folder
331
- $wp_content.'/cache',
332
- //WP-CONTENT-UPLOADS
333
- $wp_content_upload.'/aiowps_backups',
334
- $wp_content_upload.'/backupbuddy_temp',
335
- $wp_content_upload.'/backupbuddy_backups',
336
- $wp_content_upload.'/ithemes-security/backups',
337
- $wp_content_upload.'/mainwp/backup',
338
- $wp_content_upload.'/pb_backupbuddy',
339
- $wp_content_upload.'/snapshots',
340
- $wp_content_upload.'/sucuri',
341
- $wp_content_upload.'/wp-clone',
342
- $wp_content_upload.'/wp_all_backup',
343
- $wp_content_upload.'/wpbackitup_backups'
344
- );
345
-
346
- if (class_exists('BackWPup')) {
347
- $upload_dir = wp_upload_dir(null, false, true);
348
- $this->FilterInfo->Dirs->Core[] = trailingslashit(str_replace( '\\',
349
- '/',
350
- $upload_dir['basedir'])).'backwpup-'.BackWPup::get_plugin_data('hash').'-backups/';
351
-
352
- $backwpup_cfg_logfolder = get_site_option('backwpup_cfg_logfolder');
353
- if (false !== $backwpup_cfg_logfolder) {
354
- $this->FilterInfo->Dirs->Core[] = $wp_content.'/'.$backwpup_cfg_logfolder;
355
- }
356
- }
357
- $duplicator_global_file_filters_on = apply_filters('duplicator_global_file_filters_on', $GLOBALS['DUPLICATOR_GLOBAL_FILE_FILTERS_ON']);
358
- if ($GLOBALS['DUPLICATOR_GLOBAL_FILE_FILTERS_ON']) {
359
- $duplicator_global_file_filters = apply_filters('duplicator_global_file_filters', $GLOBALS['DUPLICATOR_GLOBAL_FILE_FILTERS']);
360
- $this->FilterInfo->Files->Global = $duplicator_global_file_filters;
361
- }
362
-
363
- // Prevent adding double wp-content dir conflicts
364
- if ($this->isOuterWPContentDir()) {
365
- $default_wp_content_dir_path = DUP_Util::safePath(ABSPATH.'wp-content');
366
- if (file_exists($default_wp_content_dir_path)) {
367
- if (is_dir($default_wp_content_dir_path)) {
368
- $this->FilterInfo->Dirs->Core[] = $default_wp_content_dir_path;
369
- } else {
370
- $this->FilterInfo->Files->Core[] = $default_wp_content_dir_path;
371
- }
372
- }
373
- }
374
-
375
- $this->FilterDirsAll = array_merge($this->FilterInfo->Dirs->Instance, $this->FilterInfo->Dirs->Core);
376
- $this->FilterExtsAll = array_merge($this->FilterInfo->Exts->Instance, $this->FilterInfo->Exts->Core);
377
- $this->FilterFilesAll = array_merge($this->FilterInfo->Files->Instance, $this->FilterInfo->Files->Global);
378
-
379
- $abs_path = duplicator_get_abs_path();
380
- $this->FilterFilesAll[] = $abs_path.'/.htaccess';
381
- $this->FilterFilesAll[] = $abs_path.'/web.config';
382
- $this->FilterFilesAll[] = $abs_path.'/wp-config.php';
383
- $this->tmpFilterDirsAll = $this->FilterDirsAll;
384
-
385
- //PHP 5 on windows decode patch
386
- if (!DUP_Util::$PHP7_plus && DUP_Util::isWindows()) {
387
- foreach ($this->tmpFilterDirsAll as $key => $value) {
388
- if (preg_match('/[^\x20-\x7f]/', $value)) {
389
- $this->tmpFilterDirsAll[$key] = utf8_decode($value);
390
- }
391
- }
392
- }
393
- }
394
-
395
- /**
396
- * Get All Directories then filter
397
- *
398
- * @return null
399
- */
400
- private function setDirFilters()
401
- {
402
- $this->FilterInfo->Dirs->Warning = array();
403
- $this->FilterInfo->Dirs->Unreadable = array();
404
- $this->FilterInfo->Dirs->AddonSites = array();
405
- $skip_archive_scan = DUP_Settings::Get('skip_archive_scan');
406
-
407
- $utf8_key_list = array();
408
- $unset_key_list = array();
409
-
410
- //Filter directories invalid test checks for:
411
- // - characters over 250
412
- // - invlaid characters
413
- // - empty string
414
- // - directories ending with period (Windows incompatable)
415
- foreach ($this->Dirs as $key => $val) {
416
- $name = basename($val);
417
-
418
- //Dir is not readble remove flag for removal
419
- if (!is_readable($this->Dirs[$key])) {
420
- $unset_key_list[] = $key;
421
- $this->FilterInfo->Dirs->Unreadable[] = DUP_Encoding::toUTF8($val);
422
- }
423
-
424
- if (!$skip_archive_scan) {
425
- //Locate invalid directories and warn
426
- $invalid_test = (defined('PHP_MAXPATHLEN') && (strlen($val) > PHP_MAXPATHLEN)) || preg_match('/(\/|\*|\?|\>|\<|\:|\\|\|)/', $name) || trim($name) == '' || (strrpos($name, '.') == strlen($name) - 1 && substr($name, -1)
427
- == '.') || preg_match('/[^\x20-\x7f]/', $name);
428
-
429
- if ($invalid_test) {
430
- $utf8_key_list[] = $key;
431
- $this->FilterInfo->Dirs->Warning[] = DUP_Encoding::toUTF8($val);
432
- }
433
- }
434
-
435
- //Check for other WordPress installs
436
- if ($name === 'wp-admin') {
437
- $parent_dir = realpath(dirname($this->Dirs[$key]));
438
- if ($parent_dir != realpath(duplicator_get_abs_path())) {
439
- if (file_exists("$parent_dir/wp-includes")) {
440
- if (file_exists("$parent_dir/wp-config.php")) {
441
- // Ensure we aren't adding any critical directories
442
- $parent_name = basename($parent_dir);
443
- if (($parent_name != 'wp-includes') && ($parent_name != 'wp-content') && ($parent_name != 'wp-admin')) {
444
- $this->FilterInfo->Dirs->AddonSites[] = str_replace("\\", '/', $parent_dir);
445
- }
446
- }
447
- }
448
- }
449
- }
450
- }
451
-
452
- //Try to repair utf8 paths
453
- foreach ($utf8_key_list as $key) {
454
- $this->Dirs[$key] = DUP_Encoding::toUTF8($this->Dirs[$key]);
455
- }
456
-
457
- //Remove unreadable items outside of main loop for performance
458
- if (count($unset_key_list)) {
459
- foreach ($unset_key_list as $key) {
460
- unset($this->Dirs[$key]);
461
- }
462
- $this->Dirs = array_values($this->Dirs);
463
- }
464
- }
465
-
466
- /**
467
- * Get all files and filter out error prone subsets
468
- *
469
- * @return null
470
- */
471
- private function setFileFilters()
472
- {
473
- //Init for each call to prevent concatination from stored entity objects
474
- $this->Size = 0;
475
- $this->FilterInfo->Files->Size = array();
476
- $this->FilterInfo->Files->Warning = array();
477
- $this->FilterInfo->Files->Unreadable = array();
478
- $skip_archive_scan = DUP_Settings::Get('skip_archive_scan');
479
-
480
- $utf8_key_list = array();
481
- $unset_key_list = array();
482
-
483
- $wpconfig_filepath = $this->getWPConfigFilePath();
484
- if (!is_readable($wpconfig_filepath)) {
485
- $this->FilterInfo->Files->Unreadable[] = $wpconfig_filepath;
486
- }
487
-
488
- foreach ($this->Files as $key => $filePath) {
489
-
490
- $fileName = basename($filePath);
491
-
492
- if (!is_readable($filePath)) {
493
- $unset_key_list[] = $key;
494
- $this->FilterInfo->Files->Unreadable[] = $filePath;
495
- continue;
496
- }
497
-
498
- $fileSize = @filesize($filePath);
499
- $fileSize = empty($fileSize) ? 0 : $fileSize;
500
- $this->Size += $fileSize;
501
-
502
- if (!$skip_archive_scan) {
503
- $invalid_test = (defined('PHP_MAXPATHLEN') && (strlen($filePath) > PHP_MAXPATHLEN)) || preg_match('/(\/|\*|\?|\>|\<|\:|\\|\|)/', $fileName) || trim($fileName) == "" || preg_match('/[^\x20-\x7f]/', $fileName);
504
-
505
- if ($invalid_test) {
506
- $utf8_key_list[] = $key;
507
- $filePath = DUP_Encoding::toUTF8($filePath);
508
- $fileName = basename($filePath);
509
- $this->FilterInfo->Files->Warning[] = array(
510
- 'name' => $fileName,
511
- 'dir' => pathinfo($filePath, PATHINFO_DIRNAME),
512
- 'path' => $filePath);
513
- }
514
-
515
- if ($fileSize > DUPLICATOR_SCAN_WARNFILESIZE) {
516
- //$ext = pathinfo($filePath, PATHINFO_EXTENSION);
517
- $this->FilterInfo->Files->Size[] = array(
518
- 'ubytes' => $fileSize,
519
- 'bytes' => DUP_Util::byteSize($fileSize, 0),
520
- 'name' => $fileName,
521
- 'dir' => pathinfo($filePath, PATHINFO_DIRNAME),
522
- 'path' => $filePath);
523
- }
524
- }
525
- }
526
-
527
- //Try to repair utf8 paths
528
- foreach ($utf8_key_list as $key) {
529
- $this->Files[$key] = DUP_Encoding::toUTF8($this->Files[$key]);
530
- }
531
-
532
- //Remove unreadable items outside of main loop for performance
533
- if (count($unset_key_list)) {
534
- foreach ($unset_key_list as $key) {
535
- unset($this->Files[$key]);
536
- }
537
- $this->Files = array_values($this->Files);
538
- }
539
- }
540
-
541
- /**
542
- * Recursive function to get all directories in a wp install
543
- *
544
- * @notes:
545
- * Older PHP logic which is more stable on older version of PHP
546
- * NOTE RecursiveIteratorIterator is problematic on some systems issues include:
547
- * - error 'too many files open' for recursion
548
- * - $file->getExtension() is not reliable as it silently fails at least in php 5.2.17
549
- * - issues with when a file has a permission such as 705 and trying to get info (had to fallback to pathinfo)
550
- * - basic conclusion wait on the SPL libs until after php 5.4 is a requirments
551
- * - tight recursive loop use caution for speed
552
- *
553
- * @return array Returns an array of directories to include in the archive
554
- */
555
- private function getFileLists($path) {
556
- $handle = @opendir($path);
557
-
558
- if ($handle) {
559
- while (($file = readdir($handle)) !== false) {
560
-
561
- if ($file == '.' || $file == '..') {
562
- continue;
563
- }
564
-
565
- $fullPath = str_replace("\\", '/', "{$path}/{$file}");
566
-
567
- // @todo: Don't leave it like this. Convert into an option on the package to not follow symbolic links
568
- // if (is_dir($fullPath) && (is_link($fullPath) == false))
569
- if (is_dir($fullPath)) {
570
-
571
- $add = true;
572
- if (!is_link($fullPath)){
573
- foreach ($this->tmpFilterDirsAll as $key => $val) {
574
- $trimmedFilterDir = rtrim($val, '/');
575
- if ($fullPath == $trimmedFilterDir || strpos($fullPath, $trimmedFilterDir . '/') !== false) {
576
- $add = false;
577
- unset($this->tmpFilterDirsAll[$key]);
578
- break;
579
- }
580
- }
581
- } else{
582
- //Convert relative path of link to absolute path
583
- chdir($fullPath);
584
- $link_path = str_replace("\\", '/', realpath(readlink($fullPath)));
585
- chdir(dirname(__FILE__));
586
-
587
- $link_pos = strpos($fullPath,$link_path);
588
- if($link_pos === 0 && (strlen($link_path) < strlen($fullPath))){
589
- $add = false;
590
- $this->RecursiveLinks[] = $fullPath;
591
- $this->FilterDirsAll[] = $fullPath;
592
- } else {
593
- foreach ($this->tmpFilterDirsAll as $key => $val) {
594
- $trimmedFilterDir = rtrim($val, '/');
595
- if ($fullPath == $trimmedFilterDir || strpos($fullPath, $trimmedFilterDir . '/') !== false) {
596
- $add = false;
597
- unset($this->tmpFilterDirsAll[$key]);
598
- break;
599
- }
600
- }
601
- }
602
- }
603
-
604
- if ($add) {
605
- $this->getFileLists($fullPath);
606
- $this->Dirs[] = $fullPath;
607
- }
608
- } else {
609
- if ( ! (in_array(pathinfo($file, PATHINFO_EXTENSION), $this->FilterExtsAll)
610
- || in_array($fullPath, $this->FilterFilesAll)
611
- || in_array($file, $this->FilterFilesAll))) {
612
- $this->Files[] = $fullPath;
613
- }
614
- }
615
- }
616
- closedir($handle);
617
- }
618
- return $this->Dirs;
619
- }
620
-
621
- /**
622
- * Builds a tree for both file size warnings and name check warnings
623
- * The trees are used to apply filters from the scan screen
624
- *
625
- * @return null
626
- */
627
- private function setTreeFilters()
628
- {
629
- //-------------------------
630
- //SIZE TREE
631
- //BUILD: File Size tree
632
- $dir_group = DUP_Util::array_group_by($this->FilterInfo->Files->Size, "dir");
633
- ksort($dir_group);
634
- foreach ($dir_group as $dir => $files) {
635
- $sum = 0;
636
- foreach ($files as $key => $value) {
637
- $sum += $value['ubytes'];
638
- }
639
-
640
- //Locate core paths, wp-admin, wp-includes, etc.
641
- $iscore = 0;
642
- foreach ($this->wpCorePaths as $core_dir) {
643
- if (strpos(DUP_Util::safePath($dir), DUP_Util::safePath($core_dir)) !== false) {
644
- $iscore = 1;
645
- break;
646
- }
647
- }
648
- // Check root and content exact dir
649
- if (!$iscore) {
650
- if (in_array($dir, $this->wpCoreExactPaths)) {
651
- $iscore = 1;
652
- }
653
- }
654
-
655
- $this->FilterInfo->TreeSize[] = array(
656
- 'size' => DUP_Util::byteSize($sum, 0),
657
- 'dir' => $dir,
658
- 'sdir' => str_replace(duplicator_get_abs_path(), '/', $dir),
659
- 'iscore' => $iscore,
660
- 'files' => $files
661
- );
662
- }
663
-
664
- //-------------------------
665
- //NAME TREE
666
- //BUILD: Warning tree for file names
667
- $dir_group = DUP_Util::array_group_by($this->FilterInfo->Files->Warning, "dir");
668
- ksort($dir_group);
669
- foreach ($dir_group as $dir => $files) {
670
-
671
- //Locate core paths, wp-admin, wp-includes, etc.
672
- $iscore = 0;
673
- foreach ($this->wpCorePaths as $core_dir) {
674
- if (strpos($dir, $core_dir) !== false) {
675
- $iscore = 1;
676
- break;
677
- }
678
- }
679
- // Check root and content exact dir
680
- if (!$iscore) {
681
- if (in_array($dir, $this->wpCoreExactPaths)) {
682
- $iscore = 1;
683
- }
684
- }
685
-
686
- $this->FilterInfo->TreeWarning[] = array(
687
- 'dir' => $dir,
688
- 'sdir' => str_replace(duplicator_get_abs_path(), '/', $dir),
689
- 'iscore' => $iscore,
690
- 'count' => count($files),
691
- 'files' => $files);
692
- }
693
-
694
- //BUILD: Warning tree for dir names
695
- foreach ($this->FilterInfo->Dirs->Warning as $dir) {
696
- $add_dir = true;
697
- foreach ($this->FilterInfo->TreeWarning as $key => $value) {
698
- if ($value['dir'] == $dir) {
699
- $add_dir = false;
700
- break;
701
- }
702
- }
703
- if ($add_dir) {
704
-
705
- //Locate core paths, wp-admin, wp-includes, etc.
706
- $iscore = 0;
707
- foreach ($this->wpCorePaths as $core_dir) {
708
- if (strpos(DUP_Util::safePath($dir), DUP_Util::safePath($core_dir)) !== false) {
709
- $iscore = 1;
710
- break;
711
- }
712
- }
713
- // Check root and content exact dir
714
- if (!$iscore) {
715
- if (in_array($dir, $this->wpCoreExactPaths)) {
716
- $iscore = 1;
717
- }
718
- }
719
-
720
- $this->FilterInfo->TreeWarning[] = array(
721
- 'dir' => $dir,
722
- 'sdir' => str_replace(duplicator_get_abs_path(), '/', $dir),
723
- 'iscore' => $iscore,
724
- 'count' => 0);
725
- }
726
- }
727
-
728
- function _sortDir($a, $b)
729
- {
730
- return strcmp($a["dir"], $b["dir"]);
731
- }
732
- usort($this->FilterInfo->TreeWarning, "_sortDir");
733
- }
734
-
735
- public function getWPConfigFilePath()
736
- {
737
- $wpconfig_filepath = '';
738
- $abs_path = duplicator_get_abs_path();
739
- if (file_exists($abs_path.'/wp-config.php')) {
740
- $wpconfig_filepath = $abs_path.'/wp-config.php';
741
- } elseif (@file_exists(dirname($abs_path).'/wp-config.php') && !@file_exists(dirname($abs_path).'/wp-settings.php')) {
742
- $wpconfig_filepath = dirname($abs_path).'/wp-config.php';
743
- }
744
- return $wpconfig_filepath;
745
- }
746
-
747
- public function isOuterWPContentDir()
748
- {
749
- if (!isset($this->isOuterWPContentDir)) {
750
- $abspath_normalize = wp_normalize_path(ABSPATH);
751
- $wp_content_dir_normalize = wp_normalize_path(WP_CONTENT_DIR);
752
- if (0 !== strpos($wp_content_dir_normalize, $abspath_normalize)) {
753
- $this->isOuterWPContentDir = true;
754
- } else {
755
- $this->isOuterWPContentDir = false;
756
- }
757
- }
758
- return $this->isOuterWPContentDir;
759
- }
760
-
761
- public function wpContentDirNormalizePath()
762
- {
763
- if (!isset($this->wpContentDirNormalizePath)) {
764
- $this->wpContentDirNormalizePath = trailingslashit(wp_normalize_path(WP_CONTENT_DIR));
765
- }
766
- return $this->wpContentDirNormalizePath;
767
- }
768
-
769
- public function getUrl()
770
- {
771
- return DUP_Settings::getSsdirUrl()."/".$this->File;
772
- }
773
-
774
- public function getLocalDirPath($dir, $basePath = '')
775
- {
776
- $isOuterWPContentDir = $this->isOuterWPContentDir();
777
- $wpContentDirNormalizePath = $this->wpContentDirNormalizePath();
778
- $compressDir = rtrim(wp_normalize_path(DUP_Util::safePath($this->PackDir)), '/');
779
-
780
- $dir = trailingslashit(wp_normalize_path($dir));
781
- if ($isOuterWPContentDir && 0 === strpos($dir, $wpContentDirNormalizePath)) {
782
- $newWPContentDirPath = empty($basePath) ? 'wp-content/' : $basePath.'wp-content/';
783
- $emptyDir = ltrim(str_replace($wpContentDirNormalizePath, $newWPContentDirPath, $dir), '/');
784
- } else {
785
- $emptyDir = ltrim($basePath.preg_replace('/^'.preg_quote($compressDir, '/').'(.*)/m', '$1', $dir), '/');
786
- }
787
- return $emptyDir;
788
- }
789
-
790
- public function getLocalFilePath($file, $basePath = '')
791
- {
792
- $isOuterWPContentDir = $this->isOuterWPContentDir();
793
- $wpContentDirNormalizePath = $this->wpContentDirNormalizePath();
794
- $compressDir = rtrim(wp_normalize_path(DUP_Util::safePath($this->PackDir)), '/');
795
-
796
- $file = wp_normalize_path($file);
797
- if ($isOuterWPContentDir && 0 === strpos($file, $wpContentDirNormalizePath)) {
798
- $newWPContentDirPath = empty($basePath) ? 'wp-content/' : $basePath.'wp-content/';
799
- $localFileName = ltrim(str_replace($wpContentDirNormalizePath, $newWPContentDirPath, $file), '/');
800
- } else {
801
- $localFileName = ltrim($basePath.preg_replace('/^'.preg_quote($compressDir, '/').'(.*)/m', '$1', $file), '/');
802
- }
803
- return $localFileName;
804
- }
805
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ use Duplicator\Libs\Snap\SnapIO;
4
+ use Duplicator\Libs\Snap\SnapOS;
5
+ use Duplicator\Libs\Snap\SnapURL;
6
+ use Duplicator\Libs\Snap\SnapUtil;
7
+ use Duplicator\Libs\Snap\SnapWP;
8
+
9
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
10
+ // Exit if accessed directly
11
+ if (!defined('DUPLICATOR_VERSION')) {
12
+ exit;
13
+ }
14
+
15
+ require_once(DUPLICATOR_PLUGIN_PATH . 'classes/package/duparchive/class.pack.archive.duparchive.php');
16
+ require_once(DUPLICATOR_PLUGIN_PATH . 'classes/package/class.pack.archive.file.list.php');
17
+ require_once(DUPLICATOR_PLUGIN_PATH . 'classes/package/class.pack.archive.filters.php');
18
+ require_once(DUPLICATOR_PLUGIN_PATH . 'classes/package/class.pack.archive.zip.php');
19
+ require_once(DUPLICATOR_PLUGIN_PATH . 'lib/forceutf8/Encoding.php');
20
+ /**
21
+ * Class for handling archive setup and build process
22
+ *
23
+ * Standard: PSR-2 (almost)
24
+ * @link http://www.php-fig.org/psr/psr-2
25
+ *
26
+ * @package DUP
27
+ * @subpackage classes/package
28
+ * @copyright (c) 2017, Snapcreek LLC
29
+ * @license https://opensource.org/licenses/GPL-3.0 GNU Public License
30
+ *
31
+ */
32
+ class DUP_Archive
33
+ {
34
+ const DIRS_LIST_FILE_NAME_SUFFIX = '_dirs.txt';
35
+ const FILES_LIST_FILE_NAME_SUFFIX = '_files.txt';
36
+
37
+ //PUBLIC
38
+ public $FilterDirs;
39
+ public $FilterFiles;
40
+ public $FilterExts;
41
+ public $FilterDirsAll = array();
42
+ public $FilterFilesAll = array();
43
+ public $FilterExtsAll = array();
44
+ public $FilterOn;
45
+ public $ExportOnlyDB;
46
+ public $File;
47
+ public $Format;
48
+ public $PackDir = '';
49
+ public $Size = 0;
50
+ public $Dirs = array();
51
+ public $dirsCount = 0;
52
+ public $Files = array();
53
+ public $filesCount = 0;
54
+ /** @var DUP_Archive_Filter_Info */
55
+ public $FilterInfo = null;
56
+ public $RecursiveLinks = array();
57
+ public $file_count = -1;
58
+ /** @var DUP_Package */
59
+ protected $Package;
60
+ private $tmpFilterDirsAll = array();
61
+ private $wpCorePaths = array();
62
+ private $wpCoreExactPaths = array();
63
+ private $relativeFiltersDir = array();
64
+
65
+ /** @var DUP_Archive_File_List */
66
+ private $listFileObj = null;
67
+ /** @var DUP_Archive_File_List */
68
+ private $listDirObj = null;
69
+
70
+ /**
71
+ * Class constructor
72
+ *
73
+ * @param DUP_Package $package
74
+ */
75
+ public function __construct(DUP_Package $package)
76
+ {
77
+ $this->Package = $package;
78
+ $this->FilterOn = false;
79
+ $this->ExportOnlyDB = false;
80
+ $this->FilterInfo = new DUP_Archive_Filter_Info();
81
+ $this->PackDir = $this->getTargetRootPath();
82
+
83
+ $paths = self::getArchiveListPaths();
84
+ $this->wpCorePaths[] = $paths['abs'] . '/wp-admin';
85
+ $this->wpCorePaths[] = $paths['abs'] . '/wp-includes';
86
+ $this->wpCorePaths[] = $paths['wpcontent'] . '/languages';
87
+ $this->wpCoreExactPaths[] = $paths['home'];
88
+ $this->wpCoreExactPaths[] = $paths['abs'];
89
+ $this->wpCoreExactPaths[] = $paths['wpcontent'];
90
+ $this->wpCoreExactPaths[] = $paths['uploads'];
91
+ $this->wpCoreExactPaths[] = $paths['plugins'];
92
+ $this->wpCoreExactPaths[] = $paths['muplugins'];
93
+ $this->wpCoreExactPaths[] = $paths['themes'];
94
+
95
+ $this->relativeFiltersDir = array(DUP_Settings::getSsdirTmpPath(), 'backups-dup-pro');
96
+ }
97
+
98
+ /**
99
+ * Builds the archive based on the archive type
100
+ *
101
+ * @param obj $package The package object that started this process
102
+ *
103
+ * @return null
104
+ */
105
+ public function build($package, $rethrow_exception = false)
106
+ {
107
+ DUP_LOG::trace("b1");
108
+ $this->Package = $package;
109
+ if (!isset($this->PackDir) && !is_dir($this->PackDir)) {
110
+ throw new Exception("The 'PackDir' property must be a valid directory.");
111
+ }
112
+ if (!isset($this->File)) {
113
+ throw new Exception("A 'File' property must be set.");
114
+ }
115
+
116
+ DUP_LOG::trace("b2");
117
+ $completed = false;
118
+
119
+ switch ($this->Format) {
120
+ case 'TAR':
121
+ break;
122
+ case 'TAR-GZIP':
123
+ break;
124
+ case 'DAF':
125
+ $completed = DUP_DupArchive::create($this, $this->Package->BuildProgress, $this->Package);
126
+ $this->Package->Update();
127
+ break;
128
+ default:
129
+ if (class_exists('ZipArchive')) {
130
+ $this->Format = 'ZIP';
131
+ DUP_Zip::create($this, $this->Package->BuildProgress);
132
+ $completed = true;
133
+ }
134
+
135
+ break;
136
+ }
137
+
138
+ DUP_LOG::Trace("Completed build or build thread");
139
+ if ($this->Package->BuildProgress === null) {
140
+ // Zip path
141
+ DUP_LOG::Trace("Completed Zip");
142
+ $storePath = DUP_Settings::getSsdirTmpPath() . "/{$this->File}";
143
+ $this->Size = @filesize($storePath);
144
+ $this->Package->setStatus(DUP_PackageStatus::ARCDONE);
145
+ } elseif ($completed) {
146
+ // Completed DupArchive path
147
+ DUP_LOG::Trace("Completed DupArchive build");
148
+ if ($this->Package->BuildProgress->failed) {
149
+ DUP_LOG::Trace("Error building DupArchive");
150
+ $this->Package->setStatus(DUP_PackageStatus::ERROR);
151
+ } else {
152
+ $filepath = DUP_Settings::getSsdirTmpPath() . "/{$this->File}";
153
+ $this->Size = @filesize($filepath);
154
+ $this->Package->setStatus(DUP_PackageStatus::ARCDONE);
155
+ DUP_LOG::Trace("Done building archive");
156
+ }
157
+ } else {
158
+ DUP_Log::trace("DupArchive chunk done but package not completed yet");
159
+ }
160
+ }
161
+
162
+ /**
163
+ *
164
+ * @return int return DUP_Archive_Build_Mode
165
+ */
166
+ public function getBuildMode()
167
+ {
168
+ switch ($this->Format) {
169
+ case 'TAR':
170
+ break;
171
+ case 'TAR-GZIP':
172
+ break;
173
+ case 'DAF':
174
+ return DUP_Archive_Build_Mode::DupArchive;
175
+ default:
176
+ if (class_exists('ZipArchive')) {
177
+ return DUP_Archive_Build_Mode::ZipArchive;
178
+ } else {
179
+ return DUP_Archive_Build_Mode::Unconfigured;
180
+ }
181
+
182
+ break;
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Initializes the file list handles. Handles are set-up as properties for
188
+ * performance improvement. Otherwise each handle would be opened and closed
189
+ * with each path added.
190
+ */
191
+ private function initFileListHandles()
192
+ {
193
+ DUP_Log::trace('Inif list files');
194
+ if (is_null($this->listDirObj)) {
195
+ $this->listDirObj = new DUP_Archive_File_List(DUP_Settings::getSsdirTmpPath() . '/' . $this->Package->get_dirs_list_filename());
196
+ }
197
+ if (is_null($this->listFileObj)) {
198
+ $this->listFileObj = new DUP_Archive_File_List(DUP_Settings::getSsdirTmpPath() . '/' . $this->Package->get_files_list_filename());
199
+ }
200
+ $this->listDirObj->open(true);
201
+ $this->listFileObj->open(true);
202
+ }
203
+
204
+ /**
205
+ * Closes file and dir list handles
206
+ */
207
+ private function closeFileListHandles()
208
+ {
209
+ $this->listDirObj->close();
210
+ $this->listFileObj->close();
211
+ }
212
+
213
+ /**
214
+ * Builds a list of files and directories to be included in the archive
215
+ *
216
+ * Get the directory size recursively, but don't calc the snapshot directory, exclusion directories
217
+ * @link http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx Windows filename restrictions
218
+ *
219
+ * @return obj Returns a DUP_Archive object
220
+ */
221
+ public function getScannerData()
222
+ {
223
+ $this->initFileListHandles();
224
+ $this->createFilterInfo();
225
+ $rootPath = $this->getTargetRootPath();
226
+ if (strlen($rootPath) == 0) {
227
+ $rootPath = '/';
228
+ }
229
+
230
+ $this->RecursiveLinks = array();
231
+ // If the root directory is a filter then skip it all
232
+ if (in_array($rootPath, $this->FilterDirsAll) || $this->Package->Archive->ExportOnlyDB) {
233
+ $this->Dirs = array();
234
+ $this->dirsCount = 0;
235
+ $this->Files = array();
236
+ $this->filesCount = 0;
237
+ } else {
238
+ $mainSize = 0;
239
+ $mainNodes = 0;
240
+ $pathsToScan = self::getScanPaths();
241
+ foreach ($pathsToScan as $path) {
242
+ DUP_Log::trace('START SCAN PATH: ' . $path);
243
+ $relativePath = SnapIO::getRelativePath($path, $rootPath);
244
+ if (($result = $this->getFileLists($path, $relativePath)) != false) {
245
+ $this->addToDirList($path, $relativePath, $result['size'], $result['nodes'] + 1);
246
+ $mainSize += $result['size'];
247
+ $mainNodes += $result['nodes'] + 1;
248
+ } else {
249
+ DUP_Log::trace('Can\'t scan the folder ' . $rootPath);
250
+ }
251
+ }
252
+
253
+ if (!in_array($rootPath, $pathsToScan)) {
254
+ $this->addToDirList($rootPath, '', $mainSize, $mainNodes + 1);
255
+ }
256
+
257
+ $this->setTreeFilters();
258
+ }
259
+
260
+ $this->closeFileListHandles();
261
+
262
+ $this->FilterDirsAll = array_merge($this->FilterDirsAll, $this->FilterInfo->Dirs->Unreadable);
263
+ $this->FilterFilesAll = array_merge($this->FilterFilesAll, $this->FilterInfo->Files->Unreadable);
264
+ sort($this->FilterDirsAll);
265
+ return $this;
266
+ }
267
+
268
+ /**
269
+ * Save any property of this class through reflection
270
+ *
271
+ * @param $property A valid public property in this class
272
+ * @param $value The value for the new dynamic property
273
+ *
274
+ * @return bool Returns true if the value has changed.
275
+ */
276
+ public function saveActiveItem($package, $property, $value)
277
+ {
278
+ $package = DUP_Package::getActive();
279
+ $reflectionClass = new ReflectionClass($package->Archive);
280
+ $reflectionClass->getProperty($property)->setValue($package->Archive, $value);
281
+ return update_option(DUP_Package::OPT_ACTIVE, $package);
282
+ }
283
+
284
+ /**
285
+ * Properly creates the directory filter list that is used for filtering directories
286
+ *
287
+ * @param string $dirs A semi-colon list of dir paths
288
+ * /path1_/path/;/path1_/path2/;
289
+ *
290
+ * @returns string A cleaned up list of directory filters
291
+ * @return string
292
+ */
293
+ public function parseDirectoryFilter($dirs = "")
294
+ {
295
+ $filters = "";
296
+ $dir_array = array_unique(explode(";", $dirs));
297
+ $clean_array = array();
298
+ foreach ($dir_array as $val) {
299
+ $val = SnapIO::safePathUntrailingslashit(SnapUtil::sanitizeNSCharsNewlineTrim($val));
300
+ if (strlen($val) >= 2 && is_dir($val)) {
301
+ $clean_array[] = $val;
302
+ }
303
+ }
304
+
305
+ if (count($clean_array)) {
306
+ $clean_array = array_unique($clean_array);
307
+ sort($clean_array);
308
+ $filters = implode(';', $clean_array) . ';';
309
+ }
310
+ return $filters;
311
+ }
312
+
313
+ /**
314
+ * Properly creates the file filter list that is used for filtering files
315
+ *
316
+ * @param string $files A semi-colon list of file paths
317
+ * /path1_/path/file1.ext;/path1_/path2/file2.ext;
318
+ *
319
+ * @returns string A cleaned up list of file filters
320
+ * @return string
321
+ */
322
+ public function parseFileFilter($files = "")
323
+ {
324
+ $filters = "";
325
+ $file_array = array_unique(explode(";", $files));
326
+ $clean_array = array();
327
+ foreach ($file_array as $val) {
328
+ $val = SnapIO::safePathUntrailingslashit(SnapUtil::sanitizeNSCharsNewlineTrim($val));
329
+ if (strlen($val) >= 2 && file_exists($val)) {
330
+ $clean_array[] = $val;
331
+ }
332
+ }
333
+
334
+ if (count($clean_array)) {
335
+ $clean_array = array_unique($clean_array);
336
+ sort($clean_array);
337
+ $filters = implode(';', $clean_array) . ';';
338
+ }
339
+ return $filters;
340
+ }
341
+
342
+ /**
343
+ * Properly creates the extension filter list that is used for filtering extensions
344
+ *
345
+ * @param string $dirs A semi-colon list of dir paths
346
+ * .jpg;.zip;.gif;
347
+ *
348
+ * @returns string A cleaned up list of extension filters
349
+ */
350
+ public function parseExtensionFilter($extensions = "")
351
+ {
352
+ $filter_exts = "";
353
+ if (strlen($extensions) >= 1 && $extensions != ";") {
354
+ $filter_exts = str_replace(array(' ', '.'), '', $extensions);
355
+ $filter_exts = str_replace(",", ";", $filter_exts);
356
+ $filter_exts = DUP_Util::appendOnce($extensions, ";");
357
+ }
358
+ return $filter_exts;
359
+ }
360
+
361
+ /**
362
+ * Creates the filter info setup data used for filtering the archive
363
+ *
364
+ * @return null
365
+ */
366
+ private function createFilterInfo()
367
+ {
368
+ //FILTER: INSTANCE ITEMS
369
+ //Add the items generated at create time
370
+ if ($this->FilterOn) {
371
+ $this->FilterInfo->Dirs->Instance = array_map('DUP_Util::safePath', explode(";", $this->FilterDirs, -1));
372
+ $this->FilterInfo->Files->Instance = array_map('DUP_Util::safePath', explode(";", $this->FilterFiles, -1));
373
+ $this->FilterInfo->Exts->Instance = explode(";", $this->FilterExts, -1);
374
+ }
375
+
376
+ //FILTER: CORE ITMES
377
+ //Filters Duplicator free packages & All pro local directories
378
+ $wp_root = duplicator_get_abs_path();
379
+ $upload_dir = wp_upload_dir();
380
+ $upload_dir = isset($upload_dir['basedir']) ? basename($upload_dir['basedir']) : 'uploads';
381
+ $wp_content = str_replace("\\", "/", WP_CONTENT_DIR);
382
+ $wp_content_upload = "{$wp_content}/{$upload_dir}";
383
+ $this->FilterInfo->Dirs->Core = array(
384
+ //WP-ROOT
385
+ DUP_Settings::getSsdirPathLegacy(),
386
+ DUP_Settings::getSsdirPathWpCont(),
387
+ $wp_root . '/.opcache',
388
+ //WP-CONTENT
389
+ $wp_content . '/backups-dup-pro',
390
+ $wp_content . '/ai1wm-backups',
391
+ $wp_content . '/backupwordpress',
392
+ $wp_content . '/content/cache',
393
+ $wp_content . '/contents/cache',
394
+ $wp_content . '/infinitewp/backups',
395
+ $wp_content . '/managewp/backups',
396
+ $wp_content . '/old-cache',
397
+ $wp_content . '/plugins/all-in-one-wp-migration/storage',
398
+ $wp_content . '/updraft',
399
+ $wp_content . '/wishlist-backup',
400
+ $wp_content . '/wfcache',
401
+ $wp_content . '/bps-backup', // BulletProof Security backup folder
402
+ $wp_content . '/cache',
403
+ //WP-CONTENT-UPLOADS
404
+ $wp_content_upload . '/aiowps_backups',
405
+ $wp_content_upload . '/backupbuddy_temp',
406
+ $wp_content_upload . '/backupbuddy_backups',
407
+ $wp_content_upload . '/ithemes-security/backups',
408
+ $wp_content_upload . '/mainwp/backup',
409
+ $wp_content_upload . '/pb_backupbuddy',
410
+ $wp_content_upload . '/snapshots',
411
+ $wp_content_upload . '/sucuri',
412
+ $wp_content_upload . '/wp-clone',
413
+ $wp_content_upload . '/wp_all_backup',
414
+ $wp_content_upload . '/wpbackitup_backups'
415
+ );
416
+ if (class_exists('BackWPup')) {
417
+ $upload_dir = wp_upload_dir(null, false, true);
418
+ $this->FilterInfo->Dirs->Core[] = trailingslashit(str_replace(
419
+ '\\',
420
+ '/',
421
+ $upload_dir['basedir']
422
+ )) . 'backwpup-' . BackWPup::get_plugin_data('hash') . '-backups/';
423
+ $backwpup_cfg_logfolder = get_site_option('backwpup_cfg_logfolder');
424
+ if (false !== $backwpup_cfg_logfolder) {
425
+ $this->FilterInfo->Dirs->Core[] = $wp_content . '/' . $backwpup_cfg_logfolder;
426
+ }
427
+ }
428
+ if ($GLOBALS['DUPLICATOR_GLOBAL_FILE_FILTERS_ON']) {
429
+ $duplicator_global_file_filters = apply_filters('duplicator_global_file_filters', $GLOBALS['DUPLICATOR_GLOBAL_FILE_FILTERS']);
430
+ $this->FilterInfo->Files->Global = $duplicator_global_file_filters;
431
+ }
432
+
433
+ $this->FilterDirsAll = array_merge($this->FilterInfo->Dirs->Instance, $this->FilterInfo->Dirs->Core);
434
+ $this->FilterExtsAll = array_merge($this->FilterInfo->Exts->Instance, $this->FilterInfo->Exts->Core);
435
+ $this->FilterFilesAll = array_merge($this->FilterInfo->Files->Instance, $this->FilterInfo->Files->Global);
436
+ $abs_path = duplicator_get_abs_path();
437
+ $this->FilterFilesAll[] = $abs_path . '/.htaccess';
438
+ $this->FilterFilesAll[] = $abs_path . '/web.config';
439
+ $this->FilterFilesAll[] = $abs_path . '/wp-config.php';
440
+ $this->tmpFilterDirsAll = $this->FilterDirsAll;
441
+ //PHP 5 on windows decode patch
442
+ if (!DUP_Util::$PHP7_plus && DUP_Util::isWindows()) {
443
+ foreach ($this->tmpFilterDirsAll as $key => $value) {
444
+ if (preg_match('/[^\x20-\x7f]/', $value)) {
445
+ $this->tmpFilterDirsAll[$key] = utf8_decode($value);
446
+ }
447
+ }
448
+ }
449
+ }
450
+
451
+ /**
452
+ * Recursive function to get all directories in a wp install
453
+ *
454
+ * @notes:
455
+ * Older PHP logic which is more stable on older version of PHP
456
+ * NOTE RecursiveIteratorIterator is problematic on some systems issues include:
457
+ * - error 'too many files open' for recursion
458
+ * - $file->getExtension() is not reliable as it silently fails at least in php 5.2.17
459
+ * - issues with when a file has a permission such as 705 and trying to get info (had to fallback to path-info)
460
+ * - basic conclusion wait on the SPL libs until after php 5.4 is a requirments
461
+ * - tight recursive loop use caution for speed
462
+ *
463
+ * @return array Returns an array of directories to include in the archive
464
+ */
465
+ private function getFileLists($path, $relativePath)
466
+ {
467
+ if (($handle = opendir((strlen($path) === 0 ? '/' : $path))) === false) {
468
+ DUP_Log::trace('Can\'t open dir: ' . $path);
469
+ return false;
470
+ }
471
+
472
+ $result = array(
473
+ 'size' => 0,
474
+ 'nodes' => 1
475
+ );
476
+ $trimmedRelativePath = ltrim($relativePath . '/', '/');
477
+ while (($currentName = readdir($handle)) !== false) {
478
+ if ($currentName == '.' || $currentName == '..') {
479
+ continue;
480
+ }
481
+ $currentPath = $path . '/' . $currentName;
482
+ //DUP_Log::trace(' ANALIZE PATH: '.$currentPath);
483
+
484
+ if (is_dir($currentPath)) {
485
+ DUP_Log::trace(' Scan dir: ' . $currentPath);
486
+ $add = true;
487
+ if (is_link($currentPath)) {
488
+ //Get real path of link
489
+ //trailing slash is essential!
490
+ $realPath = SnapIO::safePathTrailingslashit($currentPath, true);
491
+ //if $currentPath starts with $realPath and is link
492
+ //=> $currentPath is located in $realPath and points back to it
493
+ if (strpos($currentPath, $realPath) === 0) {
494
+ $this->RecursiveLinks[] = $currentPath;
495
+ continue;
496
+ }
497
+ }
498
+
499
+ if (in_array($currentName, $this->relativeFiltersDir)) {
500
+ $add = false;
501
+ } else {
502
+ foreach ($this->tmpFilterDirsAll as $filteredDir) {
503
+ if (SnapIO::isChildPath($currentPath, $filteredDir)) {
504
+ $add = false;
505
+ break;
506
+ }
507
+ }
508
+ }
509
+
510
+ if ($add) {
511
+ $childResult = $this->getFileLists($currentPath, $trimmedRelativePath . $currentName);
512
+ $result['size'] += $childResult['size'];
513
+ $result['nodes'] += $childResult['nodes'];
514
+ $this->addToDirList($currentPath, $trimmedRelativePath . $currentName, $childResult['size'], $childResult['nodes']);
515
+ }
516
+ } else {
517
+ // Note: The last clause is present to perform just a filename check
518
+ if (
519
+ !(in_array(pathinfo($currentName, PATHINFO_EXTENSION), $this->FilterExtsAll) ||
520
+ in_array($currentPath, $this->FilterFilesAll) ||
521
+ in_array($currentName, $this->FilterFilesAll))
522
+ ) {
523
+ $fileSize = (int) @filesize($currentPath);
524
+ $result['size'] += $fileSize;
525
+ $result['nodes'] += 1;
526
+ $this->addToFileList($currentPath, $trimmedRelativePath . $currentName, $fileSize);
527
+ }
528
+ }
529
+ }
530
+ closedir($handle);
531
+ return $result;
532
+ }
533
+
534
+ public static function isValidEncoding($string)
535
+ {
536
+ return !preg_match('/([\/\*\?><\:\\\\\|]|[^\x20-\x7f])/', $string);
537
+ }
538
+
539
+ private function addToDirList($dirPath, $relativePath, $size, $nodes)
540
+ {
541
+ //Dir is not readble remove and flag
542
+ if (!SnapOS::isWindows() && !is_readable($dirPath)) {
543
+ $this->FilterInfo->Dirs->Unreadable[] = $dirPath;
544
+ return;
545
+ }
546
+
547
+ // is relative path is empty is the root path
548
+ if (strlen($relativePath) && !DUP_Settings::Get('skip_archive_scan')) {
549
+ $name = basename($dirPath);
550
+ // Locate invalid directories and warn
551
+ $invalid_encoding = !self::isValidEncoding($name);
552
+ if ($invalid_encoding) {
553
+ $dirPath = DUP_Encoding::toUTF8($dirPath);
554
+ }
555
+ $trimmedName = trim($name);
556
+ $invalid_name = (
557
+ $invalid_encoding ||
558
+ (defined('PHP_MAXPATHLEN') && strlen($dirPath) > PHP_MAXPATHLEN) ||
559
+ strlen($trimmedName) === 0 ||
560
+ $name[strlen($name) - 1] === '.'
561
+ );
562
+ if ($invalid_name) {
563
+ $this->FilterInfo->Dirs->Warning[] = $dirPath;
564
+ }
565
+
566
+ if ($size > DUPLICATOR_SCAN_WARN_DIR_SIZE) {
567
+ $dirData = array(
568
+ 'ubytes' => $size,
569
+ 'bytes' => DUP_Util::byteSize($size, 0),
570
+ 'nodes' => $nodes,
571
+ 'name' => $name,
572
+ 'dir' => pathinfo($dirPath, PATHINFO_DIRNAME),
573
+ 'path' => $dirPath
574
+ );
575
+ $this->FilterInfo->Dirs->Size[] = $dirData;
576
+ DUP_Log::traceObject('ADD DIR SIZE:', $dirData);
577
+ }
578
+
579
+ //Check for other WordPress installs
580
+ if (!self::isCurrentWordpressInstallPath($dirPath) && SnapWP::isWpHomeFolder($dirPath)) {
581
+ $this->FilterInfo->Dirs->AddonSites[] = $dirPath;
582
+ }
583
+ }
584
+
585
+ $this->listDirObj->addEntry($relativePath, $size, $nodes);
586
+ $this->Dirs[] = $dirPath;
587
+ $this->dirsCount ++;
588
+ }
589
+
590
+ private function addToFileList($filePath, $relativePath, $fileSize)
591
+ {
592
+ if (!is_readable($filePath)) {
593
+ $this->FilterInfo->Files->Unreadable[] = $filePath;
594
+ return;
595
+ }
596
+
597
+ if (!DUP_Settings::Get('skip_archive_scan')) {
598
+ $fileName = basename($filePath);
599
+ //File Warnings
600
+ $invalid_encoding = !self::isValidEncoding($fileName);
601
+ $trimmed_name = trim($fileName);
602
+ $invalid_name = $invalid_encoding || (defined('PHP_MAXPATHLEN') && strlen($filePath) > PHP_MAXPATHLEN) || strlen($trimmed_name) === 0;
603
+ if ($invalid_encoding) {
604
+ $filePath = DUP_Encoding::toUTF8($filePath);
605
+ $fileName = DUP_Encoding::toUTF8($fileName);
606
+ }
607
+
608
+ if ($invalid_name) {
609
+ $this->FilterInfo->Files->Warning[] = array(
610
+ 'name' => $fileName,
611
+ 'dir' => pathinfo($filePath, PATHINFO_DIRNAME),
612
+ 'path' => $filePath
613
+ );
614
+ }
615
+
616
+ if ($fileSize > DUPLICATOR_SCAN_WARNFILESIZE) {
617
+ $this->FilterInfo->Files->Size[] = array(
618
+ 'ubytes' => $fileSize,
619
+ 'bytes' => DUP_Util::byteSize($fileSize, 0),
620
+ 'nodes' => 1,
621
+ 'name' => $fileName,
622
+ 'dir' => pathinfo($filePath, PATHINFO_DIRNAME),
623
+ 'path' => $filePath
624
+ );
625
+ }
626
+ }
627
+
628
+
629
+ $this->Size += $fileSize;
630
+ $this->listFileObj->addEntry($relativePath, $fileSize, 1);
631
+ $this->Files[] = $filePath;
632
+ $this->filesCount++;
633
+ }
634
+
635
+ /**
636
+ * Builds a tree for both file size warnings and name check warnings
637
+ * The trees are used to apply filters from the scan screen
638
+ *
639
+ * @return null
640
+ */
641
+ private function setTreeFilters()
642
+ {
643
+ //-------------------------
644
+ //SIZE TREE
645
+ //BUILD: File Size tree
646
+ $dir_group = DUP_Util::array_group_by($this->FilterInfo->Files->Size, "dir");
647
+ ksort($dir_group);
648
+ foreach ($dir_group as $dir => $files) {
649
+ $sum = 0;
650
+ foreach ($files as $key => $value) {
651
+ $sum += $value['ubytes'];
652
+ }
653
+
654
+ //Locate core paths, wp-admin, wp-includes, etc.
655
+ $iscore = 0;
656
+ foreach ($this->wpCorePaths as $core_dir) {
657
+ if (strpos(DUP_Util::safePath($dir), DUP_Util::safePath($core_dir)) !== false) {
658
+ $iscore = 1;
659
+ break;
660
+ }
661
+ }
662
+ // Check root and content exact dir
663
+ if (!$iscore) {
664
+ if (in_array($dir, $this->wpCoreExactPaths)) {
665
+ $iscore = 1;
666
+ }
667
+ }
668
+
669
+ $this->FilterInfo->TreeSize[] = array(
670
+ 'size' => DUP_Util::byteSize($sum, 0),
671
+ 'dir' => $dir,
672
+ 'sdir' => str_replace(duplicator_get_abs_path(), '/', $dir),
673
+ 'iscore' => $iscore,
674
+ 'files' => $files
675
+ );
676
+ }
677
+
678
+ //-------------------------
679
+ //NAME TREE
680
+ //BUILD: Warning tree for file names
681
+ $dir_group = DUP_Util::array_group_by($this->FilterInfo->Files->Warning, "dir");
682
+ ksort($dir_group);
683
+ foreach ($dir_group as $dir => $files) {
684
+ //Locate core paths, wp-admin, wp-includes, etc.
685
+ $iscore = 0;
686
+ foreach ($this->wpCorePaths as $core_dir) {
687
+ if (strpos($dir, $core_dir) !== false) {
688
+ $iscore = 1;
689
+ break;
690
+ }
691
+ }
692
+ // Check root and content exact dir
693
+ if (!$iscore) {
694
+ if (in_array($dir, $this->wpCoreExactPaths)) {
695
+ $iscore = 1;
696
+ }
697
+ }
698
+
699
+ $this->FilterInfo->TreeWarning[] = array(
700
+ 'dir' => $dir,
701
+ 'sdir' => str_replace(duplicator_get_abs_path(), '/', $dir),
702
+ 'iscore' => $iscore,
703
+ 'count' => count($files),
704
+ 'files' => $files);
705
+ }
706
+
707
+ //BUILD: Warning tree for dir names
708
+ foreach ($this->FilterInfo->Dirs->Warning as $dir) {
709
+ $add_dir = true;
710
+ foreach ($this->FilterInfo->TreeWarning as $key => $value) {
711
+ if ($value['dir'] == $dir) {
712
+ $add_dir = false;
713
+ break;
714
+ }
715
+ }
716
+ if ($add_dir) {
717
+ //Locate core paths, wp-admin, wp-includes, etc.
718
+ $iscore = 0;
719
+ foreach ($this->wpCorePaths as $core_dir) {
720
+ if (strpos(DUP_Util::safePath($dir), DUP_Util::safePath($core_dir)) !== false) {
721
+ $iscore = 1;
722
+ break;
723
+ }
724
+ }
725
+ // Check root and content exact dir
726
+ if (!$iscore) {
727
+ if (in_array($dir, $this->wpCoreExactPaths)) {
728
+ $iscore = 1;
729
+ }
730
+ }
731
+
732
+ $this->FilterInfo->TreeWarning[] = array(
733
+ 'dir' => $dir,
734
+ 'sdir' => str_replace(duplicator_get_abs_path(), '/', $dir),
735
+ 'iscore' => $iscore,
736
+ 'count' => 0);
737
+ }
738
+ }
739
+
740
+ function _sortDir($a, $b)
741
+ {
742
+ return strcmp($a["dir"], $b["dir"]);
743
+ }
744
+ usort($this->FilterInfo->TreeWarning, "_sortDir");
745
+ }
746
+
747
+ public function getWPConfigFilePath()
748
+ {
749
+ $wpconfig_filepath = '';
750
+ $abs_path = duplicator_get_abs_path();
751
+ if (file_exists($abs_path . '/wp-config.php')) {
752
+ $wpconfig_filepath = $abs_path . '/wp-config.php';
753
+ } elseif (@file_exists(dirname($abs_path) . '/wp-config.php') && !@file_exists(dirname($abs_path) . '/wp-settings.php')) {
754
+ $wpconfig_filepath = dirname($abs_path) . '/wp-config.php';
755
+ }
756
+ return $wpconfig_filepath;
757
+ }
758
+
759
+ /**
760
+ * get the main target root path to make archive
761
+ *
762
+ * @staticvar type $targerRoorPath
763
+ * @return string
764
+ */
765
+ public static function getTargetRootPath()
766
+ {
767
+ static $targetRoorPath = null;
768
+ if (is_null($targetRoorPath)) {
769
+ $paths = self::getArchiveListPaths();
770
+ unset($paths['wpconfig']);
771
+ $targetRoorPath = SnapIO::getCommonPath($paths);
772
+ }
773
+ return $targetRoorPath;
774
+ }
775
+
776
+ /**
777
+ * @param null|string $urlKey if set will only return the url identified by that key
778
+ * @return array|string|bool
779
+ */
780
+ public static function getOriginalUrls($urlKey = null)
781
+ {
782
+ static $origUrls = null;
783
+ if (is_null($origUrls)) {
784
+ $restoreMultisite = false;
785
+ if (is_multisite() && SnapWP::getMainSiteId() !== get_current_blog_id()) {
786
+ $restoreMultisite = true;
787
+ restore_current_blog();
788
+ switch_to_blog(SnapWP::getMainSiteId());
789
+ }
790
+
791
+ $updDirs = wp_upload_dir(null, false, true);
792
+ if (($wpConfigDir = SnapWP::getWPConfigPath()) !== false) {
793
+ $wpConfigDir = dirname($wpConfigDir);
794
+ }
795
+
796
+ $homeUrl = home_url();
797
+ $homeParse = SnapURL::parseUrl(home_url());
798
+ $absParse = SnapURL::parseUrl(site_url());
799
+ if ($homeParse['host'] === $absParse['host'] && SnapIO::isChildPath($homeParse['path'], $absParse['path'], false, false)) {
800
+ $homeParse['path'] = $absParse['path'];
801
+ $homeUrl = SnapURL::buildUrl($homeParse);
802
+ }
803
+
804
+ $origUrls = array(
805
+ 'home' => $homeUrl,
806
+ 'abs' => site_url(),
807
+ 'wpcontent' => content_url(),
808
+ 'uploads' => $updDirs['baseurl'],
809
+ 'plugins' => plugins_url(),
810
+ 'muplugins' => WPMU_PLUGIN_URL,
811
+ 'themes' => get_theme_root_uri()
812
+ );
813
+ if ($restoreMultisite) {
814
+ restore_current_blog();
815
+ }
816
+ }
817
+
818
+ if ($urlKey === null) {
819
+ return $origUrls;
820
+ }
821
+
822
+ if (isset($origUrls[$urlKey])) {
823
+ return $origUrls[$urlKey];
824
+ } else {
825
+ return false;
826
+ }
827
+ }
828
+
829
+ /**
830
+ * return all paths to scan
831
+ *
832
+ * @return string[]
833
+ */
834
+ public static function getScanPaths()
835
+ {
836
+ static $scanPaths = null;
837
+ if (is_null($scanPaths)) {
838
+ $paths = self::getArchiveListPaths();
839
+ // The folder that contains wp-config must not be scanned in full but only added
840
+ unset($paths['wpconfig']);
841
+ $scanPaths = array(
842
+ $paths['home']
843
+ );
844
+ unset($paths['home']);
845
+ foreach ($paths as $path) {
846
+ $addPath = true;
847
+ foreach ($scanPaths as $resPath) {
848
+ if (SnapIO::getRelativePath($path, $resPath) !== false) {
849
+ $addPath = false;
850
+ break;
851
+ }
852
+ }
853
+ if ($addPath) {
854
+ $scanPaths[] = $path;
855
+ }
856
+ }
857
+ $scanPaths = array_values(array_unique($scanPaths));
858
+ }
859
+ return $scanPaths;
860
+ }
861
+
862
+ /**
863
+ * return the wordpress original dir paths
864
+ *
865
+ * @staticvar string[] $origPaths if is null retur the array of paths or the single key path
866
+ * @param string|null $pathKey
867
+ *
868
+ * @return string[]|string|bool return false if key doesn\'t exist
869
+ */
870
+ public static function getOriginalPaths($pathKey = null)
871
+ {
872
+ static $origPaths = null;
873
+ if (is_null($origPaths)) {
874
+ $restoreMultisite = false;
875
+ if (is_multisite() && SnapWP::getMainSiteId() !== get_current_blog_id()) {
876
+ $restoreMultisite = true;
877
+ restore_current_blog();
878
+ switch_to_blog(SnapWP::getMainSiteId());
879
+ }
880
+
881
+ $updDirs = wp_upload_dir(null, false, true);
882
+ // fix for old network installation
883
+ $baseDir = preg_replace('/^(.+\/blogs\.dir)\/\d+\/files$/', '$1', $updDirs['basedir']);
884
+ if (($wpConfigDir = SnapWP::getWPConfigPath()) !== false) {
885
+ $wpConfigDir = dirname($wpConfigDir);
886
+ }
887
+ $origPaths = array(
888
+ 'home' => SnapWP::getHomePath(),
889
+ 'abs' => ABSPATH,
890
+ 'wpconfig' => $wpConfigDir,
891
+ 'wpcontent' => WP_CONTENT_DIR,
892
+ 'uploads' => $baseDir,
893
+ 'plugins' => WP_PLUGIN_DIR,
894
+ 'muplugins' => WPMU_PLUGIN_DIR,
895
+ 'themes' => get_theme_root()
896
+ );
897
+ if ($restoreMultisite) {
898
+ restore_current_blog();
899
+ }
900
+ }
901
+
902
+ if (!empty($pathKey)) {
903
+ if (array_key_exists($pathKey, $origPaths)) {
904
+ return $origPaths[$pathKey];
905
+ } else {
906
+ return false;
907
+ }
908
+ } else {
909
+ return $origPaths;
910
+ }
911
+ }
912
+
913
+ /**
914
+ * return the wordpress original dir paths
915
+ *
916
+ * @staticvar string[] $paths if is null retur the array of paths or the single key path
917
+ * @param string|null $pathKey
918
+ *
919
+ * @return string[]|string|bool return false if key doesn\'t exist
920
+ */
921
+ public static function getArchiveListPaths($pathKey = null)
922
+ {
923
+ static $archivePaths = null;
924
+ if (is_null($archivePaths)) {
925
+ $archivePaths = array();
926
+ $originalPaths = self::getOriginalPaths();
927
+
928
+ $archivePaths = array(
929
+ 'home' => SnapIO::safePathUntrailingslashit($originalPaths['home'], true)
930
+ );
931
+ unset($originalPaths['home']);
932
+
933
+ foreach ($originalPaths as $key => $originalPath) {
934
+ $path = SnapIO::safePathUntrailingslashit($originalPath, false);
935
+ $realPath = SnapIO::safePathUntrailingslashit($originalPath, true);
936
+
937
+ if ($path == $realPath) {
938
+ $archivePaths[$key] = $path;
939
+ } elseif (
940
+ !SnapIO::isChildPath($realPath, $archivePaths['home']) &&
941
+ SnapIO::isChildPath($path, $archivePaths['home'])
942
+ ) {
943
+ $archivePaths[$key] = $path;
944
+ } else {
945
+ $archivePaths[$key] = $realPath;
946
+ }
947
+ }
948
+ }
949
+
950
+ if (!empty($pathKey)) {
951
+ if (array_key_exists($pathKey, $archivePaths)) {
952
+ return $archivePaths[$pathKey];
953
+ } else {
954
+ return false;
955
+ }
956
+ } else {
957
+ return $archivePaths;
958
+ }
959
+ }
960
+
961
+ /**
962
+ * return true if path is child of duplicator backup path
963
+ *
964
+ * @param string $path
965
+ * @return boolean
966
+ */
967
+ public static function isBackupPathChild($path)
968
+ {
969
+ return (preg_match('/[\/]' . preg_quote(DUP_Settings::getSsdirTmpPath(), '/') . '[\/][^\/]+$/', $path) === 1);
970
+ }
971
+
972
+ /**
973
+ *
974
+ * @param string $path
975
+ *
976
+ * @return bool return true if path is a path of current wordpress installation
977
+ */
978
+ public static function isCurrentWordpressInstallPath($path)
979
+ {
980
+ static $currentWpPaths = null;
981
+
982
+ if (is_null($currentWpPaths)) {
983
+ $currentWpPaths = array_merge(self::getOriginalPaths(), self::getArchiveListPaths());
984
+ $currentWpPaths = array_map('trailingslashit', $currentWpPaths);
985
+ $currentWpPaths = array_values(array_unique($currentWpPaths));
986
+ }
987
+ return in_array(trailingslashit($path), $currentWpPaths);
988
+ }
989
+
990
+ public function wpContentDirNormalizePath()
991
+ {
992
+ if (!isset($this->wpContentDirNormalizePath)) {
993
+ $this->wpContentDirNormalizePath = trailingslashit(wp_normalize_path(WP_CONTENT_DIR));
994
+ }
995
+ return $this->wpContentDirNormalizePath;
996
+ }
997
+
998
+ public function getUrl()
999
+ {
1000
+ return DUP_Settings::getSsdirUrl() . "/" . $this->File;
1001
+ }
1002
+
1003
+ public function getLocalDirPath($dir, $basePath = '')
1004
+ {
1005
+ $safeDir = SnapIO::safePathUntrailingslashit($dir);
1006
+ return ltrim($basePath . preg_replace('/^' . preg_quote($this->PackDir, '/') . '(.*)/m', '$1', $safeDir), '/');
1007
+ }
1008
+
1009
+ public function getLocalFilePath($file, $basePath = '')
1010
+ {
1011
+ $safeFile = SnapIO::safePathUntrailingslashit($file);
1012
+ return ltrim($basePath . preg_replace('/^' . preg_quote($this->PackDir, '/') . '(.*)/m', '$1', $safeFile), '/');
1013
+ }
1014
+ }
classes/package/class.pack.archive.zip.php CHANGED
@@ -1,9 +1,14 @@
1
  <?php
 
 
 
2
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
  // Exit if accessed directly
4
- if (! defined('DUPLICATOR_VERSION')) exit;
 
 
5
 
6
- require_once (DUPLICATOR_PLUGIN_PATH.'classes/package/class.pack.archive.php');
7
 
8
  /**
9
  * Creates a zip file using the built in PHP ZipArchive class
@@ -32,55 +37,55 @@ class DUP_Zip extends DUP_Archive
32
  $package_zip_flush = DUP_Settings::Get('package_zip_flush');
33
 
34
  self::$compressDir = rtrim(wp_normalize_path(DUP_Util::safePath($archive->PackDir)), '/');
35
- self::$sqlPath = DUP_Settings::getSsdirTmpPath()."/{$archive->Package->Database->File}";
36
- self::$zipPath = DUP_Settings::getSsdirTmpPath()."/{$archive->File}";
37
  self::$zipArchive = new ZipArchive();
38
  self::$networkFlush = empty($package_zip_flush) ? false : $package_zip_flush;
39
 
40
- $filterDirs = empty($archive->FilterDirs) ? 'not set' : $archive->FilterDirs;
41
- $filterExts = empty($archive->FilterExts) ? 'not set' : $archive->FilterExts;
42
- $filterFiles = empty($archive->FilterFiles) ? 'not set' : $archive->FilterFiles;
43
- $filterOn = ($archive->FilterOn) ? 'ON' : 'OFF';
44
  $filterDirsFormat = rtrim(str_replace(';', "\n\t", $filterDirs));
45
- $filterFilesFormat = rtrim(str_replace(';', "\n\t", $filterFiles));
46
- $lastDirSuccess = self::$compressDir;
47
 
48
  //LOAD SCAN REPORT
49
- $json = file_get_contents(DUP_Settings::getSsdirTmpPath()."/{$archive->Package->NameHash}_scan.json");
50
  self::$scanReport = json_decode($json);
51
 
52
  DUP_Log::Info("\n********************************************************************************");
53
  DUP_Log::Info("ARCHIVE (ZIP):");
54
  DUP_Log::Info("********************************************************************************");
55
- $isZipOpen = (self::$zipArchive->open(self::$zipPath, ZIPARCHIVE::CREATE) === TRUE);
56
  if (!$isZipOpen) {
57
  $error_message = "Cannot open zip file with PHP ZipArchive.";
58
  $buildProgress->set_failed($error_message);
59
- DUP_Log::error($error_message, "Path location [".self::$zipPath."]", Dup_ErrorBehavior::LogOnly);
60
  $archive->Package->setStatus(DUP_PackageStatus::ERROR);
61
  return;
62
  }
63
- DUP_Log::Info("ARCHIVE DIR: ".self::$compressDir);
64
- DUP_Log::Info("ARCHIVE FILE: ".basename(self::$zipPath));
65
  DUP_Log::Info("FILTERS: *{$filterOn}*");
66
  DUP_Log::Info("DIRS:\n\t{$filterDirsFormat}");
67
- DUP_Log::Info("FILES:\n\t{$filterFilesFormat}");
68
  DUP_Log::Info("EXTS: {$filterExts}");
69
 
70
  DUP_Log::Info("----------------------------------------");
71
  DUP_Log::Info("COMPRESSING");
72
- DUP_Log::Info("SIZE:\t".self::$scanReport->ARC->Size);
73
- DUP_Log::Info("STATS:\tDirs ".self::$scanReport->ARC->DirCount." | Files ".self::$scanReport->ARC->FileCount);
74
 
75
  //ADD SQL
76
  $sql_ark_file_path = $archive->Package->getSqlArkFilePath();
77
- $isSQLInZip = self::$zipArchive->addFile(self::$sqlPath, $sql_ark_file_path);
78
 
79
  if ($isSQLInZip) {
80
- DUP_Log::Info("SQL ADDED: ".basename(self::$sqlPath));
81
  } else {
82
  $error_message = "Unable to add database.sql to archive.";
83
- DUP_Log::error($error_message, "SQL File Path [".self::$sqlPath."]", Dup_ErrorBehavior::LogOnly);
84
  $buildProgress->set_failed($error_message);
85
  $archive->Package->setStatus(DUP_PackageStatus::ERROR);
86
  return;
@@ -100,7 +105,7 @@ class DUP_Zip extends DUP_Archive
100
  //Don't warn when dirtory is the root path
101
  if (strcmp($dir, rtrim(self::$compressDir, '/')) != 0) {
102
  $dir_path = strlen($dir) ? "[{$dir}]" : "[Read Error] - last successful read was: [{$lastDirSuccess}]";
103
- $info .= "DIR: {$dir_path}\n";
104
  }
105
  }
106
  }
@@ -114,20 +119,25 @@ class DUP_Zip extends DUP_Archive
114
  /**
115
  * count update for integrity check
116
  */
117
- $sumItems = (self::$countDirs + self::$countFiles);
118
 
119
  /* ZIP FILES: Network Flush
120
  * This allows the process to not timeout on fcgi
121
  * setups that need a response every X seconds */
122
  $totalFileCount = count(self::$scanReport->ARC->Files);
123
- $info = '';
124
  if (self::$networkFlush) {
125
  foreach (self::$scanReport->ARC->Files as $file) {
126
- $file_size = @filesize($file);
127
  $localFileName = $archive->getLocalFilePath($file);
128
 
129
  if (is_readable($file)) {
130
- if (defined('DUPLICATOR_ZIP_ARCHIVE_ADD_FROM_STR') && DUPLICATOR_ZIP_ARCHIVE_ADD_FROM_STR && $file_size < DUP_Constants::ZIP_STRING_LIMIT && self::$zipArchive->addFromString($localFileName, file_get_contents($file))) {
 
 
 
 
 
131
  Dup_Log::Info("Adding {$file} to zip");
132
  self::$limitItems++;
133
  self::$countFiles++;
@@ -150,21 +160,25 @@ class DUP_Zip extends DUP_Archive
150
  DUP_Log::Info("Items archived [{$sumItems}] flushing response.");
151
  }
152
 
153
- if(self::$countFiles % 500 == 0) {
154
  // Every so many files update the status so the UI can display
155
- $archive->Package->Status = DupLiteSnapLibUtil::getWorkPercent(DUP_PackageStatus::ARCSTART, DUP_PackageStatus::ARCVALIDATION, $totalFileCount, self::$countFiles);
156
  $archive->Package->update();
157
  }
158
  }
159
- }
160
- //Normal
161
- else {
162
  foreach (self::$scanReport->ARC->Files as $file) {
163
- $file_size = @filesize($file);
164
  $localFileName = $archive->getLocalFilePath($file);
165
 
166
  if (is_readable($file)) {
167
- if (defined('DUPLICATOR_ZIP_ARCHIVE_ADD_FROM_STR') && DUPLICATOR_ZIP_ARCHIVE_ADD_FROM_STR && $file_size < DUP_Constants::ZIP_STRING_LIMIT && self::$zipArchive->addFromString($localFileName, file_get_contents($file))) {
 
 
 
 
 
168
  self::$countFiles++;
169
  } elseif (self::$zipArchive->addFile($file, $localFileName)) {
170
  self::$countFiles++;
@@ -175,9 +189,9 @@ class DUP_Zip extends DUP_Archive
175
  $info .= "FILE: [{$file}]\n";
176
  }
177
 
178
- if(self::$countFiles % 500 == 0) {
179
  // Every so many files update the status so the UI can display
180
- $archive->Package->Status = DupLiteSnapLibUtil::getWorkPercent(DUP_PackageStatus::ARCSTART, DUP_PackageStatus::ARCVALIDATION, $totalFileCount, self::$countFiles);
181
  $archive->Package->update();
182
  }
183
  }
@@ -196,22 +210,24 @@ class DUP_Zip extends DUP_Archive
196
  * count update for integrity check
197
  */
198
  $archive->file_count = self::$countDirs + self::$countFiles;
199
- DUP_Log::Info("FILE ADDED TO ZIP: ".$archive->file_count);
200
 
201
 
202
  //--------------------------------
203
  //LOG FINAL RESULTS
204
  DUP_Util::fcgiFlush();
205
  $zipCloseResult = self::$zipArchive->close();
206
- if($zipCloseResult) {
207
  DUP_Log::Info("COMPRESSION RESULT: '{$zipCloseResult}'");
208
  } else {
209
  $error_message = "ZipArchive close failure.";
210
- DUP_Log::error($error_message,
211
- "The ZipArchive engine is having issues zipping up the files on this server. For more details visit the FAQ\n"
212
- . "I'm getting a ZipArchive close failure when building. How can I resolve this?\n"
213
- . "[https://snapcreek.com/duplicator/docs/faqs-tech/#faq-package-165-q]",
214
- Dup_ErrorBehavior::LogOnly);
 
 
215
  $buildProgress->set_failed($error_message);
216
  $archive->Package->setStatus(DUP_PackageStatus::ERROR);
217
  return;
@@ -221,12 +237,9 @@ class DUP_Zip extends DUP_Archive
221
  $timerAllSum = DUP_Util::elapsedTime($timerAllEnd, $timerAllStart);
222
 
223
  self::$zipFileSize = @filesize(self::$zipPath);
224
- DUP_Log::Info("COMPRESSED SIZE: ".DUP_Util::byteSize(self::$zipFileSize));
225
  DUP_Log::Info("ARCHIVE RUNTIME: {$timerAllSum}");
226
- DUP_Log::Info("MEMORY STACK: ".DUP_Server::getPHPMemory());
227
-
228
-
229
-
230
  } catch (Exception $e) {
231
  $error_message = "Runtime error in class.pack.archive.zip.php constructor.";
232
  DUP_Log::error($error_message, "Exception: {$e}", Dup_ErrorBehavior::LogOnly);
@@ -235,4 +248,4 @@ class DUP_Zip extends DUP_Archive
235
  return;
236
  }
237
  }
238
- }
1
  <?php
2
+
3
+ use Duplicator\Libs\Snap\SnapUtil;
4
+
5
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
6
  // Exit if accessed directly
7
+ if (! defined('DUPLICATOR_VERSION')) {
8
+ exit;
9
+ }
10
 
11
+ require_once(DUPLICATOR_PLUGIN_PATH . 'classes/package/class.pack.archive.php');
12
 
13
  /**
14
  * Creates a zip file using the built in PHP ZipArchive class
37
  $package_zip_flush = DUP_Settings::Get('package_zip_flush');
38
 
39
  self::$compressDir = rtrim(wp_normalize_path(DUP_Util::safePath($archive->PackDir)), '/');
40
+ self::$sqlPath = DUP_Settings::getSsdirTmpPath() . "/{$archive->Package->Database->File}";
41
+ self::$zipPath = DUP_Settings::getSsdirTmpPath() . "/{$archive->File}";
42
  self::$zipArchive = new ZipArchive();
43
  self::$networkFlush = empty($package_zip_flush) ? false : $package_zip_flush;
44
 
45
+ $filterDirs = empty($archive->FilterDirs) ? 'not set' : $archive->FilterDirs;
46
+ $filterExts = empty($archive->FilterExts) ? 'not set' : $archive->FilterExts;
47
+ $filterFiles = empty($archive->FilterFiles) ? 'not set' : $archive->FilterFiles;
48
+ $filterOn = ($archive->FilterOn) ? 'ON' : 'OFF';
49
  $filterDirsFormat = rtrim(str_replace(';', "\n\t", $filterDirs));
50
+ $filterFilesFormat = rtrim(str_replace(';', "\n\t", $filterFiles));
51
+ $lastDirSuccess = self::$compressDir;
52
 
53
  //LOAD SCAN REPORT
54
+ $json = file_get_contents(DUP_Settings::getSsdirTmpPath() . "/{$archive->Package->NameHash}_scan.json");
55
  self::$scanReport = json_decode($json);
56
 
57
  DUP_Log::Info("\n********************************************************************************");
58
  DUP_Log::Info("ARCHIVE (ZIP):");
59
  DUP_Log::Info("********************************************************************************");
60
+ $isZipOpen = (self::$zipArchive->open(self::$zipPath, ZIPARCHIVE::CREATE) === true);
61
  if (!$isZipOpen) {
62
  $error_message = "Cannot open zip file with PHP ZipArchive.";
63
  $buildProgress->set_failed($error_message);
64
+ DUP_Log::error($error_message, "Path location [" . self::$zipPath . "]", Dup_ErrorBehavior::LogOnly);
65
  $archive->Package->setStatus(DUP_PackageStatus::ERROR);
66
  return;
67
  }
68
+ DUP_Log::Info("ARCHIVE DIR: " . self::$compressDir);
69
+ DUP_Log::Info("ARCHIVE FILE: " . basename(self::$zipPath));
70
  DUP_Log::Info("FILTERS: *{$filterOn}*");
71
  DUP_Log::Info("DIRS:\n\t{$filterDirsFormat}");
72
+ DUP_Log::Info("FILES:\n\t{$filterFilesFormat}");
73
  DUP_Log::Info("EXTS: {$filterExts}");
74
 
75
  DUP_Log::Info("----------------------------------------");
76
  DUP_Log::Info("COMPRESSING");
77
+ DUP_Log::Info("SIZE:\t" . self::$scanReport->ARC->Size);
78
+ DUP_Log::Info("STATS:\tDirs " . self::$scanReport->ARC->DirCount . " | Files " . self::$scanReport->ARC->FileCount);
79
 
80
  //ADD SQL
81
  $sql_ark_file_path = $archive->Package->getSqlArkFilePath();
82
+ $isSQLInZip = self::$zipArchive->addFile(self::$sqlPath, $sql_ark_file_path);
83
 
84
  if ($isSQLInZip) {
85
+ DUP_Log::Info("SQL ADDED: " . basename(self::$sqlPath));
86
  } else {
87
  $error_message = "Unable to add database.sql to archive.";
88
+ DUP_Log::error($error_message, "SQL File Path [" . self::$sqlPath . "]", Dup_ErrorBehavior::LogOnly);
89
  $buildProgress->set_failed($error_message);
90
  $archive->Package->setStatus(DUP_PackageStatus::ERROR);
91
  return;
105
  //Don't warn when dirtory is the root path
106
  if (strcmp($dir, rtrim(self::$compressDir, '/')) != 0) {
107
  $dir_path = strlen($dir) ? "[{$dir}]" : "[Read Error] - last successful read was: [{$lastDirSuccess}]";
108
+ $info .= "DIR: {$dir_path}\n";
109
  }
110
  }
111
  }
119
  /**
120
  * count update for integrity check
121
  */
122
+ $sumItems = (self::$countDirs + self::$countFiles);
123
 
124
  /* ZIP FILES: Network Flush
125
  * This allows the process to not timeout on fcgi
126
  * setups that need a response every X seconds */
127
  $totalFileCount = count(self::$scanReport->ARC->Files);
128
+ $info = '';
129
  if (self::$networkFlush) {
130
  foreach (self::$scanReport->ARC->Files as $file) {
131
+ $file_size = @filesize($file);
132
  $localFileName = $archive->getLocalFilePath($file);
133
 
134
  if (is_readable($file)) {
135
+ if (
136
+ defined('DUPLICATOR_ZIP_ARCHIVE_ADD_FROM_STR') &&
137
+ DUPLICATOR_ZIP_ARCHIVE_ADD_FROM_STR &&
138
+ $file_size < DUP_Constants::ZIP_STRING_LIMIT &&
139
+ self::$zipArchive->addFromString($localFileName, file_get_contents($file))
140
+ ) {
141
  Dup_Log::Info("Adding {$file} to zip");
142
  self::$limitItems++;
143
  self::$countFiles++;
160
  DUP_Log::Info("Items archived [{$sumItems}] flushing response.");
161
  }
162
 
163
+ if (self::$countFiles % 500 == 0) {
164
  // Every so many files update the status so the UI can display
165
+ $archive->Package->Status = SnapUtil::getWorkPercent(DUP_PackageStatus::ARCSTART, DUP_PackageStatus::ARCVALIDATION, $totalFileCount, self::$countFiles);
166
  $archive->Package->update();
167
  }
168
  }
169
+ } else {
170
+ //Normal
 
171
  foreach (self::$scanReport->ARC->Files as $file) {
172
+ $file_size = @filesize($file);
173
  $localFileName = $archive->getLocalFilePath($file);
174
 
175
  if (is_readable($file)) {
176
+ if (
177
+ defined('DUPLICATOR_ZIP_ARCHIVE_ADD_FROM_STR') &&
178
+ DUPLICATOR_ZIP_ARCHIVE_ADD_FROM_STR &&
179
+ $file_size < DUP_Constants::ZIP_STRING_LIMIT &&
180
+ self::$zipArchive->addFromString($localFileName, file_get_contents($file))
181
+ ) {
182
  self::$countFiles++;
183
  } elseif (self::$zipArchive->addFile($file, $localFileName)) {
184
  self::$countFiles++;
189
  $info .= "FILE: [{$file}]\n";
190
  }
191
 
192
+ if (self::$countFiles % 500 == 0) {
193
  // Every so many files update the status so the UI can display
194
+ $archive->Package->Status = SnapUtil::getWorkPercent(DUP_PackageStatus::ARCSTART, DUP_PackageStatus::ARCVALIDATION, $totalFileCount, self::$countFiles);
195
  $archive->Package->update();
196
  }
197
  }
210
  * count update for integrity check
211
  */
212
  $archive->file_count = self::$countDirs + self::$countFiles;
213
+ DUP_Log::Info("FILE ADDED TO ZIP: " . $archive->file_count);
214
 
215
 
216
  //--------------------------------
217
  //LOG FINAL RESULTS
218
  DUP_Util::fcgiFlush();
219
  $zipCloseResult = self::$zipArchive->close();
220
+ if ($zipCloseResult) {
221
  DUP_Log::Info("COMPRESSION RESULT: '{$zipCloseResult}'");
222
  } else {
223
  $error_message = "ZipArchive close failure.";
224
+ DUP_Log::error(
225
+ $error_message,
226
+ "The ZipArchive engine is having issues zipping up the files on this server. For more details visit the FAQ\n"
227
+ . "I'm getting a ZipArchive close failure when building. How can I resolve this?\n"
228
+ . "[https://snapcreek.com/duplicator/docs/faqs-tech/#faq-package-165-q]",
229
+ Dup_ErrorBehavior::LogOnly
230
+ );
231
  $buildProgress->set_failed($error_message);
232
  $archive->Package->setStatus(DUP_PackageStatus::ERROR);
233
  return;
237
  $timerAllSum = DUP_Util::elapsedTime($timerAllEnd, $timerAllStart);
238
 
239
  self::$zipFileSize = @filesize(self::$zipPath);
240
+ DUP_Log::Info("COMPRESSED SIZE: " . DUP_Util::byteSize(self::$zipFileSize));
241
  DUP_Log::Info("ARCHIVE RUNTIME: {$timerAllSum}");
242
+ DUP_Log::Info("MEMORY STACK: " . DUP_Server::getPHPMemory());
 
 
 
243
  } catch (Exception $e) {
244
  $error_message = "Runtime error in class.pack.archive.zip.php constructor.";
245
  DUP_Log::error($error_message, "Exception: {$e}", Dup_ErrorBehavior::LogOnly);
248
  return;
249
  }
250
  }
251
+ }
classes/package/class.pack.database.php CHANGED
@@ -1,779 +1,787 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- // Exit if accessed directly
4
- if (!defined('DUPLICATOR_VERSION'))
5
- exit;
6
-
7
- /**
8
- * Class for gathering system information about a database
9
- *
10
- * Standard: PSR-2
11
- * @link http://www.php-fig.org/psr/psr-2
12
- *
13
- */
14
- class DUP_DatabaseInfo
15
- {
16
-
17
- /**
18
- * The SQL file was built with mysqldump or PHP
19
- */
20
- public $buildMode;
21
-
22
- /**
23
- * A unique list of all the collation table types used in the database
24
- */
25
- public $collationList;
26
-
27
- /**
28
- * Does any filtered table have an upper case character in it
29
- */
30
- public $isTablesUpperCase;
31
-
32
- /**
33
- * Does the database name have any filtered characters in it
34
- */
35
- public $isNameUpperCase;
36
-
37
- /**
38
- * The real name of the database
39
- */
40
- public $name;
41
-
42
- /**
43
- * The full count of all tables in the database
44
- */
45
- public $tablesBaseCount;
46
-
47
- /**
48
- * The count of tables after the tables filter has been applied
49
- */
50
- public $tablesFinalCount;
51
-
52
- /**
53
- * The number of rows from all filtered tables in the database
54
- */
55
- public $tablesRowCount;
56
-
57
- /**
58
- * The estimated data size on disk from all filtered tables in the database
59
- */
60
- public $tablesSizeOnDisk;
61
-
62
- /**
63
- * Gets the server variable lower_case_table_names
64
- *
65
- * 0 store=lowercase; compare=sensitive (works only on case sensitive file systems )
66
- * 1 store=lowercase; compare=insensitive
67
- * 2 store=exact; compare=insensitive (works only on case INsensitive file systems )
68
- * default is 0/Linux ; 1/Windows
69
- */
70
- public $varLowerCaseTables;
71
-
72
- /**
73
- * The simple numeric version number of the database server
74
- * @exmaple: 5.5
75
- */
76
- public $version;
77
-
78
- /**
79
- * The full text version number of the database server
80
- * @exmaple: 10.2 mariadb.org binary distribution
81
- */
82
- public $versionComment;
83
-
84
- /**
85
- * table wise row counts array, Key as table name and value as row count
86
- * table name => row count
87
- */
88
- public $tableWiseRowCounts;
89
-
90
- /**
91
- * @var array List of triggers included in the database
92
- */
93
- public $triggerList = array();
94
-
95
- /**
96
- * Integer field file structure of table, table name as key
97
- */
98
- private $intFieldsStruct = array();
99
-
100
- /**
101
- * $currentIndex => processedSchemaSize
102
- */
103
- private $indexProcessedSchemaSize = array();
104
-
105
- //CONSTRUCTOR
106
- function __construct()
107
- {
108
- $this->collationList = array();
109
- $this->tableWiseRowCounts = array();
110
- }
111
-
112
- public function addTriggers()
113
- {
114
- global $wpdb;
115
-
116
- if (!is_array($triggers = $wpdb->get_results("SHOW TRIGGERS", ARRAY_A))) {
117
- return;
118
- }
119
-
120
- foreach ($triggers as $trigger) {
121
- $name = $trigger["Trigger"];
122
- $create = $wpdb->get_row("SHOW CREATE TRIGGER `{$name}`", ARRAY_N);
123
- $this->triggerList[$name] = array(
124
- "create" => "DELIMITER ;;\n".$create[2].";;\nDELIMITER ;"
125
- );
126
- }
127
- }
128
- }
129
-
130
- class DUP_Database
131
- {
132
- //PUBLIC
133
- public $Type = 'MySQL';
134
- public $Size;
135
- public $File;
136
- public $Path;
137
- public $FilterTables;
138
- public $FilterOn;
139
- public $Name;
140
- public $Compatible;
141
- public $Comments;
142
-
143
- /**
144
- *
145
- * @var DUP_DatabaseInfo
146
- */
147
- public $info = null;
148
- //PROTECTED
149
- protected $Package;
150
- //PRIVATE
151
- private $tempDbPath;
152
- private $EOFMarker;
153
- private $networkFlush;
154
-
155
- /**
156
- * Init this object
157
- */
158
- function __construct($package)
159
- {
160
- $this->Package = $package;
161
- $this->EOFMarker = "";
162
- $package_zip_flush = DUP_Settings::Get('package_zip_flush');
163
- $this->networkFlush = empty($package_zip_flush) ? false : $package_zip_flush;
164
- $this->info = new DUP_DatabaseInfo();
165
- $this->info->varLowerCaseTables = DUP_Util::isWindows() ? 1 : 0;
166
- }
167
-
168
- /**
169
- * Build the database script
170
- *
171
- * @param DUP_Package $package A reference to the package that this database object belongs in
172
- *
173
- * @return null
174
- */
175
- public function build($package, $errorBehavior = Dup_ErrorBehavior::ThrowException)
176
- {
177
- try {
178
-
179
- $this->Package = $package;
180
- do_action('duplicator_lite_build_database_before_start', $package);
181
-
182
- $time_start = DUP_Util::getMicrotime();
183
- $this->Package->setStatus(DUP_PackageStatus::DBSTART);
184
- $this->tempDbPath = DUP_Settings::getSsdirTmpPath()."/{$this->File}";
185
-
186
- $package_mysqldump = DUP_Settings::Get('package_mysqldump');
187
- $package_phpdump_qrylimit = DUP_Settings::Get('package_phpdump_qrylimit');
188
-
189
- $mysqlDumpPath = DUP_DB::getMySqlDumpPath();
190
- $mode = DUP_DB::getBuildMode();
191
- $reserved_db_filepath = duplicator_get_abs_path().'/database.sql';
192
-
193
- $log = "\n********************************************************************************\n";
194
- $log .= "DATABASE:\n";
195
- $log .= "********************************************************************************\n";
196
- $log .= "BUILD MODE: {$mode}";
197
- $log .= ($mode == 'PHP') ? "(query limit - {$package_phpdump_qrylimit})\n" : "\n";
198
- $log .= "MYSQLTIMEOUT: ".DUPLICATOR_DB_MAX_TIME."\n";
199
- $log .= "MYSQLDUMP: ";
200
- $log .= ($mysqlDumpPath) ? "Is Supported" : "Not Supported";
201
- DUP_Log::Info($log);
202
- $log = null;
203
-
204
- do_action('duplicator_lite_build_database_start', $package);
205
-
206
- switch ($mode) {
207
- case 'MYSQLDUMP':
208
- $this->mysqlDump($mysqlDumpPath);
209
- break;
210
- case 'PHP' :
211
- $this->phpDump($package);
212
- break;
213
- }
214
-
215
- DUP_Log::Info("SQL CREATED: {$this->File}");
216
- $time_end = DUP_Util::getMicrotime();
217
- $time_sum = DUP_Util::elapsedTime($time_end, $time_start);
218
-
219
- //File below 10k considered incomplete
220
- $sql_file_size = is_file($this->tempDbPath) ? @filesize($this->tempDbPath) : 0;
221
- DUP_Log::Info("SQL FILE SIZE: ".DUP_Util::byteSize($sql_file_size)." ({$sql_file_size})");
222
-
223
- if ($sql_file_size < 1350) {
224
- $error_message = "SQL file size too low.";
225
- $package->BuildProgress->set_failed($error_message);
226
- $package->Status = DUP_PackageStatus::ERROR;
227
- $package->Update();
228
- DUP_Log::error($error_message, "File does not look complete. Check permission on file and parent directory at [{$this->tempDbPath}]", $errorBehavior);
229
- do_action('duplicator_lite_build_database_fail', $package);
230
- } else {
231
- do_action('duplicator_lite_build_database_completed', $package);
232
- }
233
-
234
- DUP_Log::Info("SQL FILE TIME: ".date("Y-m-d H:i:s"));
235
- DUP_Log::Info("SQL RUNTIME: {$time_sum}");
236
-
237
- $this->Size = is_file($this->tempDbPath) ? @filesize($this->tempDbPath) : 0;
238
-
239
- $this->Package->setStatus(DUP_PackageStatus::DBDONE);
240
- }
241
- catch (Exception $e) {
242
- do_action('duplicator_lite_build_database_fail', $package);
243
- DUP_Log::error("Runtime error in DUP_Database::Build. ".$e->getMessage(), "Exception: {$e}", $errorBehavior);
244
- }
245
- }
246
-
247
- /**
248
- * Get the database meta-data such as tables as all there details
249
- *
250
- * @return array Returns an array full of meta-data about the database
251
- */
252
- public function getScannerData()
253
- {
254
- global $wpdb;
255
-
256
- $filterTables = isset($this->FilterTables) ? explode(',', $this->FilterTables) : array();
257
- $tblBaseCount = 0;
258
- $tblCount = 0;
259
-
260
- $tables = $wpdb->get_results("SHOW TABLE STATUS", ARRAY_A);
261
- $info = array();
262
- $info['Status']['Success'] = is_null($tables) ? false : true;
263
- //DB_Case for the database name is never checked on
264
- $info['Status']['DB_Case'] = 'Good';
265
- $info['Status']['DB_Rows'] = 'Good';
266
- $info['Status']['DB_Size'] = 'Good';
267
- $info['Status']['TBL_Case'] = 'Good';
268
- $info['Status']['TBL_Rows'] = 'Good';
269
- $info['Status']['TBL_Size'] = 'Good';
270
-
271
- $info['Size'] = 0;
272
- $info['Rows'] = 0;
273
- $info['TableCount'] = 0;
274
- $info['TableList'] = array();
275
- $tblCaseFound = 0;
276
- $tblRowsFound = 0;
277
- $tblSizeFound = 0;
278
-
279
- //Grab Table Stats
280
- $filteredTables = array();
281
- foreach ($tables as $table) {
282
- $tblBaseCount++;
283
- $name = $table["Name"];
284
- if ($this->FilterOn && is_array($filterTables)) {
285
- if (in_array($name, $filterTables)) {
286
- continue;
287
- }
288
- }
289
-
290
- $size = ($table["Data_length"] + $table["Index_length"]);
291
- $rows = empty($table["Rows"]) ? '0' : $table["Rows"];
292
-
293
- $info['Size'] += $size;
294
- $info['Rows'] += ($table["Rows"]);
295
- $info['TableList'][$name]['Case'] = preg_match('/[A-Z]/', $name) ? 1 : 0;
296
- $info['TableList'][$name]['Rows'] = number_format($rows);
297
- $info['TableList'][$name]['Size'] = DUP_Util::byteSize($size);
298
- $info['TableList'][$name]['USize'] = $size;
299
- $filteredTables[] = $name;
300
- $tblCount++;
301
-
302
- //Table Uppercase
303
- if ($info['TableList'][$name]['Case']) {
304
- if (!$tblCaseFound) {
305
- $tblCaseFound = 1;
306
- }
307
- }
308
-
309
- //Table Row Count
310
- if ($rows > DUPLICATOR_SCAN_DB_TBL_ROWS) {
311
- if (!$tblRowsFound) {
312
- $tblRowsFound = 1;
313
- }
314
- }
315
-
316
- //Table Size
317
- if ($size > DUPLICATOR_SCAN_DB_TBL_SIZE) {
318
- if (!$tblSizeFound) {
319
- $tblSizeFound = 1;
320
- }
321
- }
322
- }
323
- $this->setInfoObj($filteredTables);
324
- $this->info->addTriggers();
325
-
326
- $info['Status']['DB_Case'] = preg_match('/[A-Z]/', $wpdb->dbname) ? 'Warn' : 'Good';
327
- $info['Status']['DB_Rows'] = ($info['Rows'] > DUPLICATOR_SCAN_DB_ALL_ROWS) ? 'Warn' : 'Good';
328
- $info['Status']['DB_Size'] = ($info['Size'] > DUPLICATOR_SCAN_DB_ALL_SIZE) ? 'Warn' : 'Good';
329
-
330
- $info['Status']['TBL_Case'] = ($tblCaseFound) ? 'Warn' : 'Good';
331
- $info['Status']['TBL_Rows'] = ($tblRowsFound) ? 'Warn' : 'Good';
332
- $info['Status']['TBL_Size'] = ($tblSizeFound) ? 'Warn' : 'Good';
333
- $info['Status']['Triggers'] = count($this->info->triggerList) > 0 ? 'Warn' : 'Good';
334
-
335
- $info['RawSize'] = $info['Size'];
336
- $info['Size'] = DUP_Util::byteSize($info['Size']) or "unknown";
337
- $info['Rows'] = number_format($info['Rows']) or "unknown";
338
- $info['TableList'] = $info['TableList'] or "unknown";
339
- $info['TableCount'] = $tblCount;
340
-
341
- $this->info->isTablesUpperCase = $tblCaseFound;
342
- $this->info->tablesBaseCount = $tblBaseCount;
343
- $this->info->tablesFinalCount = $tblCount;
344
- $this->info->tablesRowCount = $info['Rows'];
345
- $this->info->tablesSizeOnDisk = $info['Size'];
346
-
347
- return $info;
348
- }
349
-
350
- /**
351
- * @param array &$filteredTables Filtered names of tables to include in collation search.
352
- * Parameter does not change in the function, is passed by reference only to avoid copying.
353
- *
354
- * @return void
355
- */
356
- public function setInfoObj(&$filteredTables)
357
- {
358
- global $wpdb;
359
-
360
- $this->info->buildMode = DUP_DB::getBuildMode();
361
- $this->info->version = DUP_DB::getVersion();
362
- $this->info->versionComment = DUP_DB::getVariable('version_comment');
363
- $this->info->varLowerCaseTables = DUP_DB::getVariable('lower_case_table_names');
364
- $this->info->name = $wpdb->dbname;
365
- $this->info->isNameUpperCase = preg_match('/[A-Z]/', $wpdb->dbname) ? 1 : 0;
366
- $this->info->collationList = DUP_DB::getTableCollationList($filteredTables);
367
- }
368
-
369
- /**
370
- * Unset tableWiseRowCounts table key for which row count is unstable
371
- *
372
- * @param object $package The reference to the current package being built *
373
- * @return void
374
- */
375
- public function validateTableWiseRowCounts()
376
- {
377
- foreach ($this->Package->Database->info->tableWiseRowCounts as $rewriteTableAs => $rowCount) {
378
- $newRowCount = $GLOBALS['wpdb']->get_var("SELECT Count(*) FROM `{$rewriteTableAs}`");
379
- if ($rowCount != $newRowCount) {
380
- unset($this->Package->Database->info->tableWiseRowCounts[$rewriteTableAs]);
381
- }
382
- }
383
- }
384
-
385
- /**
386
- * Build the database script using mysqldump
387
- *
388
- * @return bool Returns true if the sql script was successfully created
389
- */
390
- private function mysqlDump($exePath)
391
- {
392
- global $wpdb;
393
- require_once (DUPLICATOR_PLUGIN_PATH.'classes/utilities/class.u.shell.php');
394
-
395
- $host = explode(':', DB_HOST);
396
- $host = reset($host);
397
- $port = strpos(DB_HOST, ':') ? end(explode(':', DB_HOST)) : '';
398
- $name = DB_NAME;
399
- $mysqlcompat_on = isset($this->Compatible) && strlen($this->Compatible);
400
-
401
- //Build command
402
- $cmd = escapeshellarg($exePath);
403
- $cmd .= ' --no-create-db';
404
- $cmd .= ' --single-transaction';
405
- $cmd .= ' --hex-blob';
406
- $cmd .= ' --skip-add-drop-table';
407
- $cmd .= ' --routines';
408
- $cmd .= ' --quote-names';
409
- $cmd .= ' --skip-comments';
410
- $cmd .= ' --skip-set-charset';
411
- $cmd .= ' --skip-triggers';
412
- $cmd .= ' --allow-keywords';
413
- $cmd .= ' --no-tablespaces';
414
-
415
- //Compatibility mode
416
- if ($mysqlcompat_on) {
417
- DUP_Log::Info("COMPATIBLE: [{$this->Compatible}]");
418
- $cmd .= " --compatible={$this->Compatible}";
419
- }
420
-
421
- //Filter tables
422
- $res = $wpdb->get_results('SHOW FULL TABLES', ARRAY_N);
423
- $tables = array();
424
- $baseTables = array();
425
- foreach ($res as $row) {
426
- if (DUP_Util::isTableExists($row[0])) {
427
- $tables[] = $row[0];
428
- if ('BASE TABLE' == $row[1]) {
429
- $baseTables[] = $row[0];
430
- }
431
- }
432
- }
433
- $filterTables = isset($this->FilterTables) ? explode(',', $this->FilterTables) : null;
434
- $tblAllCount = count($tables);
435
-
436
- foreach ($tables as $table) {
437
- if (in_array($table, $baseTables)) {
438
- $row_count = $GLOBALS['wpdb']->get_var("SELECT Count(*) FROM `{$table}`");
439
- $rewrite_table_as = $this->rewriteTableNameAs($table);
440
- $this->Package->Database->info->tableWiseRowCounts[$rewrite_table_as] = $row_count;
441
- }
442
- }
443
- //$tblFilterOn = ($this->FilterOn) ? 'ON' : 'OFF';
444
-
445
- if (is_array($filterTables) && $this->FilterOn) {
446
- foreach ($tables as $key => $val) {
447
- if (in_array($tables[$key], $filterTables)) {
448
- $cmd .= " --ignore-table={$name}.{$tables[$key]} ";
449
- unset($tables[$key]);
450
- }
451
- }
452
- }
453
-
454
- $cmd .= ' -u '.escapeshellarg(DB_USER);
455
- $cmd .= (DB_PASSWORD) ?
456
- ' -p'.DUP_Shell_U::escapeshellargWindowsSupport(DB_PASSWORD) : '';
457
-
458
- $cmd .= ' -h '.escapeshellarg($host);
459
- $cmd .= (!empty($port) && is_numeric($port) ) ?
460
- ' -P '.$port : '';
461
-
462
- $isPopenEnabled = DUP_Shell_U::isPopenEnabled();
463
-
464
- if (!$isPopenEnabled) {
465
- $cmd .= ' -r '.escapeshellarg($this->tempDbPath);
466
- }
467
-
468
- $cmd .= ' '.escapeshellarg(DB_NAME);
469
- $cmd .= ' 2>&1';
470
-
471
- if ($isPopenEnabled) {
472
- $needToRewrite = false;
473
- foreach ($tables as $tableName) {
474
- $rewriteTableAs = $this->rewriteTableNameAs($tableName);
475
- if ($tableName != $rewriteTableAs) {
476
- $needToRewrite = true;
477
- break;
478
- }
479
- }
480
-
481
- if ($needToRewrite) {
482
- $findReplaceTableNames = array(); // orignal table name => rewrite table name
483
-
484
- foreach ($tables as $tableName) {
485
- $rewriteTableAs = $this->rewriteTableNameAs($tableName);
486
- if ($tableName != $rewriteTableAs) {
487
- $findReplaceTableNames[$tableName] = $rewriteTableAs;
488
- }
489
- }
490
- }
491
-
492
- $firstLine = '';
493
- DUP_LOG::trace("Executing mysql dump command by popen: $cmd");
494
- $handle = popen($cmd, "r");
495
- if ($handle) {
496
- $sql_header = "/* DUPLICATOR-LITE (MYSQL-DUMP BUILD MODE) MYSQL SCRIPT CREATED ON : ".@date("Y-m-d H:i:s")." */\n\n";
497
- file_put_contents($this->tempDbPath, $sql_header, FILE_APPEND);
498
- while (!feof($handle)) {
499
- $line = fgets($handle); //get ony one line
500
- if ($line) {
501
- if (empty($firstLine)) {
502
- $firstLine = $line;
503
- if (false !== stripos($line, 'Using a password on the command line interface can be insecure'))
504
- continue;
505
- }
506
-
507
- if ($needToRewrite) {
508
- $replaceCount = 1;
509
-
510
- if (preg_match('/CREATE TABLE `(.*?)`/', $line, $matches)) {
511
- $tableName = $matches[1];
512
- if (isset($findReplaceTableNames[$tableName])) {
513
- $rewriteTableAs = $findReplaceTableNames[$tableName];
514
- $line = str_replace('CREATE TABLE `'.$tableName.'`', 'CREATE TABLE `'.$rewriteTableAs.'`', $line, $replaceCount);
515
- }
516
- } elseif (preg_match('/INSERT INTO `(.*?)`/', $line, $matches)) {
517
- $tableName = $matches[1];
518
- if (isset($findReplaceTableNames[$tableName])) {
519
- $rewriteTableAs = $findReplaceTableNames[$tableName];
520
- $line = str_replace('INSERT INTO `'.$tableName.'`', 'INSERT INTO `'.$rewriteTableAs.'`', $line, $replaceCount);
521
- }
522
- } elseif (preg_match('/LOCK TABLES `(.*?)`/', $line, $matches)) {
523
- $tableName = $matches[1];
524
- if (isset($findReplaceTableNames[$tableName])) {
525
- $rewriteTableAs = $findReplaceTableNames[$tableName];
526
- $line = str_replace('LOCK TABLES `'.$tableName.'`', 'LOCK TABLES `'.$rewriteTableAs.'`', $line, $replaceCount);
527
- }
528
- }
529
- }
530
-
531
- file_put_contents($this->tempDbPath, $line, FILE_APPEND);
532
- $output = "Ran from {$exePath}";
533
- }
534
- }
535
- $mysqlResult = pclose($handle);
536
- } else {
537
- $output = '';
538
- }
539
-
540
- // Password bug > 5.6 (@see http://bugs.mysql.com/bug.php?id=66546)
541
- if (empty($output) && trim($firstLine) === 'Warning: Using a password on the command line interface can be insecure.') {
542
- $output = '';
543
- }
544
- } else {
545
- DUP_LOG::trace("Executing mysql dump command $cmd");
546
- exec($cmd, $output, $mysqlResult);
547
- $output = implode("\n", $output);
548
-
549
- // Password bug > 5.6 (@see http://bugs.mysql.com/bug.php?id=66546)
550
- if (trim($output) === 'Warning: Using a password on the command line interface can be insecure.') {
551
- $output = '';
552
- }
553
- $output = (strlen($output)) ? $output : "Ran from {$exePath}";
554
-
555
- $tblCreateCount = count($tables);
556
- $tblFilterCount = $tblAllCount - $tblCreateCount;
557
-
558
- //DEBUG
559
- //DUP_Log::Info("COMMAND: {$cmd}");
560
- DUP_Log::Info("FILTERED: [{$this->FilterTables}]");
561
- DUP_Log::Info("RESPONSE: {$output}");
562
- DUP_Log::Info("TABLES: total:{$tblAllCount} | filtered:{$tblFilterCount} | create:{$tblCreateCount}");
563
- }
564
-
565
- $sql_footer = "\n\n/* Duplicator WordPress Timestamp: ".date("Y-m-d H:i:s")."*/\n";
566
- $sql_footer .= "/* ".DUPLICATOR_DB_EOF_MARKER." */\n";
567
- file_put_contents($this->tempDbPath, $sql_footer, FILE_APPEND);
568
- if ($mysqlResult !== 0) {
569
- /**
570
- * -1 error command shell
571
- * mysqldump return
572
- * 0 - Success
573
- * 1 - Warning
574
- * 2 - Exception
575
- */
576
- DUP_Log::Info('MYSQL DUMP ERROR '.print_r($mysqlResult, true));
577
- DUP_Log::error(__('Shell mysql dump error. Change SQL Mode to the "PHP Code" in the Duplicator > Settings > Packages.', 'duplicator'), implode("\n", DupLiteSnapLibIOU::getLastLinesOfFile($this->tempDbPath,
578
- DUPLICATOR_DB_MYSQLDUMP_ERROR_CONTAINING_LINE_COUNT, DUPLICATOR_DB_MYSQLDUMP_ERROR_CHARS_IN_LINE_COUNT)), Dup_ErrorBehavior::ThrowException);
579
- return false;
580
- }
581
-
582
- return true;
583
- }
584
-
585
- /**
586
- * Build the database script using php
587
- *
588
- * @return bool Returns true if the sql script was successfully created
589
- */
590
- private function phpDump($package)
591
- {
592
- global $wpdb;
593
-
594
- $wpdb->query("SET session wait_timeout = ".DUPLICATOR_DB_MAX_TIME);
595
- if (($handle = fopen($this->tempDbPath, 'w+')) == false) {
596
- DUP_Log::error('[PHP DUMP] ERROR Can\'t open sbStorePath "'.$this->tempDbPath.'"', Dup_ErrorBehavior::ThrowException);
597
- }
598
- $tables = $wpdb->get_col("SHOW FULL TABLES WHERE Table_Type != 'VIEW'");
599
-
600
- $filterTables = isset($this->FilterTables) ? explode(',', $this->FilterTables) : null;
601
- $tblAllCount = count($tables);
602
- //$tblFilterOn = ($this->FilterOn) ? 'ON' : 'OFF';
603
- $qryLimit = DUP_Settings::Get('package_phpdump_qrylimit');
604
-
605
- if (is_array($filterTables) && $this->FilterOn) {
606
- foreach ($tables as $key => $val) {
607
- if (in_array($tables[$key], $filterTables)) {
608
- unset($tables[$key]);
609
- }
610
- }
611
- }
612
- $tblCreateCount = count($tables);
613
- $tblFilterCount = $tblAllCount - $tblCreateCount;
614
-
615
- DUP_Log::Info("TABLES: total:{$tblAllCount} | filtered:{$tblFilterCount} | create:{$tblCreateCount}");
616
- DUP_Log::Info("FILTERED: [{$this->FilterTables}]");
617
-
618
- //Added 'NO_AUTO_VALUE_ON_ZERO' at plugin version 1.2.12 to fix :
619
- //**ERROR** database error write 'Invalid default value for for older mysql versions
620
- $sql_header = "/* DUPLICATOR-LITE (PHP BUILD MODE) MYSQL SCRIPT CREATED ON : ".@date("Y-m-d H:i:s")." */\n\n";
621
- $sql_header .= "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n\n";
622
- $sql_header .= "SET FOREIGN_KEY_CHECKS = 0;\n\n";
623
- fwrite($handle, $sql_header);
624
-
625
- //BUILD CREATES:
626
- //All creates must be created before inserts do to foreign key constraints
627
- foreach ($tables as $table) {
628
- $rewrite_table_as = $this->rewriteTableNameAs($table);
629
- $create = $wpdb->get_row("SHOW CREATE TABLE `{$table}`", ARRAY_N);
630
- $count = 1;
631
- $create_table_query = str_replace($table, $rewrite_table_as, $create[1], $count);
632
- @fwrite($handle, "{$create_table_query};\n\n");
633
- }
634
-
635
- $procedures = $wpdb->get_col("SHOW PROCEDURE STATUS WHERE `Db` = '{$wpdb->dbname}'", 1);
636
- if (count($procedures)) {
637
- foreach ($procedures as $procedure) {
638
- @fwrite($handle, "DELIMITER ;;\n");
639
- $create = $wpdb->get_row("SHOW CREATE PROCEDURE `{$procedure}`", ARRAY_N);
640
- @fwrite($handle, "{$create[2]} ;;\n");
641
- @fwrite($handle, "DELIMITER ;\n\n");
642
- }
643
- }
644
-
645
- $functions = $wpdb->get_col("SHOW FUNCTION STATUS WHERE `Db` = '{$wpdb->dbname}'", 1);
646
- if (count($functions)) {
647
- foreach ($functions as $function) {
648
- @fwrite($handle, "DELIMITER ;;\n");
649
- $create = $wpdb->get_row("SHOW CREATE FUNCTION `{$function}`", ARRAY_N);
650
- @fwrite($handle, "{$create[2]} ;;\n");
651
- @fwrite($handle, "DELIMITER ;\n\n");
652
- }
653
- }
654
-
655
- $views = $wpdb->get_col("SHOW FULL TABLES WHERE Table_Type = 'VIEW'");
656
- if (count($views)) {
657
- foreach ($views as $view) {
658
- $create = $wpdb->get_row("SHOW CREATE VIEW `{$view}`", ARRAY_N);
659
- @fwrite($handle, "{$create[1]};\n\n");
660
- }
661
- }
662
-
663
- $table_count = count($tables);
664
- $table_number = 0;
665
-
666
- //BUILD INSERTS:
667
- //Create Insert in 100 row increments to better handle memory
668
- foreach ($tables as $table) {
669
-
670
- $table_number++;
671
- if ($table_number % 2 == 0) {
672
- $this->Package->Status = DupLiteSnapLibUtil::getWorkPercent(DUP_PackageStatus::DBSTART, DUP_PackageStatus::DBDONE, $table_count, $table_number);
673
- $this->Package->update();
674
- }
675
-
676
- $row_count = $wpdb->get_var("SELECT Count(*) FROM `{$table}`");
677
- $rewrite_table_as = $this->rewriteTableNameAs($table);
678
-
679
- $this->Package->Database->info->tableWiseRowCounts[$rewrite_table_as] = $row_count;
680
-
681
- if ($row_count > $qryLimit) {
682
- $row_count = ceil($row_count / $qryLimit);
683
- } else if ($row_count > 0) {
684
- $row_count = 1;
685
- }
686
-
687
- if ($row_count >= 1) {
688
- fwrite($handle, "\n/* INSERT TABLE DATA: {$table} */\n");
689
- }
690
-
691
- for ($i = 0; $i < $row_count; $i++) {
692
- $sql = "";
693
- $limit = $i * $qryLimit;
694
- $query = "SELECT * FROM `{$table}` LIMIT {$limit}, {$qryLimit}";
695
- $rows = $wpdb->get_results($query, ARRAY_A);
696
-
697
- $select_last_error = $wpdb->last_error;
698
- if ('' !== $select_last_error) {
699
- $fix = esc_html__('Please contact your DataBase administrator to fix the error.', 'duplicator');
700
- $errorMessage = $select_last_error.' '.$fix.'.';
701
- $package->BuildProgress->set_failed($errorMessage);
702
- $package->BuildProgress->failed = true;
703
- $package->failed = true;
704
- $package->Status = DUP_PackageStatus::ERROR;
705
- $package->Update();
706
- DUP_Log::error($select_last_error, $fix, Dup_ErrorBehavior::ThrowException);
707
- return;
708
- }
709
-
710
- if (is_array($rows)) {
711
- foreach ($rows as $row) {
712
- $sql .= "INSERT INTO `{$rewrite_table_as}` VALUES(";
713
- $num_values = count($row);
714
- $num_counter = 1;
715
- foreach ($row as $value) {
716
- if (is_null($value) || !isset($value)) {
717
- ($num_values == $num_counter) ? $sql .= 'NULL' : $sql .= 'NULL, ';
718
- } else {
719
- ($num_values == $num_counter) ? $sql .= '"'.DUP_DB::escSQL($value, true).'"' : $sql .= '"'.DUP_DB::escSQL($value, true).'", ';
720
- }
721
- $num_counter++;
722
- }
723
- $sql .= ");\n";
724
- }
725
- fwrite($handle, $sql);
726
- }
727
- }
728
-
729
- //Flush buffer if enabled
730
- if ($this->networkFlush) {
731
- DUP_Util::fcgiFlush();
732
- }
733
- $sql = null;
734
- $rows = null;
735
- }
736
-
737
- $sql_footer = "\nSET FOREIGN_KEY_CHECKS = 1; \n\n";
738
- $sql_footer .= "/* Duplicator WordPress Timestamp: ".date("Y-m-d H:i:s")."*/\n";
739
- $sql_footer .= "/* ".DUPLICATOR_DB_EOF_MARKER." */\n";
740
- fwrite($handle, $sql_footer);
741
- $wpdb->flush();
742
- fclose($handle);
743
- }
744
-
745
- private function rewriteTableNameAs($table)
746
- {
747
- $table_prefix = $this->getTablePrefix();
748
- if (!isset($this->sameNameTableExists)) {
749
- global $wpdb;
750
- $this->sameNameTableExists = false;
751
- $all_tables = $wpdb->get_col("SHOW FULL TABLES WHERE Table_Type != 'VIEW'");
752
- foreach ($all_tables as $table_name) {
753
- if (strtolower($table_name) != $table_name && in_array(strtolower($table_name), $all_tables)) {
754
- $this->sameNameTableExists = true;
755
- break;
756
- }
757
- }
758
- }
759
- if (false === $this->sameNameTableExists && 0 === stripos($table, $table_prefix) && 0 !== strpos($table, $table_prefix)) {
760
- $post_fix = substr($table, strlen($table_prefix));
761
- $rewrite_table_name = $table_prefix.$post_fix;
762
- } else {
763
- $rewrite_table_name = $table;
764
- }
765
- return $rewrite_table_name;
766
- }
767
-
768
- private function getTablePrefix()
769
- {
770
- global $wpdb;
771
- $table_prefix = (is_multisite() && !defined('MULTISITE')) ? $wpdb->base_prefix : $wpdb->get_blog_prefix(0);
772
- return $table_prefix;
773
- }
774
-
775
- public function getUrl()
776
- {
777
- return DUP_Settings::getSsdirUrl()."/".$this->File;
778
- }
779
- }
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ use Duplicator\Libs\Snap\SnapDB;
4
+ use Duplicator\Libs\Snap\SnapIO;
5
+ use Duplicator\Libs\Snap\SnapURL;
6
+ use Duplicator\Libs\Snap\SnapUtil;
7
+ use Duplicator\Libs\Snap\SnapWP;
8
+
9
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
10
+ // Exit if accessed directly
11
+ if (!defined('DUPLICATOR_VERSION')) {
12
+ exit;
13
+ }
14
+
15
+ /**
16
+ * Class for gathering system information about a database
17
+ *
18
+ * Standard: PSR-2
19
+ * @link http://www.php-fig.org/psr/psr-2
20
+ *
21
+ */
22
+ class DUP_DatabaseInfo
23
+ {
24
+ /**
25
+ * The SQL file was built with mysqldump or PHP
26
+ */
27
+ public $buildMode = 'PHP';
28
+ /** @var string[] A unique list of all charsets table types used in the database */
29
+ public $charSetList = array();
30
+ /** @var string[] A unique list of all the collation table types used in the database */
31
+ public $collationList = array();
32
+ /** @var string[] engile list used in database tables */
33
+ public $engineList = array();
34
+ /**
35
+ * Does any filtered table have an upper case character in it
36
+ */
37
+ public $isTablesUpperCase = false;
38
+ /**
39
+ * Does the database name have any filtered characters in it
40
+ */
41
+ public $isNameUpperCase = false;
42
+ /**
43
+ * The real name of the database
44
+ */
45
+ public $name = '';
46
+ /** @var int The full count of all tables in the database */
47
+ public $tablesBaseCount = 0;
48
+ /** @var int The count of tables after the tables filter has been applied */
49
+ public $tablesFinalCount = 0;
50
+ /** @var int multisite tables filtered count */
51
+ public $muFilteredTableCount = 0;
52
+ /** @var int The number of rows from all filtered tables in the database */
53
+ public $tablesRowCount = 0;
54
+ /** @var int The estimated data size on disk from all filtered tables in the database */
55
+ public $tablesSizeOnDisk = 0;
56
+ /** @var array */
57
+ public $tablesList = array();
58
+ /**
59
+ * Gets the server variable lower_case_table_names
60
+ *
61
+ * 0 store=lowercase; compare=sensitive (works only on case sensitive file systems )
62
+ * 1 store=lowercase; compare=insensitive
63
+ * 2 store=exact; compare=insensitive (works only on case INsensitive file systems )
64
+ * default is 0/Linux ; 1/Windows
65
+ */
66
+ public $varLowerCaseTables = false;
67
+ /**
68
+ * The database engine (MySQL/MariaDB/Percona)
69
+ * @var string
70
+ * @example MariaDB
71
+ */
72
+ public $dbEngine = '';
73
+ /**
74
+ * The simple numeric version number of the database server
75
+ * @exmaple: 5.5
76
+ */
77
+ public $version = 0;
78
+ /**
79
+ * The full text version number of the database server
80
+ * @exmaple: 10.2 mariadb.org binary distribution
81
+ */
82
+ public $versionComment = 0;
83
+
84
+ /**
85
+ * @var int Number of VIEWs in the database
86
+ */
87
+ public $viewCount = 0;
88
+
89
+ /**
90
+ * @var int Number of PROCEDUREs in the database
91
+ */
92
+ public $procCount = 0;
93
+
94
+ /**
95
+ * @var int Number of PROCEDUREs in the database
96
+ */
97
+ public $funcCount = 0;
98
+
99
+ /**
100
+ * @var array List of triggers included in the database
101
+ */
102
+ public $triggerList = array();
103
+ /**
104
+ * Integer field file structure of table, table name as key
105
+ */
106
+ private $intFieldsStruct = array();
107
+ /**
108
+ * $currentIndex => processedSchemaSize
109
+ */
110
+ private $indexProcessedSchemaSize = array();
111
+ //CONSTRUCTOR
112
+ public function __construct()
113
+ {
114
+ }
115
+
116
+ public function addTriggers()
117
+ {
118
+ global $wpdb;
119
+ if (!is_array($triggers = $wpdb->get_results("SHOW TRIGGERS", ARRAY_A))) {
120
+ return;
121
+ }
122
+
123
+ foreach ($triggers as $trigger) {
124
+ $name = $trigger["Trigger"];
125
+ $create = $wpdb->get_row("SHOW CREATE TRIGGER `{$name}`", ARRAY_N);
126
+ $this->triggerList[$name] = array(
127
+ "create" => "DELIMITER ;;\n" . $create[2] . ";;\nDELIMITER ;"
128
+ );
129
+ }
130
+ }
131
+
132
+ /**
133
+ *
134
+ * @param stirng $name // table name
135
+ * @param int $inaccurateRows // This data is intended as a preliminary count and therefore not necessarily accurate
136
+ * @param int $size // This data is intended as a preliminary count and therefore not necessarily accurate
137
+ * @param int|bool $insertedRows // This value, if other than false, is the exact line value inserted into the dump file
138
+ */
139
+ public function addTableInList($name, $inaccurateRows, $size, $insertedRows = false)
140
+ {
141
+ $this->tablesList[$name] = array(
142
+ 'inaccurateRows' => (int) $inaccurateRows,
143
+ 'insertedRows' => (int) $insertedRows,
144
+ 'size' => (int) $size
145
+ );
146
+ }
147
+ }
148
+
149
+ class DUP_Database
150
+ {
151
+ const TABLE_CREATION_END_MARKER = "/***** TABLE CREATION END *****/\n";
152
+
153
+ //PUBLIC
154
+ public $Type = 'MySQL';
155
+ public $Size;
156
+ public $File;
157
+ public $Path;
158
+ public $FilterTables;
159
+ public $FilterOn;
160
+ public $Name;
161
+ public $Compatible;
162
+ public $Comments;
163
+ /**
164
+ *
165
+ * @var DUP_DatabaseInfo
166
+ */
167
+ public $info = null;
168
+ //PROTECTED
169
+ protected $Package;
170
+ //PRIVATE
171
+ private $tempDbPath;
172
+ private $EOFMarker;
173
+ private $networkFlush;
174
+ /**
175
+ * Init this object
176
+ */
177
+ public function __construct($package)
178
+ {
179
+ $this->Package = $package;
180
+ $this->EOFMarker = "";
181
+ $package_zip_flush = DUP_Settings::Get('package_zip_flush');
182
+ $this->networkFlush = empty($package_zip_flush) ? false : $package_zip_flush;
183
+ $this->info = new DUP_DatabaseInfo();
184
+ $this->info->varLowerCaseTables = DUP_Util::isWindows() ? 1 : 0;
185
+ }
186
+
187
+ /**
188
+ * Build the database script
189
+ *
190
+ * @param DUP_Package $package A reference to the package that this database object belongs in
191
+ *
192
+ * @return null
193
+ */
194
+ public function build($package, $errorBehavior = Dup_ErrorBehavior::ThrowException)
195
+ {
196
+ try {
197
+ $this->Package = $package;
198
+ do_action('duplicator_lite_build_database_before_start', $package);
199
+ $time_start = DUP_Util::getMicrotime();
200
+ $this->Package->setStatus(DUP_PackageStatus::DBSTART);
201
+ $this->tempDbPath = DUP_Settings::getSsdirTmpPath() . "/{$this->File}";
202
+ $package_mysqldump = DUP_Settings::Get('package_mysqldump');
203
+ $package_phpdump_qrylimit = DUP_Settings::Get('package_phpdump_qrylimit');
204
+ $mysqlDumpPath = DUP_DB::getMySqlDumpPath();
205
+ $mode = DUP_DB::getBuildMode();
206
+ $reserved_db_filepath = duplicator_get_abs_path() . '/database.sql';
207
+ $log = "\n********************************************************************************\n";
208
+ $log .= "DATABASE:\n";
209
+ $log .= "********************************************************************************\n";
210
+ $log .= "BUILD MODE: {$mode}";
211
+ $log .= ($mode == 'PHP') ? "(query limit - {$package_phpdump_qrylimit})\n" : "\n";
212
+ $log .= "MYSQLTIMEOUT: " . DUPLICATOR_DB_MAX_TIME . "\n";
213
+ $log .= "MYSQLDUMP: ";
214
+ $log .= ($mysqlDumpPath) ? "Is Supported" : "Not Supported";
215
+ DUP_Log::Info($log);
216
+ $log = null;
217
+ do_action('duplicator_lite_build_database_start', $package);
218
+ switch ($mode) {
219
+ case 'MYSQLDUMP':
220
+ $this->mysqlDump($mysqlDumpPath);
221
+ break;
222
+ case 'PHP':
223
+ $this->phpDump($package);
224
+ break;
225
+ }
226
+
227
+ DUP_Log::Info("SQL CREATED: {$this->File}");
228
+ $time_end = DUP_Util::getMicrotime();
229
+ $time_sum = DUP_Util::elapsedTime($time_end, $time_start);
230
+ //File below 10k considered incomplete
231
+ $sql_file_size = is_file($this->tempDbPath) ? @filesize($this->tempDbPath) : 0;
232
+ DUP_Log::Info("SQL FILE SIZE: " . DUP_Util::byteSize($sql_file_size) . " ({$sql_file_size})");
233
+ if ($sql_file_size < 1350) {
234
+ $error_message = "SQL file size too low.";
235
+ $package->BuildProgress->set_failed($error_message);
236
+ $package->Status = DUP_PackageStatus::ERROR;
237
+ $package->Update();
238
+ DUP_Log::error($error_message, "File does not look complete. Check permission on file and parent directory at [{$this->tempDbPath}]", $errorBehavior);
239
+ do_action('duplicator_lite_build_database_fail', $package);
240
+ } else {
241
+ do_action('duplicator_lite_build_database_completed', $package);
242
+ }
243
+
244
+ DUP_Log::Info("SQL FILE TIME: " . date("Y-m-d H:i:s"));
245
+ DUP_Log::Info("SQL RUNTIME: {$time_sum}");
246
+ $this->Size = is_file($this->tempDbPath) ? @filesize($this->tempDbPath) : 0;
247
+ $this->Package->setStatus(DUP_PackageStatus::DBDONE);
248
+ } catch (Exception $e) {
249
+ do_action('duplicator_lite_build_database_fail', $package);
250
+ DUP_Log::error("Runtime error in DUP_Database::Build. " . $e->getMessage(), "Exception: {$e}", $errorBehavior);
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Get the database meta-data such as tables as all there details
256
+ *
257
+ * @return array Returns an array full of meta-data about the database
258
+ */
259
+ public function getScannerData()
260
+ {
261
+ global $wpdb;
262
+ $filterTables = isset($this->FilterTables) ? explode(',', $this->FilterTables) : array();
263
+ $tblBaseCount = 0;
264
+ $tblCount = 0;
265
+ $tables = $this->getBaseTables();
266
+ $info = array();
267
+ $info['Status']['Success'] = is_null($tables) ? false : true;
268
+ //DB_Case for the database name is never checked on
269
+ $info['Status']['DB_Case'] = 'Good';
270
+ $info['Status']['DB_Rows'] = 'Good';
271
+ $info['Status']['DB_Size'] = 'Good';
272
+ $info['Status']['TBL_Case'] = 'Good';
273
+ $info['Status']['TBL_Rows'] = 'Good';
274
+ $info['Status']['TBL_Size'] = 'Good';
275
+ $info['Size'] = 0;
276
+ $info['Rows'] = 0;
277
+ $info['TableCount'] = 0;
278
+ $info['TableList'] = array();
279
+ $tblCaseFound = 0;
280
+ $tblRowsFound = 0;
281
+ $tblSizeFound = 0;
282
+ //Grab Table Stats
283
+ $filteredTables = array();
284
+ $this->info->tablesList = array();
285
+
286
+ foreach ($tables as $table) {
287
+ $tblBaseCount++;
288
+ $name = $table["name"];
289
+ if ($this->FilterOn && is_array($filterTables)) {
290
+ if (in_array($name, $filterTables)) {
291
+ continue;
292
+ }
293
+ }
294
+
295
+ $size = $table['size'];
296
+ $rows = empty($table["rows"]) ? '0' : $table["rows"];
297
+ $info['Size'] += $size;
298
+ $info['Rows'] += $rows;
299
+ $info['TableList'][$name]['Case'] = preg_match('/[A-Z]/', $name) ? 1 : 0;
300
+ $info['TableList'][$name]['Rows'] = number_format($rows);
301
+ $info['TableList'][$name]['Size'] = DUP_Util::byteSize($size);
302
+ $info['TableList'][$name]['USize'] = $size;
303
+ $filteredTables[] = $name;
304
+
305
+ if (($qRes = $GLOBALS['wpdb']->get_var("SELECT Count(*) FROM `{$name}`")) === null) {
306
+ $qRes = $rows;
307
+ }
308
+
309
+ $row_count = (int) $qRes;
310
+ $this->info->addTableInList($name, $rows, $size, $row_count);
311
+ $tblCount++;
312
+
313
+ // Table Uppercase
314
+ if ($info['TableList'][$name]['Case']) {
315
+ if (!$tblCaseFound) {
316
+ $tblCaseFound = 1;
317
+ }
318
+ }
319
+
320
+ //Table Row Count
321
+ if ($rows > DUPLICATOR_SCAN_DB_TBL_ROWS) {
322
+ if (!$tblRowsFound) {
323
+ $tblRowsFound = 1;
324
+ }
325
+ }
326
+
327
+ //Table Size
328
+ if ($size > DUPLICATOR_SCAN_DB_TBL_SIZE) {
329
+ if (!$tblSizeFound) {
330
+ $tblSizeFound = 1;
331
+ }
332
+ }
333
+ }
334
+
335
+ $this->setInfoObj($filteredTables);
336
+ $this->info->addTriggers();
337
+ $info['Status']['DB_Case'] = preg_match('/[A-Z]/', $wpdb->dbname) ? 'Warn' : 'Good';
338
+ $info['Status']['DB_Rows'] = ($info['Rows'] > DUPLICATOR_SCAN_DB_ALL_ROWS) ? 'Warn' : 'Good';
339
+ $info['Status']['DB_Size'] = ($info['Size'] > DUPLICATOR_SCAN_DB_ALL_SIZE) ? 'Warn' : 'Good';
340
+ $info['Status']['TBL_Case'] = ($tblCaseFound) ? 'Warn' : 'Good';
341
+ $info['Status']['TBL_Rows'] = ($tblRowsFound) ? 'Warn' : 'Good';
342
+ $info['Status']['TBL_Size'] = ($tblSizeFound) ? 'Warn' : 'Good';
343
+ $info['Status']['Triggers'] = count($this->info->triggerList) > 0 ? 'Warn' : 'Good';
344
+ $info['RawSize'] = $info['Size'];
345
+ $info['TableList'] = $info['TableList'] or "unknown";
346
+ $info['TableCount'] = $tblCount;
347
+ $this->info->isTablesUpperCase = $tblCaseFound;
348
+ $this->info->tablesBaseCount = $tblBaseCount;
349
+ $this->info->tablesFinalCount = $tblCount;
350
+ $this->info->tablesRowCount = (int) $info['Rows'];
351
+ $this->info->tablesSizeOnDisk = (int) $info['Size'];
352
+ $this->info->dbEngine = SnapDB::getDBEngine($wpdb->dbh);
353
+ $info['EasySize'] = DUP_Util::byteSize($info['Size']) or "unknown";
354
+
355
+ $this->info->viewCount = count($wpdb->get_results("SHOW FULL TABLES WHERE Table_Type = 'VIEW'", ARRAY_A));
356
+ $this->info->procCount = count($wpdb->get_results("SHOW PROCEDURE STATUS WHERE `Db`='" . DB_NAME . "'", ARRAY_A));
357
+ $this->info->funcCount = count($wpdb->get_results("SHOW FUNCTION STATUS WHERE `Db`='" . DB_NAME . "'", ARRAY_A));
358
+
359
+ return $info;
360
+ }
361
+
362
+ /**
363
+ * @param array &$filteredTables Filtered names of tables to include in collation search.
364
+ * Parameter does not change in the function, is passed by reference only to avoid copying.
365
+ *
366
+ * @return void
367
+ */
368
+ public function setInfoObj($filteredTables)
369
+ {
370
+ global $wpdb;
371
+ $this->info->buildMode = DUP_DB::getBuildMode();
372
+ $this->info->version = DUP_DB::getVersion();
373
+ $this->info->versionComment = DUP_DB::getVariable('version_comment');
374
+ $this->info->varLowerCaseTables = DUP_DB::getVariable('lower_case_table_names');
375
+ $this->info->name = $wpdb->dbname;
376
+ $this->info->isNameUpperCase = preg_match('/[A-Z]/', $wpdb->dbname) ? 1 : 0;
377
+ $this->info->charSetList = DUP_DB::getTableCharSetList($filteredTables);
378
+ $this->info->collationList = DUP_DB::getTableCollationList($filteredTables);
379
+ $this->info->engineList = DUP_DB::getTableEngineList($filteredTables);
380
+ }
381
+
382
+ /**
383
+ * Return list of base tables to dump
384
+ *
385
+ * @return array
386
+ */
387
+ protected function getBaseTables($nameOnly = false)
388
+ {
389
+ /** @var \wpdb $wpdb */
390
+ global $wpdb;
391
+
392
+ // (TABLE_NAME REGEXP '^rte4ed_(2|6)_' OR TABLE_NAME NOT REGEXP '^rte4ed_[0-9]+_')
393
+ $query = 'SELECT `TABLE_NAME` as `name`, `TABLE_ROWS` as `rows`, DATA_LENGTH + INDEX_LENGTH as `size` FROM `information_schema`.`tables`';
394
+
395
+ $where = array(
396
+ 'TABLE_SCHEMA = "' . esc_sql($wpdb->dbname) . '"',
397
+ 'TABLE_TYPE != "VIEW"'
398
+ );
399
+
400
+ $query .= ' WHERE ' . implode(' AND ', $where);
401
+ $query .= ' ORDER BY TABLE_NAME';
402
+
403
+ if ($nameOnly) {
404
+ return $wpdb->get_col($query, 0);
405
+ } else {
406
+ return $wpdb->get_results($query, ARRAY_A);
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Build the database script using mysqldump
412
+ *
413
+ * @return bool Returns true if the sql script was successfully created
414
+ */
415
+ private function mysqlDump($exePath)
416
+ {
417
+ global $wpdb;
418
+ require_once(DUPLICATOR_PLUGIN_PATH . 'classes/utilities/class.u.shell.php');
419
+ $host = SnapURL::parseUrl(DB_HOST, PHP_URL_HOST);
420
+ if (($port = SnapURL::parseUrl(DB_HOST, PHP_URL_PORT)) == false) {
421
+ $port = '';
422
+ }
423
+ $name = DB_NAME;
424
+ $mysqlcompat_on = isset($this->Compatible) && strlen($this->Compatible);
425
+ //Build command
426
+ $cmd = escapeshellarg($exePath);
427
+ $cmd .= ' --no-create-db';
428
+ $cmd .= ' --single-transaction';
429
+ $cmd .= ' --hex-blob';
430
+ $cmd .= ' --skip-add-drop-table';
431
+ $cmd .= ' --routines';
432
+ $cmd .= ' --quote-names';
433
+ $cmd .= ' --skip-comments';
434
+ $cmd .= ' --skip-set-charset';
435
+ $cmd .= ' --skip-triggers';
436
+ $cmd .= ' --allow-keywords';
437
+ $cmd .= ' --no-tablespaces';
438
+ //Compatibility mode
439
+ if ($mysqlcompat_on) {
440
+ DUP_Log::Info("COMPATIBLE: [{$this->Compatible}]");
441
+ $cmd .= " --compatible={$this->Compatible}";
442
+ }
443
+
444
+ //Filter tables
445
+ $res = $wpdb->get_results('SHOW FULL TABLES', ARRAY_N);
446
+ $tables = array();
447
+ $baseTables = array();
448
+ foreach ($res as $row) {
449
+ if (DUP_Util::isTableExists($row[0])) {
450
+ $tables[] = $row[0];
451
+ if ('BASE TABLE' == $row[1]) {
452
+ $baseTables[] = $row[0];
453
+ }
454
+ }
455
+ }
456
+ $filterTables = isset($this->FilterTables) ? explode(',', $this->FilterTables) : null;
457
+ $tblAllCount = count($tables);
458
+
459
+ //$tblFilterOn = ($this->FilterOn) ? 'ON' : 'OFF';
460
+ if (is_array($filterTables) && $this->FilterOn) {
461
+ foreach ($tables as $key => $val) {
462
+ if (in_array($tables[$key], $filterTables)) {
463
+ $cmd .= " --ignore-table={$name}.{$tables[$key]} ";
464
+ unset($tables[$key]);
465
+ }
466
+ }
467
+ }
468
+
469
+ $cmd .= ' -u ' . escapeshellarg(DB_USER);
470
+ $cmd .= (DB_PASSWORD) ?
471
+ ' -p' . DUP_Shell_U::escapeshellargWindowsSupport(DB_PASSWORD) : '';
472
+ $cmd .= ' -h ' . escapeshellarg($host);
473
+ $cmd .= (!empty($port) && is_numeric($port) ) ?
474
+ ' -P ' . $port : '';
475
+ $isPopenEnabled = DUP_Shell_U::isPopenEnabled();
476
+ if (!$isPopenEnabled) {
477
+ $cmd .= ' -r ' . escapeshellarg($this->tempDbPath);
478
+ }
479
+
480
+ $cmd .= ' ' . escapeshellarg(DB_NAME);
481
+ $cmd .= ' 2>&1';
482
+ if ($isPopenEnabled) {
483
+ $needToRewrite = false;
484
+ foreach ($tables as $tableName) {
485
+ $rewriteTableAs = $this->rewriteTableNameAs($tableName);
486
+ if ($tableName != $rewriteTableAs) {
487
+ $needToRewrite = true;
488
+ break;
489
+ }
490
+ }
491
+
492
+ if ($needToRewrite) {
493
+ $findReplaceTableNames = array();
494
+ // orignal table name => rewrite table name
495
+
496
+ foreach ($tables as $tableName) {
497
+ $rewriteTableAs = $this->rewriteTableNameAs($tableName);
498
+ if ($tableName != $rewriteTableAs) {
499
+ $findReplaceTableNames[$tableName] = $rewriteTableAs;
500
+ }
501
+ }
502
+ }
503
+
504
+ $firstLine = '';
505
+ DUP_LOG::trace("Executing mysql dump command by popen: $cmd");
506
+ $handle = popen($cmd, "r");
507
+ if ($handle) {
508
+ $sql_header = "/* DUPLICATOR-LITE (MYSQL-DUMP BUILD MODE) MYSQL SCRIPT CREATED ON : " . @date("Y-m-d H:i:s") . " */\n\n";
509
+ file_put_contents($this->tempDbPath, $sql_header, FILE_APPEND);
510
+ while (!feof($handle)) {
511
+ $line = fgets($handle);
512
+ //get ony one line
513
+ if ($line) {
514
+ if (empty($firstLine)) {
515
+ $firstLine = $line;
516
+ if (false !== stripos($line, 'Using a password on the command line interface can be insecure')) {
517
+ continue;
518
+ }
519
+ }
520
+
521
+ if ($needToRewrite) {
522
+ $replaceCount = 1;
523
+ if (preg_match('/CREATE TABLE `(.*?)`/', $line, $matches)) {
524
+ $tableName = $matches[1];
525
+ if (isset($findReplaceTableNames[$tableName])) {
526
+ $rewriteTableAs = $findReplaceTableNames[$tableName];
527
+ $line = str_replace('CREATE TABLE `' . $tableName . '`', 'CREATE TABLE `' . $rewriteTableAs . '`', $line, $replaceCount);
528
+ }
529
+ } elseif (preg_match('/INSERT INTO `(.*?)`/', $line, $matches)) {
530
+ $tableName = $matches[1];
531
+ if (isset($findReplaceTableNames[$tableName])) {
532
+ $rewriteTableAs = $findReplaceTableNames[$tableName];
533
+ $line = str_replace('INSERT INTO `' . $tableName . '`', 'INSERT INTO `' . $rewriteTableAs . '`', $line, $replaceCount);
534
+ }
535
+ } elseif (preg_match('/LOCK TABLES `(.*?)`/', $line, $matches)) {
536
+ $tableName = $matches[1];
537
+ if (isset($findReplaceTableNames[$tableName])) {
538
+ $rewriteTableAs = $findReplaceTableNames[$tableName];
539
+ $line = str_replace('LOCK TABLES `' . $tableName . '`', 'LOCK TABLES `' . $rewriteTableAs . '`', $line, $replaceCount);
540
+ }
541
+ }
542
+ }
543
+
544
+ file_put_contents($this->tempDbPath, $line, FILE_APPEND);
545
+ $output = "Ran from {$exePath}";
546
+ }
547
+ }
548
+ $mysqlResult = pclose($handle);
549
+ } else {
550
+ $output = '';
551
+ }
552
+
553
+ // Password bug > 5.6 (@see http://bugs.mysql.com/bug.php?id=66546)
554
+ if (empty($output) && trim($firstLine) === 'Warning: Using a password on the command line interface can be insecure.') {
555
+ $output = '';
556
+ }
557
+ } else {
558
+ DUP_LOG::trace("Executing mysql dump command $cmd");
559
+ exec($cmd, $output, $mysqlResult);
560
+ $output = implode("\n", $output);
561
+ // Password bug > 5.6 (@see http://bugs.mysql.com/bug.php?id=66546)
562
+ if (trim($output) === 'Warning: Using a password on the command line interface can be insecure.') {
563
+ $output = '';
564
+ }
565
+ $output = (strlen($output)) ? $output : "Ran from {$exePath}";
566
+ $tblCreateCount = count($tables);
567
+ $tblFilterCount = $tblAllCount - $tblCreateCount;
568
+ //DEBUG
569
+ //DUP_Log::Info("COMMAND: {$cmd}");
570
+ DUP_Log::Info("FILTERED: [{$this->FilterTables}]");
571
+ DUP_Log::Info("RESPONSE: {$output}");
572
+ DUP_Log::Info("TABLES: total:{$tblAllCount} | filtered:{$tblFilterCount} | create:{$tblCreateCount}");
573
+ }
574
+
575
+ $sql_footer = "\n\n/* Duplicator WordPress Timestamp: " . date("Y-m-d H:i:s") . "*/\n";
576
+ $sql_footer .= "/* " . DUPLICATOR_DB_EOF_MARKER . " */\n";
577
+ file_put_contents($this->tempDbPath, $sql_footer, FILE_APPEND);
578
+ if ($mysqlResult !== 0) {
579
+ /**
580
+ * -1 error command shell
581
+ * mysqldump return
582
+ * 0 - Success
583
+ * 1 - Warning
584
+ * 2 - Exception
585
+ */
586
+ DUP_Log::Info('MYSQL DUMP ERROR ' . print_r($mysqlResult, true));
587
+ DUP_Log::error(
588
+ __('Shell mysql dump error. Change SQL Mode to the "PHP Code" in the Duplicator > Settings > Packages.', 'duplicator'),
589
+ implode("\n", SnapIO::getLastLinesOfFile(
590
+ $this->tempDbPath,
591
+ DUPLICATOR_DB_MYSQLDUMP_ERROR_CONTAINING_LINE_COUNT,
592
+ DUPLICATOR_DB_MYSQLDUMP_ERROR_CHARS_IN_LINE_COUNT
593
+ )),
594
+ Dup_ErrorBehavior::ThrowException
595
+ );
596
+ return false;
597
+ }
598
+
599
+ return true;
600
+ }
601
+
602
+ /**
603
+ * Build the database script using php
604
+ *
605
+ * @return bool Returns true if the sql script was successfully created
606
+ */
607
+ private function phpDump($package)
608
+ {
609
+ global $wpdb;
610
+ $wpdb->query("SET session wait_timeout = " . DUPLICATOR_DB_MAX_TIME);
611
+ if (($handle = fopen($this->tempDbPath, 'w+')) == false) {
612
+ DUP_Log::error('[PHP DUMP] ERROR Can\'t open sbStorePath "' . $this->tempDbPath . '"', Dup_ErrorBehavior::ThrowException);
613
+ }
614
+ $tables = $wpdb->get_col("SHOW FULL TABLES WHERE Table_Type != 'VIEW'");
615
+ $filterTables = isset($this->FilterTables) ? explode(',', $this->FilterTables) : null;
616
+ $tblAllCount = count($tables);
617
+ //$tblFilterOn = ($this->FilterOn) ? 'ON' : 'OFF';
618
+ $qryLimit = DUP_Settings::Get('package_phpdump_qrylimit');
619
+ if (is_array($filterTables) && $this->FilterOn) {
620
+ foreach ($tables as $key => $val) {
621
+ if (in_array($tables[$key], $filterTables)) {
622
+ unset($tables[$key]);
623
+ }
624
+ }
625
+ }
626
+ $tblCreateCount = count($tables);
627
+ $tblFilterCount = $tblAllCount - $tblCreateCount;
628
+ DUP_Log::Info("TABLES: total:{$tblAllCount} | filtered:{$tblFilterCount} | create:{$tblCreateCount}");
629
+ DUP_Log::Info("FILTERED: [{$this->FilterTables}]");
630
+ //Added 'NO_AUTO_VALUE_ON_ZERO' at plugin version 1.2.12 to fix :
631
+ //**ERROR** database error write 'Invalid default value for for older mysql versions
632
+ $sql_header = "/* DUPLICATOR-LITE (PHP BUILD MODE) MYSQL SCRIPT CREATED ON : " . @date("Y-m-d H:i:s") . " */\n\n";
633
+ $sql_header .= "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n\n";
634
+ $sql_header .= "SET FOREIGN_KEY_CHECKS = 0;\n\n";
635
+ fwrite($handle, $sql_header);
636
+ //BUILD CREATES:
637
+ //All creates must be created before inserts do to foreign key constraints
638
+ foreach ($tables as $table) {
639
+ $rewrite_table_as = $this->rewriteTableNameAs($table);
640
+ $create = $wpdb->get_row("SHOW CREATE TABLE `{$table}`", ARRAY_N);
641
+ $count = 1;
642
+ $create_table_query = str_replace($table, $rewrite_table_as, $create[1], $count);
643
+ @fwrite($handle, "{$create_table_query};\n\n");
644
+ }
645
+
646
+ $procedures = $wpdb->get_col("SHOW PROCEDURE STATUS WHERE `Db` = '{$wpdb->dbname}'", 1);
647
+ if (count($procedures)) {
648
+ foreach ($procedures as $procedure) {
649
+ @fwrite($handle, "DELIMITER ;;\n");
650
+ $create = $wpdb->get_row("SHOW CREATE PROCEDURE `{$procedure}`", ARRAY_N);
651
+ @fwrite($handle, "{$create[2]} ;;\n");
652
+ @fwrite($handle, "DELIMITER ;\n\n");
653
+ }
654
+ }
655
+
656
+ $functions = $wpdb->get_col("SHOW FUNCTION STATUS WHERE `Db` = '{$wpdb->dbname}'", 1);
657
+ if (count($functions)) {
658
+ foreach ($functions as $function) {
659
+ @fwrite($handle, "DELIMITER ;;\n");
660
+ $create = $wpdb->get_row("SHOW CREATE FUNCTION `{$function}`", ARRAY_N);
661
+ @fwrite($handle, "{$create[2]} ;;\n");
662
+ @fwrite($handle, "DELIMITER ;\n\n");
663
+ }
664
+ }
665
+
666
+ $views = $wpdb->get_col("SHOW FULL TABLES WHERE Table_Type = 'VIEW'");
667
+ if (count($views)) {
668
+ foreach ($views as $view) {
669
+ $create = $wpdb->get_row("SHOW CREATE VIEW `{$view}`", ARRAY_N);
670
+ @fwrite($handle, "{$create[1]};\n\n");
671
+ }
672
+ }
673
+
674
+ @fwrite($handle, self::TABLE_CREATION_END_MARKER . "\n");
675
+
676
+ $table_count = count($tables);
677
+ $table_number = 0;
678
+ //BUILD INSERTS:
679
+ //Create Insert in 100 row increments to better handle memory
680
+ foreach ($tables as $table) {
681
+ $table_number++;
682
+ if ($table_number % 2 == 0) {
683
+ $this->Package->Status = SnapUtil::getWorkPercent(DUP_PackageStatus::DBSTART, DUP_PackageStatus::DBDONE, $table_count, $table_number);
684
+ $this->Package->update();
685
+ }
686
+
687
+ $row_count = $wpdb->get_var("SELECT Count(*) FROM `{$table}`");
688
+ $rewrite_table_as = $this->rewriteTableNameAs($table);
689
+
690
+ if ($row_count > $qryLimit) {
691
+ $row_count = ceil($row_count / $qryLimit);
692
+ } elseif ($row_count > 0) {
693
+ $row_count = 1;
694
+ }
695
+
696
+ if ($row_count >= 1) {
697
+ fwrite($handle, "\n/* INSERT TABLE DATA: {$table} */\n");
698
+ }
699
+
700
+ for ($i = 0; $i < $row_count; $i++) {
701
+ $sql = "";
702
+ $limit = $i * $qryLimit;
703
+ $query = "SELECT * FROM `{$table}` LIMIT {$limit}, {$qryLimit}";
704
+ $rows = $wpdb->get_results($query, ARRAY_A);
705
+ $select_last_error = $wpdb->last_error;
706
+ if ('' !== $select_last_error) {
707
+ $fix = esc_html__('Please contact your DataBase administrator to fix the error.', 'duplicator');
708
+ $errorMessage = $select_last_error . ' ' . $fix . '.';
709
+ $package->BuildProgress->set_failed($errorMessage);
710
+ $package->BuildProgress->failed = true;
711
+ $package->failed = true;
712
+ $package->Status = DUP_PackageStatus::ERROR;
713
+ $package->Update();
714
+ DUP_Log::error($select_last_error, $fix, Dup_ErrorBehavior::ThrowException);
715
+ return;
716
+ }
717
+
718
+ if (is_array($rows)) {
719
+ foreach ($rows as $row) {
720
+ $sql .= "INSERT INTO `{$rewrite_table_as}` VALUES(";
721
+ $num_values = count($row);
722
+ $num_counter = 1;
723
+ foreach ($row as $value) {
724
+ if (is_null($value) || !isset($value)) {
725
+ ($num_values == $num_counter) ? $sql .= 'NULL' : $sql .= 'NULL, ';
726
+ } else {
727
+ ($num_values == $num_counter) ? $sql .= '"' . DUP_DB::escSQL($value, true) . '"' : $sql .= '"' . DUP_DB::escSQL($value, true) . '", ';
728
+ }
729
+ $num_counter++;
730
+ }
731
+ $sql .= ");\n";
732
+ }
733
+ fwrite($handle, $sql);
734
+ }
735
+ }
736
+
737
+ //Flush buffer if enabled
738
+ if ($this->networkFlush) {
739
+ DUP_Util::fcgiFlush();
740
+ }
741
+ $sql = null;
742
+ $rows = null;
743
+ }
744
+
745
+ $sql_footer = "\nSET FOREIGN_KEY_CHECKS = 1; \n\n";
746
+ $sql_footer .= "/* Duplicator WordPress Timestamp: " . date("Y-m-d H:i:s") . "*/\n";
747
+ $sql_footer .= "/* " . DUPLICATOR_DB_EOF_MARKER . " */\n";
748
+ fwrite($handle, $sql_footer);
749
+ $wpdb->flush();
750
+ fclose($handle);
751
+ }
752
+
753
+ private function rewriteTableNameAs($table)
754
+ {
755
+ $table_prefix = $this->getTablePrefix();
756
+ if (!isset($this->sameNameTableExists)) {
757
+ global $wpdb;
758
+ $this->sameNameTableExists = false;
759
+ $all_tables = $wpdb->get_col("SHOW FULL TABLES WHERE Table_Type != 'VIEW'");
760
+ foreach ($all_tables as $table_name) {
761
+ if (strtolower($table_name) != $table_name && in_array(strtolower($table_name), $all_tables)) {
762
+ $this->sameNameTableExists = true;
763
+ break;
764
+ }
765
+ }
766
+ }
767
+ if (false === $this->sameNameTableExists && 0 === stripos($table, $table_prefix) && 0 !== strpos($table, $table_prefix)) {
768
+ $post_fix = substr($table, strlen($table_prefix));
769
+ $rewrite_table_name = $table_prefix . $post_fix;
770
+ } else {
771
+ $rewrite_table_name = $table;
772
+ }
773
+ return $rewrite_table_name;
774
+ }
775
+
776
+ private function getTablePrefix()
777
+ {
778
+ global $wpdb;
779
+ $table_prefix = (is_multisite() && !defined('MULTISITE')) ? $wpdb->base_prefix : $wpdb->get_blog_prefix(0);
780
+ return $table_prefix;
781
+ }
782
+
783
+ public function getUrl()
784
+ {
785
+ return DUP_Settings::getSsdirUrl() . "/" . $this->File;
786
+ }
787
+ }
classes/package/class.pack.installer.php CHANGED
@@ -1,551 +1,1043 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- // Exit if accessed directly
4
- /* @var $global DUP_Global_Entity */
5
-
6
- require_once(DUPLICATOR_PLUGIN_PATH.'/classes/class.archive.config.php');
7
- require_once(DUPLICATOR_PLUGIN_PATH.'/classes/utilities/class.u.zip.php');
8
- require_once(DUPLICATOR_PLUGIN_PATH.'/classes/utilities/class.u.multisite.php');
9
- require_once(DUPLICATOR_PLUGIN_PATH.'/classes/class.password.php');
10
-
11
- class DUP_Installer
12
- {
13
- const INSTALLER_SERVER_EXTENSION = '.php.bak';
14
- const DEFAULT_INSTALLER_FILE_NAME_WITHOUT_HASH = 'installer';
15
-
16
- //PUBLIC
17
- public $File;
18
- public $Size = 0;
19
- public $OptsDBHost;
20
- public $OptsDBPort;
21
- public $OptsDBName;
22
- public $OptsDBUser;
23
- public $OptsDBCharset;
24
- public $OptsDBCollation;
25
- public $OptsSecureOn = 0;
26
- public $OptsSecurePass;
27
- public $numFilesAdded = 0;
28
- public $numDirsAdded = 0;
29
- //PROTECTED
30
- protected $Package;
31
-
32
- /**
33
- * Init this object
34
- */
35
- function __construct($package)
36
- {
37
- $this->Package = $package;
38
- }
39
-
40
- public function build($package, $error_behavior = Dup_ErrorBehavior::Quit)
41
- {
42
- DUP_Log::Info("building installer");
43
-
44
- $this->Package = $package;
45
- $success = false;
46
-
47
- if ($this->create_enhanced_installer_files()) {
48
- $success = $this->add_extra_files($package);
49
- } else {
50
- DUP_Log::Info("error creating enhanced installer files");
51
- }
52
-
53
-
54
- if ($success) {
55
- // No longer need to store wp-config.txt file in main storage area
56
- $temp_conf_ark_file_path = $this->getTempWPConfArkFilePath();
57
- @unlink($temp_conf_ark_file_path);
58
-
59
- $package->BuildProgress->installer_built = true;
60
- } else {
61
- $error_message = 'Error adding installer';
62
- $package->BuildProgress->set_failed($error_message);
63
- $package->Status = DUP_PackageStatus::ERROR;
64
- $package->Update();
65
-
66
- DUP_Log::error($error_message, "Marking build progress as failed because couldn't add installer files", $error_behavior);
67
- //$package->BuildProgress->failed = true;
68
- //$package->setStatus(DUP_PackageStatus::ERROR);
69
- }
70
-
71
- return $success;
72
- }
73
-
74
- private function create_enhanced_installer_files()
75
- {
76
- $success = false;
77
- if ($this->create_enhanced_installer()) {
78
- $success = $this->create_archive_config_file();
79
- }
80
- return $success;
81
- }
82
-
83
- private function create_enhanced_installer()
84
- {
85
- $success = true;
86
- $archive_filepath = DUP_Settings::getSsdirTmpPath()."/{$this->Package->Archive->File}";
87
- $installer_filepath = apply_filters(
88
- 'duplicator_installer_file_path',
89
- DUP_Settings::getSsdirTmpPath()."/{$this->Package->NameHash}_installer" . self::INSTALLER_SERVER_EXTENSION
90
- );
91
- $template_filepath = DUPLICATOR_PLUGIN_PATH.'/installer/installer.tpl';
92
- $mini_expander_filepath = DUPLICATOR_PLUGIN_PATH.'/lib/dup_archive/classes/class.duparchive.mini.expander.php';
93
-
94
- // Replace the @@ARCHIVE@@ token
95
- $installer_contents = file_get_contents($template_filepath);
96
-
97
- if (DUP_Settings::Get('archive_build_mode') == DUP_Archive_Build_Mode::DupArchive) {
98
- $mini_expander_string = file_get_contents($mini_expander_filepath);
99
-
100
- if ($mini_expander_string === false) {
101
- DUP_Log::error(DUP_U::__('Error reading DupArchive mini expander'), DUP_U::__('Error reading DupArchive mini expander'), Dup_ErrorBehavior::LogOnly);
102
- return false;
103
- }
104
- } else {
105
- $mini_expander_string = '';
106
- }
107
-
108
- $search_array = array('@@ARCHIVE@@', '@@VERSION@@', '@@ARCHIVE_SIZE@@', '@@PACKAGE_HASH@@', '@@SECONDARY_PACKAGE_HASH@@', '@@DUPARCHIVE_MINI_EXPANDER@@');
109
- $package_hash = $this->Package->getPackageHash();
110
- $secondary_package_hash = $this->Package->getSecondaryPackageHash();
111
- $replace_array = array($this->Package->Archive->File, DUPLICATOR_VERSION, @filesize($archive_filepath), $package_hash, $secondary_package_hash, $mini_expander_string);
112
- $installer_contents = str_replace($search_array, $replace_array, $installer_contents);
113
-
114
- if (@file_put_contents($installer_filepath, $installer_contents) === false) {
115
- DUP_Log::error(esc_html__('Error writing installer contents', 'duplicator'), esc_html__("Couldn't write to $installer_filepath", 'duplicator'));
116
- $success = false;
117
- }
118
-
119
- if ($success) {
120
- $storePath = DUP_Settings::getSsdirTmpPath()."/{$this->File}";
121
- $this->Size = @filesize($storePath);
122
- }
123
-
124
- return $success;
125
- }
126
-
127
- /**
128
- * Create archive.txt file */
129
- private function create_archive_config_file()
130
- {
131
- global $wpdb;
132
-
133
- $success = true;
134
- $archive_config_filepath = DUP_Settings::getSsdirTmpPath()."/{$this->Package->NameHash}_archive.txt";
135
- $ac = new DUP_Archive_Config();
136
- $extension = strtolower($this->Package->Archive->Format);
137
-
138
- $hasher = new DUP_PasswordHash(8, FALSE);
139
- $pass_hash = $hasher->HashPassword($this->Package->Installer->OptsSecurePass);
140
-
141
- $this->Package->Database->getScannerData();
142
-
143
- //READ-ONLY: COMPARE VALUES
144
- $ac->created = $this->Package->Created;
145
- $ac->version_dup = DUPLICATOR_VERSION;
146
- $ac->version_wp = $this->Package->VersionWP;
147
- $ac->version_db = $this->Package->VersionDB;
148
- $ac->version_php = $this->Package->VersionPHP;
149
- $ac->version_os = $this->Package->VersionOS;
150
- $ac->dup_type = 'lite';
151
- $ac->dbInfo = $this->Package->Database->info;
152
-
153
- //READ-ONLY: GENERAL
154
- // $ac->installer_base_name = $global->installer_base_name;
155
- $ac->installer_base_name = 'installer' . self::INSTALLER_SERVER_EXTENSION;
156
- $ac->installer_backup_name = $this->Package->NameHash.'_installer-backup.php';
157
- $ac->package_name = "{$this->Package->NameHash}_archive.{$extension}";
158
- $ac->package_hash = $this->Package->getPackageHash();
159
- $ac->package_notes = $this->Package->Notes;
160
- $ac->url_old = get_option('siteurl');
161
- $ac->opts_delete = DupLiteSnapJsonU::wp_json_encode_pprint($GLOBALS['DUPLICATOR_OPTS_DELETE']);
162
- $ac->blogname = esc_html(get_option('blogname'));
163
-
164
- $abs_path = duplicator_get_abs_path();
165
- $ac->wproot = $abs_path;
166
- $ac->relative_content_dir = str_replace($abs_path, '', WP_CONTENT_DIR);
167
- $ac->exportOnlyDB = $this->Package->Archive->ExportOnlyDB;
168
- $ac->installSiteOverwriteOn = DUPLICATOR_INSTALL_SITE_OVERWRITE_ON;
169
- $ac->wplogin_url = wp_login_url();
170
-
171
- //PRE-FILLED: GENERAL
172
- $ac->secure_on = $this->Package->Installer->OptsSecureOn;
173
- $ac->secure_pass = $pass_hash;
174
- $ac->skipscan = false;
175
- $ac->dbhost = $this->Package->Installer->OptsDBHost;
176
- $ac->dbname = $this->Package->Installer->OptsDBName;
177
- $ac->dbuser = $this->Package->Installer->OptsDBUser;
178
- $ac->dbpass = '';
179
- $ac->dbcharset = $this->Package->Installer->OptsDBCharset;
180
- $ac->dbcollation = $this->Package->Installer->OptsDBCollation;
181
-
182
- $ac->wp_tableprefix = $wpdb->base_prefix;
183
-
184
- $ac->mu_mode = DUP_MU::getMode();
185
- $ac->is_outer_root_wp_config_file = (!file_exists($abs_path.'/wp-config.php')) ? true : false;
186
- $ac->is_outer_root_wp_content_dir = $this->Package->Archive->isOuterWPContentDir();
187
-
188
- $json = DupLiteSnapJsonU::wp_json_encode_pprint($ac);
189
- DUP_Log::TraceObject('json', $json);
190
-
191
- if (file_put_contents($archive_config_filepath, $json) === false) {
192
- DUP_Log::error("Error writing archive config", "Couldn't write archive config at $archive_config_filepath", Dup_ErrorBehavior::LogOnly);
193
- $success = false;
194
- }
195
-
196
- return $success;
197
- }
198
-
199
- /**
200
- * Puts an installer zip file in the archive for backup purposes.
201
- */
202
- private function add_extra_files($package)
203
- {
204
- $success = false;
205
- $installer_filepath = apply_filters(
206
- 'duplicator_installer_file_path',
207
- DUP_Settings::getSsdirTmpPath()."/{$this->Package->NameHash}_installer" . self::INSTALLER_SERVER_EXTENSION
208
- );
209
- $scan_filepath = DUP_Settings::getSsdirTmpPath()."/{$this->Package->NameHash}_scan.json";
210
- $sql_filepath = DUP_Settings::getSsdirTmpPath()."/{$this->Package->Database->File}";
211
- $archive_filepath = DUP_Settings::getSsdirTmpPath()."/{$this->Package->Archive->File}";
212
- $archive_config_filepath = DUP_Settings::getSsdirTmpPath()."/{$this->Package->NameHash}_archive.txt";
213
-
214
- DUP_Log::Info("add_extra_files1");
215
-
216
- if (file_exists($installer_filepath) == false) {
217
- DUP_Log::error("Installer $installer_filepath not present", '', Dup_ErrorBehavior::LogOnly);
218
- return false;
219
- }
220
-
221
- DUP_Log::Info("add_extra_files2");
222
- if (file_exists($sql_filepath) == false) {
223
- DUP_Log::error("Database SQL file $sql_filepath not present", '', Dup_ErrorBehavior::LogOnly);
224
- return false;
225
- }
226
-
227
- DUP_Log::Info("add_extra_files3");
228
- if (file_exists($archive_config_filepath) == false) {
229
- DUP_Log::error("Archive configuration file $archive_config_filepath not present", '', Dup_ErrorBehavior::LogOnly);
230
- return false;
231
- }
232
-
233
- DUP_Log::Info("add_extra_files4");
234
- if ($package->Archive->file_count != 2) {
235
- DUP_Log::Info("Doing archive file check");
236
- // Only way it's 2 is if the root was part of the filter in which case the archive won't be there
237
- DUP_Log::Info("add_extra_files5");
238
- if (file_exists($archive_filepath) == false) {
239
-
240
- DUP_Log::error("$error_text. **RECOMMENDATION: $fix_text", '', Dup_ErrorBehavior::LogOnly);
241
-
242
- return false;
243
- }
244
- DUP_Log::Info("add_extra_files6");
245
- }
246
-
247
- $wpconfig_filepath = $package->Archive->getWPConfigFilePath();
248
-
249
- if ($package->Archive->Format == 'DAF') {
250
- DUP_Log::Info("add_extra_files7");
251
- $success = $this->add_extra_files_using_duparchive($installer_filepath, $scan_filepath, $sql_filepath, $archive_filepath, $archive_config_filepath, $wpconfig_filepath);
252
- } else {
253
- DUP_Log::Info("add_extra_files8");
254
- $success = $this->add_extra_files_using_ziparchive($installer_filepath, $scan_filepath, $sql_filepath, $archive_filepath, $archive_config_filepath, $wpconfig_filepath);
255
- }
256
-
257
- // No sense keeping the archive config around
258
- @unlink($archive_config_filepath);
259
- $package->Archive->Size = @filesize($archive_filepath);
260
-
261
- return $success;
262
- }
263
-
264
- private function add_extra_files_using_duparchive($installer_filepath, $scan_filepath, $sql_filepath, $archive_filepath, $archive_config_filepath, $wpconfig_filepath)
265
- {
266
- $success = false;
267
-
268
- try {
269
- DUP_Log::Info("add_extra_files_using_da1");
270
- $htaccess_filepath = $this->getHtaccessFilePath();
271
- $webconf_filepath = duplicator_get_abs_path().'/web.config';
272
-
273
- $logger = new DUP_DupArchive_Logger();
274
-
275
- DupArchiveEngine::init($logger, 'DUP_Log::profile');
276
-
277
- $embedded_scan_ark_file_path = $this->getEmbeddedScanFilePath();
278
- DupArchiveEngine::addRelativeFileToArchiveST($archive_filepath, $scan_filepath, $embedded_scan_ark_file_path);
279
- $this->numFilesAdded++;
280
-
281
- if (file_exists($htaccess_filepath)) {
282
- $htaccess_ark_file_path = $this->getHtaccessArkFilePath();
283
- try {
284
- DupArchiveEngine::addRelativeFileToArchiveST($archive_filepath, $htaccess_filepath, $htaccess_ark_file_path);
285
- $this->numFilesAdded++;
286
- }
287
- catch (Exception $ex) {
288
- // Non critical so bury exception
289
- }
290
- }
291
-
292
- if (file_exists($webconf_filepath)) {
293
- try {
294
- DupArchiveEngine::addRelativeFileToArchiveST($archive_filepath, $webconf_filepath, DUPLICATOR_WEBCONFIG_ORIG_FILENAME);
295
- $this->numFilesAdded++;
296
- }
297
- catch (Exception $ex) {
298
- // Non critical so bury exception
299
- }
300
- }
301
-
302
- if (file_exists($wpconfig_filepath)) {
303
- $conf_ark_file_path = $this->getWPConfArkFilePath();
304
- $temp_conf_ark_file_path = $this->getTempWPConfArkFilePath();
305
- if (copy($wpconfig_filepath, $temp_conf_ark_file_path)) {
306
- $this->cleanTempWPConfArkFilePath($temp_conf_ark_file_path);
307
- DupArchiveEngine::addRelativeFileToArchiveST($archive_filepath, $temp_conf_ark_file_path, $conf_ark_file_path);
308
- } else {
309
- DupArchiveEngine::addRelativeFileToArchiveST($archive_filepath, $wpconfig_filepath, $conf_ark_file_path);
310
- }
311
- $this->numFilesAdded++;
312
- }
313
-
314
- $this->add_installer_files_using_duparchive($archive_filepath, $installer_filepath, $archive_config_filepath);
315
-
316
- $success = true;
317
- }
318
- catch (Exception $ex) {
319
- DUP_Log::error("Error adding installer files to archive. ", $ex->getMessage(), Dup_ErrorBehavior::ThrowException);
320
- }
321
-
322
- return $success;
323
- }
324
-
325
- private function add_installer_files_using_duparchive($archive_filepath, $installer_filepath, $archive_config_filepath)
326
- {
327
- $installer_backup_filename = $this->Package->NameHash.'_installer-backup.php';
328
-
329
-
330
- DUP_Log::Info('Adding enhanced installer files to archive using DupArchive');
331
- DupArchiveEngine::addRelativeFileToArchiveST($archive_filepath, $installer_filepath, $installer_backup_filename);
332
-
333
- $this->numFilesAdded++;
334
-
335
- $base_installer_directory = DUPLICATOR_PLUGIN_PATH.'installer';
336
- $installer_directory = "$base_installer_directory/dup-installer";
337
-
338
- $counts = DupArchiveEngine::addDirectoryToArchiveST($archive_filepath, $installer_directory, $base_installer_directory, true);
339
- $this->numFilesAdded += $counts->numFilesAdded;
340
- $this->numDirsAdded += $counts->numDirsAdded;
341
-
342
- $archive_config_relative_path = $this->getArchiveTxtFilePath();
343
-
344
- DupArchiveEngine::addRelativeFileToArchiveST($archive_filepath, $archive_config_filepath, $archive_config_relative_path);
345
- $this->numFilesAdded++;
346
-
347
- // Include dup archive
348
- $duparchive_lib_directory = DUPLICATOR_PLUGIN_PATH.'lib/dup_archive';
349
- $duparchive_lib_counts = DupArchiveEngine::addDirectoryToArchiveST($archive_filepath, $duparchive_lib_directory, DUPLICATOR_PLUGIN_PATH, true, 'dup-installer/');
350
- $this->numFilesAdded += $duparchive_lib_counts->numFilesAdded;
351
- $this->numDirsAdded += $duparchive_lib_counts->numDirsAdded;
352
-
353
- // Include snaplib
354
- $snaplib_directory = DUPLICATOR_PLUGIN_PATH.'lib/snaplib';
355
- $snaplib_counts = DupArchiveEngine::addDirectoryToArchiveST($archive_filepath, $snaplib_directory, DUPLICATOR_PLUGIN_PATH, true, 'dup-installer/');
356
- $this->numFilesAdded += $snaplib_counts->numFilesAdded;
357
- $this->numDirsAdded += $snaplib_counts->numDirsAdded;
358
-
359
- // Include fileops
360
- $fileops_directory = DUPLICATOR_PLUGIN_PATH.'lib/fileops';
361
- $fileops_counts = DupArchiveEngine::addDirectoryToArchiveST($archive_filepath, $fileops_directory, DUPLICATOR_PLUGIN_PATH, true, 'dup-installer/');
362
- $this->numFilesAdded += $fileops_counts->numFilesAdded;
363
- $this->numDirsAdded += $fileops_counts->numDirsAdded;
364
-
365
- // Include config
366
- $config_directory = DUPLICATOR_PLUGIN_PATH.'lib/config';
367
- $config_counts = DupArchiveEngine::addDirectoryToArchiveST($archive_filepath, $config_directory, DUPLICATOR_PLUGIN_PATH, true, 'dup-installer/');
368
- $this->numFilesAdded += $config_counts->numFilesAdded;
369
- $this->numDirsAdded += $fileops_counts->numDirsAdded;
370
- }
371
-
372
- private function add_extra_files_using_ziparchive($installer_filepath, $scan_filepath, $sql_filepath, $zip_filepath, $archive_config_filepath, $wpconfig_filepath)
373
- {
374
- $htaccess_filepath = $this->getHtaccessFilePath();
375
- $webconfig_filepath = duplicator_get_abs_path().'/web.config';
376
-
377
- $success = false;
378
- $zipArchive = new ZipArchive();
379
-
380
- if ($zipArchive->open($zip_filepath, ZIPARCHIVE::CREATE) === TRUE) {
381
- DUP_Log::Info("Successfully opened zip $zip_filepath");
382
-
383
- if (file_exists($htaccess_filepath)) {
384
- $htaccess_ark_file_path = $this->getHtaccessArkFilePath();
385
- DUP_Zip_U::addFileToZipArchive($zipArchive, $htaccess_filepath, $htaccess_ark_file_path, true);
386
- }
387
-
388
- if (file_exists($webconfig_filepath)) {
389
- DUP_Zip_U::addFileToZipArchive($zipArchive, $webconfig_filepath, DUPLICATOR_WEBCONFIG_ORIG_FILENAME, true);
390
- }
391
-
392
- if (!empty($wpconfig_filepath)) {
393
- $conf_ark_file_path = $this->getWPConfArkFilePath();
394
- $temp_conf_ark_file_path = $this->getTempWPConfArkFilePath();
395
- if (copy($wpconfig_filepath, $temp_conf_ark_file_path)) {
396
- $this->cleanTempWPConfArkFilePath($temp_conf_ark_file_path);
397
- DUP_Zip_U::addFileToZipArchive($zipArchive, $temp_conf_ark_file_path, $conf_ark_file_path, true);
398
- } else {
399
- DUP_Zip_U::addFileToZipArchive($zipArchive, $wpconfig_filepath, $conf_ark_file_path, true);
400
- }
401
- }
402
-
403
- $embedded_scan_file_path = $this->getEmbeddedScanFilePath();
404
- if (DUP_Zip_U::addFileToZipArchive($zipArchive, $scan_filepath, $embedded_scan_file_path, true)) {
405
- if ($this->add_installer_files_using_zip_archive($zipArchive, $installer_filepath, $archive_config_filepath, true)) {
406
- DUP_Log::info("Installer files added to archive");
407
- DUP_Log::info("Added to archive");
408
-
409
- $success = true;
410
- } else {
411
- DUP_Log::error("Unable to add enhanced enhanced installer files to archive.", '', Dup_ErrorBehavior::LogOnly);
412
- }
413
- } else {
414
- DUP_Log::error("Unable to add scan file to archive.", '', Dup_ErrorBehavior::LogOnly);
415
- }
416
-
417
- if ($zipArchive->close() === false) {
418
- DUP_Log::error("Couldn't close archive when adding extra files.", '');
419
- $success = false;
420
- }
421
-
422
- DUP_Log::Info('After ziparchive close when adding installer');
423
- }
424
-
425
- return $success;
426
- }
427
-
428
- // Add installer directory to the archive and the archive.cfg
429
- private function add_installer_files_using_zip_archive(&$zip_archive, $installer_filepath, $archive_config_filepath, $is_compressed)
430
- {
431
- $success = false;
432
- $installer_backup_filename = $this->Package->NameHash.'_installer-backup.php';
433
-
434
- DUP_Log::Info('Adding enhanced installer files to archive using ZipArchive');
435
-
436
- if (DUP_Zip_U::addFileToZipArchive($zip_archive, $installer_filepath, $installer_backup_filename, true)) {
437
- DUPLICATOR_PLUGIN_PATH.'installer/';
438
- $installer_directory = DUPLICATOR_PLUGIN_PATH.'installer/dup-installer';
439
-
440
- if (DUP_Zip_U::addDirWithZipArchive($zip_archive, $installer_directory, true, '', $is_compressed)) {
441
- $archive_config_local_name = $this->getArchiveTxtFilePath();
442
-
443
- if (DUP_Zip_U::addFileToZipArchive($zip_archive, $archive_config_filepath, $archive_config_local_name, true)) {
444
-
445
- $snaplib_directory = DUPLICATOR_PLUGIN_PATH.'lib/snaplib';
446
- $config_directory = DUPLICATOR_PLUGIN_PATH.'lib/config';
447
-
448
- if (DUP_Zip_U::addDirWithZipArchive($zip_archive, $snaplib_directory, true, 'dup-installer/lib/', $is_compressed) &&
449
- DUP_Zip_U::addDirWithZipArchive($zip_archive, $config_directory, true, 'dup-installer/lib/', $is_compressed)
450
- ) {
451
- $success = true;
452
- } else {
453
- DUP_Log::error("Error adding directory {$snaplib_directory} and {$config_directory} to zipArchive", '', Dup_ErrorBehavior::LogOnly);
454
- }
455
- } else {
456
- DUP_Log::error("Error adding $archive_config_filepath to zipArchive", '', Dup_ErrorBehavior::LogOnly);
457
- }
458
- } else {
459
- DUP_Log::error("Error adding directory $installer_directory to zipArchive", '', Dup_ErrorBehavior::LogOnly);
460
- }
461
- } else {
462
- DUP_Log::error("Error adding backup installer file to zipArchive", '', Dup_ErrorBehavior::LogOnly);
463
- }
464
-
465
- return $success;
466
- }
467
-
468
- /**
469
- * Get .htaccess file path
470
- *
471
- * @return string
472
- */
473
- private function getHtaccessFilePath()
474
- {
475
- return duplicator_get_abs_path().'/.htaccess';
476
- }
477
-
478
- /**
479
- * Get .htaccss in archive file
480
- *
481
- * @return string
482
- */
483
- private function getHtaccessArkFilePath()
484
- {
485
- $packageHash = $this->Package->getPackageHash();
486
- $htaccessArkFilePath = '.htaccess__'.$packageHash;
487
- return $htaccessArkFilePath;
488
- }
489
-
490
- /**
491
- * Get wp-config.php file path along with name in archive file
492
- */
493
- private function getWPConfArkFilePath()
494
- {
495
- if (DUPLICATOR_INSTALL_SITE_OVERWRITE_ON) {
496
- $package_hash = $this->Package->getPackageHash();
497
- $conf_ark_file_path = 'dup-wp-config-arc__'.$package_hash.'.txt';
498
- } else {
499
- $conf_ark_file_path = 'wp-config.php';
500
- }
501
- return $conf_ark_file_path;
502
- }
503
-
504
- /**
505
- * Get temp wp-config.php file path along with name in temp folder
506
- */
507
- private function getTempWPConfArkFilePath()
508
- {
509
- $temp_conf_ark_file_path = DUP_Settings::getSsdirTmpPath().'/'.$this->Package->NameHash.'_wp-config.txt';
510
- return $temp_conf_ark_file_path;
511
- }
512
-
513
- /**
514
- * Clear out sensitive database connection information
515
- *
516
- * @param $temp_conf_ark_file_path Temp config file path
517
- */
518
- private static function cleanTempWPConfArkFilePath($temp_conf_ark_file_path)
519
- {
520
- if (function_exists('token_get_all')) {
521
- require_once(DUPLICATOR_PLUGIN_PATH.'lib/config/class.wp.config.tranformer.php');
522
- $transformer = new DupLiteWPConfigTransformer($temp_conf_ark_file_path);
523
- $constants = array('DB_NAME', 'DB_USER', 'DB_PASSWORD', 'DB_HOST');
524
- foreach ($constants as $constant) {
525
- if ($transformer->exists('constant', $constant)) {
526
- $transformer->update('constant', $constant, '');
527
- }
528
- }
529
- }
530
- }
531
-
532
- /**
533
- * Get scan.json file path along with name in archive file
534
- */
535
- private function getEmbeddedScanFilePath()
536
- {
537
- $package_hash = $this->Package->getPackageHash();
538
- $embedded_scan_ark_file_path = 'dup-installer/dup-scan__'.$package_hash.'.json';
539
- return $embedded_scan_ark_file_path;
540
- }
541
-
542
- /**
543
- * Get archive.txt file path along with name in archive file
544
- */
545
- private function getArchiveTxtFilePath()
546
- {
547
- $package_hash = $this->Package->getPackageHash();
548
- $archive_txt_file_path = 'dup-installer/dup-archive__'.$package_hash.'.txt';
549
- return $archive_txt_file_path;
550
- }
551
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ use Duplicator\Libs\DupArchive\DupArchiveEngine;
4
+ use Duplicator\Libs\Snap\SnapCode;
5
+ use Duplicator\Libs\Snap\SnapIO;
6
+ use Duplicator\Libs\Snap\SnapJson;
7
+ use Duplicator\Libs\Snap\SnapOrigFileManager;
8
+ use Duplicator\Libs\Snap\SnapWP;
9
+ use Duplicator\Libs\WpConfig\WPConfigTransformer;
10
+
11
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
12
+ // Exit if accessed directly
13
+ /* @var $global DUP_Global_Entity */
14
+
15
+ require_once(DUPLICATOR_PLUGIN_PATH . '/classes/class.archive.config.php');
16
+ require_once(DUPLICATOR_PLUGIN_PATH . '/classes/utilities/class.u.zip.php');
17
+ require_once(DUPLICATOR_PLUGIN_PATH . '/classes/utilities/class.u.multisite.php');
18
+ require_once(DUPLICATOR_PLUGIN_PATH . '/classes/class.password.php');
19
+ class DUP_Installer
20
+ {
21
+ const INSTALLER_SERVER_EXTENSION = '.php.bak';
22
+ const DEFAULT_INSTALLER_FILE_NAME_WITHOUT_HASH = 'installer';
23
+ const CONFIG_ORIG_FILE_FOLDER_PREFIX = 'source_site_';
24
+ const CONFIG_ORIG_FILE_USERINI_ID = 'userini';
25
+ const CONFIG_ORIG_FILE_HTACCESS_ID = 'htaccess';
26
+ const CONFIG_ORIG_FILE_WPCONFIG_ID = 'wpconfig';
27
+ const CONFIG_ORIG_FILE_PHPINI_ID = 'phpini';
28
+ const CONFIG_ORIG_FILE_WEBCONFIG_ID = 'webconfig';
29
+
30
+ //PUBLIC
31
+ public $File;
32
+ public $Size = 0;
33
+ public $OptsDBHost;
34
+ public $OptsDBPort;
35
+ public $OptsDBName;
36
+ public $OptsDBUser;
37
+ public $OptsDBCharset;
38
+ public $OptsDBCollation;
39
+ public $OptsSecureOn = 0;
40
+ public $OptsSecurePass;
41
+ public $numFilesAdded = 0;
42
+ public $numDirsAdded = 0;
43
+
44
+ /** @var DUP_Package */
45
+ protected $Package = null;
46
+ /** @var SnapOrigFileManager */
47
+ protected $origFileManger = null;
48
+
49
+ /** @var WPConfigTransformer */
50
+ private $configTransformer = null;
51
+
52
+ /**
53
+ * Class contructor
54
+ */
55
+ public function __construct($package)
56
+ {
57
+ $this->Package = $package;
58
+
59
+ if (($wpConfigPath = SnapWP::getWPConfigPath()) !== false) {
60
+ $this->configTransformer = new WPConfigTransformer($wpConfigPath);
61
+ }
62
+ }
63
+
64
+ public function build($package, $error_behavior = Dup_ErrorBehavior::Quit)
65
+ {
66
+ DUP_Log::Info("building installer");
67
+ $this->Package = $package;
68
+ $success = false;
69
+ if ($this->create_enhanced_installer_files()) {
70
+ $success = $this->add_extra_files($package);
71
+ } else {
72
+ DUP_Log::Info("error creating enhanced installer files");
73
+ }
74
+
75
+
76
+ if ($success) {
77
+ $package->BuildProgress->installer_built = true;
78
+ } else {
79
+ $error_message = 'Error adding installer';
80
+ $package->BuildProgress->set_failed($error_message);
81
+ $package->Status = DUP_PackageStatus::ERROR;
82
+ $package->Update();
83
+ DUP_Log::error($error_message, "Marking build progress as failed because couldn't add installer files", $error_behavior);
84
+ //$package->BuildProgress->failed = true;
85
+ //$package->setStatus(DUP_PackageStatus::ERROR);
86
+ }
87
+
88
+ return $success;
89
+ }
90
+
91
+ private function create_enhanced_installer_files()
92
+ {
93
+ $success = false;
94
+ if ($this->create_enhanced_installer()) {
95
+ $success = $this->create_archive_config_file();
96
+ } else {
97
+ DUP_Log::infoTrace("Error in create_enhanced_installer, set build failed");
98
+ }
99
+
100
+ return $success;
101
+ }
102
+
103
+ private function create_enhanced_installer()
104
+ {
105
+ $success = true;
106
+ $archive_filepath = DUP_Settings::getSsdirTmpPath() . "/{$this->Package->Archive->File}";
107
+ $installer_filepath = apply_filters(
108
+ 'duplicator_installer_file_path',
109
+ DUP_Settings::getSsdirTmpPath() . "/{$this->Package->NameHash}_installer" . self::INSTALLER_SERVER_EXTENSION
110
+ );
111
+ $template_filepath = DUPLICATOR_PLUGIN_PATH . '/installer/installer.tpl';
112
+ // Replace the @@ARCHIVE@@ token
113
+ $header = <<<HEADER
114
+ <?php
115
+ /* ------------------------------ NOTICE ----------------------------------
116
+
117
+ If you're seeing this text when browsing to the installer, it means your
118
+ web server is not set up properly.
119
+
120
+ Please contact your host and ask them to enable "PHP" processing on your
121
+ account.
122
+ ----------------------------- NOTICE --------------------------------- */
123
+ ?>
124
+ HEADER;
125
+ $installer_contents = $header . SnapCode::getSrcClassCode($template_filepath, false) . "\n/* DUPLICATOR_INSTALLER_EOF */";
126
+ // $installer_contents = file_get_contents($template_filepath);
127
+ // $csrf_class_contents = file_get_contents($csrf_class_filepath);
128
+
129
+ if (DUP_Settings::Get('archive_build_mode') == DUP_Archive_Build_Mode::DupArchive) {
130
+ $dupLib = DUPLICATOR_PLUGIN_PATH . '/src/Libs/DupArchive/';
131
+ $dupExpanderCoder = '';
132
+ $dupExpanderCoder .= SnapCode::getSrcClassCode($dupLib . 'DupArchive.php') . "\n";
133
+ $dupExpanderCoder .= SnapCode::getSrcClassCode($dupLib . 'DupArchiveExpandBasicEngine.php') . "\n";
134
+ $dupExpanderCoder .= SnapCode::getSrcClassCode($dupLib . 'Headers/DupArchiveReaderDirectoryHeader.php') . "\n";
135
+ $dupExpanderCoder .= SnapCode::getSrcClassCode($dupLib . 'Headers/DupArchiveReaderFileHeader.php') . "\n";
136
+ $dupExpanderCoder .= SnapCode::getSrcClassCode($dupLib . 'Headers/DupArchiveReaderGlobHeader.php') . "\n";
137
+ $dupExpanderCoder .= SnapCode::getSrcClassCode($dupLib . 'Headers/DupArchiveReaderHeader.php') . "\n";
138
+ $dupExpanderCoder .= SnapCode::getSrcClassCode($dupLib . 'Headers/DupArchiveHeaderU.php') . "\n";
139
+ $dupExpanderCoder .= SnapCode::getSrcClassCode($dupLib . 'Info/DupArchiveExpanderInfo.php') . "\n";
140
+ if (strlen($dupExpanderCoder) == 0) {
141
+ DUP_Log::error(__('Error reading DupArchive expander', 'Duplicator'), __('Error reading DupArchive expander', 'duplicator'), false);
142
+ return false;
143
+ }
144
+ } else {
145
+ $dupExpanderCoder = '';
146
+ }
147
+
148
+ $search_array = array('@@ARCHIVE@@', '@@VERSION@@', '@@ARCHIVE_SIZE@@', '@@PACKAGE_HASH@@', '@@SECONDARY_PACKAGE_HASH@@', '@@DUPARCHIVE_MINI_EXPANDER@@');
149
+ $package_hash = $this->Package->getPackageHash();
150
+ $secondary_package_hash = $this->Package->getSecondaryPackageHash();
151
+ $replace_array = array($this->Package->Archive->File, DUPLICATOR_VERSION, @filesize($archive_filepath), $package_hash, $secondary_package_hash, $dupExpanderCoder);
152
+ $installer_contents = str_replace($search_array, $replace_array, $installer_contents);
153
+ if (@file_put_contents($installer_filepath, $installer_contents) === false) {
154
+ DUP_Log::error(__('Error writing installer contents', 'duplicator'), __("Couldn't write to $installer_filepath", 'duplicator'), false);
155
+ $success = false;
156
+ }
157
+
158
+ if ($success) {
159
+ $this->Size = @filesize($installer_filepath);
160
+ }
161
+
162
+ return $success;
163
+ }
164
+
165
+ /**
166
+ * Create archive.txt file */
167
+ private function create_archive_config_file()
168
+ {
169
+ global $wpdb;
170
+ $success = true;
171
+ $archive_config_filepath = DUP_Settings::getSsdirTmpPath() . "/{$this->Package->NameHash}_archive.txt";
172
+ $ac = new DUP_Archive_Config();
173
+ $extension = strtolower($this->Package->Archive->Format);
174
+ $hasher = new DUP_PasswordHash(8, false);
175
+ $pass_hash = $hasher->HashPassword($this->Package->Installer->OptsSecurePass);
176
+ $this->Package->Database->getScannerData();
177
+ //READ-ONLY: COMPARE VALUES
178
+ $ac->created = $this->Package->Created;
179
+ $ac->version_dup = DUPLICATOR_VERSION;
180
+ $ac->version_wp = $this->Package->VersionWP;
181
+ $ac->version_db = $this->Package->VersionDB;
182
+ $ac->version_php = $this->Package->VersionPHP;
183
+ $ac->version_os = $this->Package->VersionOS;
184
+ $ac->dbInfo = $this->Package->Database->info;
185
+ $ac->packInfo = array(
186
+ 'packageId' => $this->Package->ID,
187
+ 'packageName' => $this->Package->Name,
188
+ 'packageHash' => $this->Package->getPackageHash(),
189
+ 'secondaryHash' => $this->Package->getSecondaryPackageHash()
190
+ );
191
+ $ac->fileInfo = array(
192
+ 'dirCount' => $this->Package->Archive->dirsCount,
193
+ 'fileCount' => $this->Package->Archive->filesCount,
194
+ 'size' => $this->Package->Archive->Size
195
+ );
196
+ $ac->wpInfo = $this->getWpInfo();
197
+
198
+ $ac->installer_base_name = 'installer' . self::INSTALLER_SERVER_EXTENSION;
199
+ $ac->installer_backup_name = $this->Package->NameHash . '_installer-backup.php';
200
+ $ac->package_name = "{$this->Package->NameHash}_archive.{$extension}";
201
+ $ac->package_hash = $this->Package->getPackageHash();
202
+ $ac->package_notes = $this->Package->Notes;
203
+ $ac->opts_delete = SnapJson::jsonEncode($GLOBALS['DUPLICATOR_OPTS_DELETE']);
204
+ $ac->blogname = esc_html(get_option('blogname'));
205
+
206
+ $ac->exportOnlyDB = $this->Package->Archive->ExportOnlyDB;
207
+
208
+ // PRE-FILLED: GENERAL
209
+ $ac->secure_on = filter_var($this->Package->Installer->OptsSecureOn, FILTER_VALIDATE_BOOLEAN);
210
+ $ac->secure_pass = $pass_hash;
211
+ $ac->dbhost = (strlen($this->Package->Installer->OptsDBHost) ? $this->Package->Installer->OptsDBHost : null);
212
+ $ac->dbname = (strlen($this->Package->Installer->OptsDBName) ? $this->Package->Installer->OptsDBName : null);
213
+ $ac->dbuser = (strlen($this->Package->Installer->OptsDBUser) ? $this->Package->Installer->OptsDBUser : null);
214
+ $ac->dbpass = null;
215
+
216
+ $ac->mu_mode = DUP_MU::getMode();
217
+ $ac->wp_tableprefix = $wpdb->base_prefix;
218
+ $ac->mu_generation = DUP_MU::getGeneration();
219
+ $ac->mu_is_filtered = !empty($this->Package->Multisite->FilterSites) ? true : false;
220
+
221
+ $ac->mu_siteadmins = array_values(get_super_admins());
222
+ $filteredTables = ($this->Package->Database->FilterOn && isset($this->Package->Database->FilterTables)) ? explode(',', $this->Package->Database->FilterTables) : array();
223
+ $ac->subsites = DUP_MU::getSubsites(array(), $filteredTables, $this->Package->Archive->FilterInfo->Dirs->Instance);
224
+ if ($ac->subsites === false) {
225
+ DUP_Log::error("Error get subsites", "Couldn't get subisites", false);
226
+ $success = false;
227
+ }
228
+ $ac->main_site_id = SnapWP::getMainSiteId();
229
+ $ac->brand = array(
230
+ 'name' => "Duplicator",
231
+ 'isDefault' => true,
232
+ 'logo' => '<i class="fa fa-bolt fa-sm"></i> Duplicator',
233
+ 'enabled' => false,
234
+ 'style' => array()
235
+ );
236
+ $ac->wp_tableprefix = $wpdb->base_prefix;
237
+ $ac->mu_mode = DUP_MU::getMode();
238
+
239
+ $ac->overwriteInstallerParams = apply_filters('duplicator_overwrite_params_data', $this->getPrefillParams());
240
+ $json = SnapJson::jsonEncodePPrint($ac);
241
+ DUP_Log::TraceObject('json', $json);
242
+ if (file_put_contents($archive_config_filepath, $json) === false) {
243
+ DUP_Log::error("Error writing archive config", "Couldn't write archive config at $archive_config_filepath", Dup_ErrorBehavior::LogOnly);
244
+ $success = false;
245
+ }
246
+
247
+ return $success;
248
+ }
249
+
250
+ private function getPrefillParams()
251
+ {
252
+ $result = array();
253
+ if (strlen($this->OptsDBHost) > 0) {
254
+ $result['dbhost'] = array('value' => $this->OptsDBHost);
255
+ }
256
+
257
+ if (strlen($this->OptsDBName) > 0) {
258
+ $result['dbname'] = array('value' => $this->OptsDBName);
259
+ }
260
+
261
+ if (strlen($this->OptsDBUser) > 0) {
262
+ $result['dbuser'] = array('value' => $this->OptsDBUser);
263
+ }
264
+
265
+ $result['blogname'] = array('value' => esc_html(get_option('blogname')));
266
+
267
+ return $result;
268
+ }
269
+
270
+ /**
271
+ * get wpInfo object
272
+ *
273
+ * @return \stdClass
274
+ */
275
+ private function getWpInfo()
276
+ {
277
+ $wpInfo = new stdClass();
278
+ $wpInfo->version = $this->Package->VersionWP;
279
+ $wpInfo->is_multisite = is_multisite();
280
+ if (function_exists('get_current_network_id')) {
281
+ $wpInfo->network_id = get_current_network_id();
282
+ } else {
283
+ $wpInfo->network_id = 1;
284
+ }
285
+
286
+ $wpInfo->targetRoot = DUP_Archive::getTargetRootPath();
287
+ $wpInfo->targetPaths = DUP_Archive::getScanPaths();
288
+ $wpInfo->adminUsers = SnapWP::getAdminUserLists();
289
+ $wpInfo->configs = new stdClass();
290
+ $wpInfo->configs->defines = new stdClass();
291
+ $wpInfo->configs->realValues = new stdClass();
292
+ $wpInfo->plugins = $this->getPluginsInfo();
293
+ $wpInfo->themes = $this->getThemesInfo();
294
+
295
+ $this->addDefineIfExists($wpInfo->configs->defines, 'ABSPATH');
296
+ $this->addDefineIfExists($wpInfo->configs->defines, 'DB_CHARSET');
297
+ $this->addDefineIfExists($wpInfo->configs->defines, 'DB_COLLATE');
298
+ $this->addDefineIfExists(
299
+ $wpInfo->configs->defines,
300
+ 'MYSQL_CLIENT_FLAGS',
301
+ array('Duplicator\\Libs\\Snap\\SnapDB', 'getMysqlConnectFlagsFromMaskVal')
302
+ );
303
+ $this->addDefineIfExists($wpInfo->configs->defines, 'AUTH_KEY');
304
+ $this->addDefineIfExists($wpInfo->configs->defines, 'SECURE_AUTH_KEY');
305
+ $this->addDefineIfExists($wpInfo->configs->defines, 'LOGGED_IN_KEY');
306
+ $this->addDefineIfExists($wpInfo->configs->defines, 'NONCE_KEY');
307
+ $this->addDefineIfExists($wpInfo->configs->defines, 'AUTH_SALT');
308
+ $this->addDefineIfExists($wpInfo->configs->defines, 'SECURE_AUTH_SALT');
309
+ $this->addDefineIfExists($wpInfo->configs->defines, 'LOGGED_IN_SALT');
310
+ $this->addDefineIfExists($wpInfo->configs->defines, 'NONCE_SALT');
311
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WP_SITEURL');
312
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WP_HOME');
313
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WP_CONTENT_DIR');
314
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WP_CONTENT_URL');
315
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WP_PLUGIN_DIR');
316
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WP_PLUGIN_URL');
317
+ $this->addDefineIfExists($wpInfo->configs->defines, 'PLUGINDIR');
318
+ $this->addDefineIfExists($wpInfo->configs->defines, 'UPLOADS');
319
+ $this->addDefineIfExists($wpInfo->configs->defines, 'AUTOSAVE_INTERVAL');
320
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WP_POST_REVISIONS');
321
+ $this->addDefineIfExists($wpInfo->configs->defines, 'COOKIE_DOMAIN');
322
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WP_ALLOW_MULTISITE');
323
+ $this->addDefineIfExists($wpInfo->configs->defines, 'ALLOW_MULTISITE');
324
+ $this->addDefineIfExists($wpInfo->configs->defines, 'MULTISITE');
325
+ $this->addDefineIfExists($wpInfo->configs->defines, 'DOMAIN_CURRENT_SITE');
326
+ $this->addDefineIfExists($wpInfo->configs->defines, 'PATH_CURRENT_SITE');
327
+ $this->addDefineIfExists($wpInfo->configs->defines, 'SITE_ID_CURRENT_SITE');
328
+ $this->addDefineIfExists($wpInfo->configs->defines, 'BLOG_ID_CURRENT_SITE');
329
+ $this->addDefineIfExists($wpInfo->configs->defines, 'SUBDOMAIN_INSTALL');
330
+ $this->addDefineIfExists($wpInfo->configs->defines, 'VHOST');
331
+ $this->addDefineIfExists($wpInfo->configs->defines, 'SUNRISE');
332
+ $this->addDefineIfExists($wpInfo->configs->defines, 'NOBLOGREDIRECT');
333
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WP_DEBUG');
334
+ $this->addDefineIfExists($wpInfo->configs->defines, 'SCRIPT_DEBUG');
335
+ $this->addDefineIfExists($wpInfo->configs->defines, 'CONCATENATE_SCRIPTS');
336
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WP_DEBUG_LOG');
337
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WP_DEBUG_DISPLAY');
338
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WP_MEMORY_LIMIT');
339
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WP_MAX_MEMORY_LIMIT');
340
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WP_CACHE');
341
+
342
+ // wp super cache define
343
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WPCACHEHOME');
344
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WP_TEMP_DIR');
345
+ $this->addDefineIfExists($wpInfo->configs->defines, 'CUSTOM_USER_TABLE');
346
+ $this->addDefineIfExists($wpInfo->configs->defines, 'CUSTOM_USER_META_TABLE');
347
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WPLANG');
348
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WP_LANG_DIR');
349
+ $this->addDefineIfExists($wpInfo->configs->defines, 'SAVEQUERIES');
350
+ $this->addDefineIfExists($wpInfo->configs->defines, 'FS_CHMOD_DIR');
351
+ $this->addDefineIfExists($wpInfo->configs->defines, 'FS_CHMOD_FILE');
352
+ $this->addDefineIfExists($wpInfo->configs->defines, 'FS_METHOD');
353
+ /**
354
+ $this->addDefineIfExists($wpInfo->configs->defines, 'FTP_BASE');
355
+ $this->addDefineIfExists($wpInfo->configs->defines, 'FTP_CONTENT_DIR');
356
+ $this->addDefineIfExists($wpInfo->configs->defines, 'FTP_PLUGIN_DIR');
357
+ $this->addDefineIfExists($wpInfo->configs->defines, 'FTP_PUBKEY');
358
+ $this->addDefineIfExists($wpInfo->configs->defines, 'FTP_PRIKEY');
359
+ $this->addDefineIfExists($wpInfo->configs->defines, 'FTP_USER');
360
+ $this->addDefineIfExists($wpInfo->configs->defines, 'FTP_PASS');
361
+ $this->addDefineIfExists($wpInfo->configs->defines, 'FTP_HOST');
362
+ $this->addDefineIfExists($wpInfo->configs->defines, 'FTP_SSL');
363
+ * */
364
+ $this->addDefineIfExists($wpInfo->configs->defines, 'ALTERNATE_WP_CRON');
365
+ $this->addDefineIfExists($wpInfo->configs->defines, 'DISABLE_WP_CRON');
366
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WP_CRON_LOCK_TIMEOUT');
367
+ $this->addDefineIfExists($wpInfo->configs->defines, 'COOKIEPATH');
368
+ $this->addDefineIfExists($wpInfo->configs->defines, 'SITECOOKIEPATH');
369
+ $this->addDefineIfExists($wpInfo->configs->defines, 'ADMIN_COOKIE_PATH');
370
+ $this->addDefineIfExists($wpInfo->configs->defines, 'PLUGINS_COOKIE_PATH');
371
+ $this->addDefineIfExists($wpInfo->configs->defines, 'TEMPLATEPATH');
372
+ $this->addDefineIfExists($wpInfo->configs->defines, 'STYLESHEETPATH');
373
+ $this->addDefineIfExists($wpInfo->configs->defines, 'EMPTY_TRASH_DAYS');
374
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WP_ALLOW_REPAIR');
375
+ $this->addDefineIfExists($wpInfo->configs->defines, 'DO_NOT_UPGRADE_GLOBAL_TABLES');
376
+ $this->addDefineIfExists($wpInfo->configs->defines, 'DISALLOW_FILE_EDIT');
377
+ $this->addDefineIfExists($wpInfo->configs->defines, 'DISALLOW_FILE_MODS');
378
+ $this->addDefineIfExists($wpInfo->configs->defines, 'FORCE_SSL_ADMIN');
379
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WP_HTTP_BLOCK_EXTERNAL');
380
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WP_ACCESSIBLE_HOSTS');
381
+ $this->addDefineIfExists($wpInfo->configs->defines, 'AUTOMATIC_UPDATER_DISABLED');
382
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WP_AUTO_UPDATE_CORE');
383
+ $this->addDefineIfExists($wpInfo->configs->defines, 'IMAGE_EDIT_OVERWRITE');
384
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WPMU_PLUGIN_DIR');
385
+ $this->addDefineIfExists($wpInfo->configs->defines, 'WPMU_PLUGIN_URL');
386
+ $this->addDefineIfExists($wpInfo->configs->defines, 'MUPLUGINDIR');
387
+
388
+ $originalUrls = DUP_Archive::getOriginalUrls();
389
+ $wpInfo->configs->realValues->siteUrl = $originalUrls['abs'];
390
+ $wpInfo->configs->realValues->homeUrl = $originalUrls['home'];
391
+ $wpInfo->configs->realValues->contentUrl = $originalUrls['wpcontent'];
392
+ $wpInfo->configs->realValues->uploadBaseUrl = $originalUrls['uploads'];
393
+ $wpInfo->configs->realValues->pluginsUrl = $originalUrls['plugins'];
394
+ $wpInfo->configs->realValues->mupluginsUrl = $originalUrls['muplugins'];
395
+ $wpInfo->configs->realValues->themesUrl = $originalUrls['themes'];
396
+ $wpInfo->configs->realValues->originalPaths = array();
397
+ $originalpaths = DUP_Archive::getOriginalPaths();
398
+ foreach ($originalpaths as $key => $val) {
399
+ $wpInfo->configs->realValues->originalPaths[$key] = rtrim($val, '\\/');
400
+ }
401
+ $wpInfo->configs->realValues->archivePaths = array_merge($wpInfo->configs->realValues->originalPaths, DUP_Archive::getArchiveListPaths());
402
+ return $wpInfo;
403
+ }
404
+
405
+
406
+ /**
407
+ * Check if $define is defined and add a prop to $obj
408
+ *
409
+ * @param object $obj
410
+ * @param string $define
411
+ * @param null|callable $transformCallback if it is different from null the function is applied to the value
412
+ *
413
+ * @return boolean return true if define is added of false
414
+ */
415
+ private function addDefineIfExists($obj, $define, $transformCallback = null)
416
+ {
417
+ if (!defined($define)) {
418
+ return false;
419
+ }
420
+
421
+ $obj->{$define} = new StdClass();
422
+
423
+ if (is_callable($transformCallback)) {
424
+ $obj->{$define}->value = call_user_func($transformCallback, constant($define));
425
+ } else {
426
+ if ($transformCallback !== null) {
427
+ throw new Exception('transformCallback isn\'t callable');
428
+ }
429
+ $obj->{$define}->value = constant($define);
430
+ }
431
+
432
+ if (!is_null($this->configTransformer)) {
433
+ $obj->{$define}->inWpConfig = $this->configTransformer->exists('constant', $define);
434
+ } else {
435
+ $obj->{$define}->inWpConfig = false;
436
+ }
437
+
438
+ return true;
439
+ }
440
+
441
+
442
+ /**
443
+ * get plugins array info with multisite, must-use and drop-ins
444
+ *
445
+ * @return array
446
+ */
447
+ public function getPluginsInfo()
448
+ {
449
+ if (!function_exists('get_plugins')) {
450
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
451
+ }
452
+
453
+ // parse all plugins
454
+ $result = array();
455
+ foreach (get_plugins() as $path => $plugin) {
456
+ $result[$path] = self::getPluginArrayData($path, $plugin);
457
+ $result[$path]['networkActive'] = is_plugin_active_for_network($path);
458
+ if (!is_multisite()) {
459
+ $result[$path]['active'] = is_plugin_active($path);
460
+ } else {
461
+ // if is _multisite the active value is an array with the blog ids list where the plugin is active
462
+ $result[$path]['active'] = array();
463
+ }
464
+ }
465
+
466
+ // if is _multisite the active value is an array with the blog ids list where the plugin is active
467
+ if (is_multisite()) {
468
+ foreach (SnapWP::getSitesIds() as $siteId) {
469
+ switch_to_blog($siteId);
470
+ foreach ($result as $path => $plugin) {
471
+ if (!$result[$path]['networkActive'] && is_plugin_active($path)) {
472
+ $result[$path]['active'][] = $siteId;
473
+ }
474
+ }
475
+ restore_current_blog();
476
+ }
477
+ }
478
+
479
+ // parse all must use plugins
480
+ foreach (get_mu_plugins() as $path => $plugin) {
481
+ $result[$path] = self::getPluginArrayData($path, $plugin);
482
+ $result[$path]['mustUse'] = true;
483
+ }
484
+
485
+ // parse all dropins plugins
486
+ foreach (get_dropins() as $path => $plugin) {
487
+ $result[$path] = self::getPluginArrayData($path, $plugin);
488
+ $result[$path]['dropIns'] = true;
489
+ }
490
+
491
+ return $result;
492
+ }
493
+
494
+ /**
495
+ * return plugin formatted data from plugin info
496
+ * plugin info = Array (
497
+ * [Name] => Hello Dolly
498
+ * [PluginURI] => http://wordpress.org/extend/plugins/hello-dolly/
499
+ * [Version] => 1.6
500
+ * [Description] => This is not just ...
501
+ * [Author] => Matt Mullenweg
502
+ * [AuthorURI] => http://ma.tt/
503
+ * [TextDomain] =>
504
+ * [DomainPath] =>
505
+ * [Network] =>
506
+ * [Title] => Hello Dolly
507
+ * [AuthorName] => Matt Mullenweg
508
+ * )
509
+ *
510
+ * @param string $slug // plugin slug
511
+ * @param array $plugin // pluhin info from get_plugins function
512
+ * @return array
513
+ */
514
+ protected static function getPluginArrayData($slug, $plugin)
515
+ {
516
+ return array(
517
+ 'slug' => $slug,
518
+ 'name' => $plugin['Name'],
519
+ 'version' => $plugin['Version'],
520
+ 'pluginURI' => $plugin['PluginURI'],
521
+ 'author' => $plugin['Author'],
522
+ 'authorURI' => $plugin['AuthorURI'],
523
+ 'description' => $plugin['Description'],
524
+ 'title' => $plugin['Title'],
525
+ 'networkActive' => false,
526
+ 'active' => false,
527
+ 'mustUse' => false,
528
+ 'dropIns' => false
529
+ );
530
+ }
531
+
532
+ /**
533
+ * get themes array info with active template, stylesheet
534
+ *
535
+ * @return array
536
+ */
537
+ public function getThemesInfo()
538
+ {
539
+ if (!function_exists('wp_get_themes')) {
540
+ require_once ABSPATH . 'wp-admin/includes/theme.php';
541
+ }
542
+
543
+ foreach (wp_get_themes() as $slug => $theme) {
544
+ $result[$slug] = self::getThemeArrayData($theme);
545
+ }
546
+
547
+ if (is_multisite()) {
548
+ foreach (SnapWP::getSitesIds() as $siteId) {
549
+ switch_to_blog($siteId);
550
+ $stylesheet = get_stylesheet();
551
+ if (isset($result[$stylesheet])) {
552
+ $result[$stylesheet]['isActive'][] = $siteId;
553
+ }
554
+ restore_current_blog();
555
+ }
556
+ } else {
557
+ $stylesheet = get_stylesheet();
558
+ if (isset($result[$stylesheet])) {
559
+ $result[$stylesheet]['isActive'] = true;
560
+ }
561
+ }
562
+
563
+ return $result;
564
+ }
565
+
566
+ /**
567
+ * return plugin formatted data from plugin info
568
+ *
569
+ * @param WP_Theme $theme instance of WP Core class WP_Theme. theme info from get_themes function
570
+ * @return array
571
+ */
572
+ protected static function getThemeArrayData(WP_Theme $theme)
573
+ {
574
+ $slug = $theme->get_stylesheet();
575
+ $parent = $theme->parent();
576
+ return array(
577
+ 'slug' => $slug,
578
+ 'themeName' => $theme->get('Name'),
579
+ 'version' => $theme->get('Version'),
580
+ 'themeURI' => $theme->get('ThemeURI'),
581
+ 'parentTheme' => (false === $parent) ? false : $parent->get_stylesheet(),
582
+ 'template' => $theme->get_template(),
583
+ 'stylesheet' => $theme->get_stylesheet(),
584
+ 'description' => $theme->get('Description'),
585
+ 'author' => $theme->get('Author'),
586
+ "authorURI" => $theme->get('AuthorURI'),
587
+ 'tags' => $theme->get('Tags'),
588
+ 'isAllowed' => $theme->is_allowed(),
589
+ 'isActive' => (is_multisite() ? array() : false),
590
+ 'defaultTheme' => (defined('WP_DEFAULT_THEME') && WP_DEFAULT_THEME == $slug),
591
+ );
592
+ }
593
+
594
+ /**
595
+ * return list of extra files to att to archive
596
+ *
597
+ * @param bool $checkExists
598
+ * @return array
599
+ * @throws Exception
600
+ */
601
+ private function getExtraFilesLists($checkExists = true)
602
+ {
603
+ $result = array();
604
+
605
+ $installerFilepath = apply_filters(
606
+ 'duplicator_installer_file_path',
607
+ DUP_Settings::getSsdirTmpPath() . "/{$this->Package->NameHash}_installer" . self::INSTALLER_SERVER_EXTENSION
608
+ );
609
+
610
+ $result[] = array(
611
+ 'sourcePath' => $installerFilepath,
612
+ 'archivePath' => $this->getInstallerBackupName(),
613
+ 'label' => 'installer backup file'
614
+ );
615
+
616
+ $result[] = array(
617
+ 'sourcePath' => DUPLICATOR_LITE_PATH . '/installer/dup-installer',
618
+ 'archivePath' => '/',
619
+ 'label' => 'dup installer folder'
620
+ );
621
+
622
+ $result[] = array(
623
+ 'sourcePath' => DUPLICATOR_LITE_PATH . '/src/Libs/Snap',
624
+ 'archivePath' => 'dup-installer/libs/',
625
+ 'label' => 'dup snaplib folder'
626
+ );
627
+
628
+ $result[] = array(
629
+ 'sourcePath' => DUPLICATOR_LITE_PATH . '/src/Libs/DupArchive',
630
+ 'archivePath' => 'dup-installer/libs/',
631
+ 'label' => 'dup snaplib folder'
632
+ );
633
+
634
+ $result[] = array(
635
+ 'sourcePath' => DUPLICATOR_LITE_PATH . '/src/Libs/WpConfig',
636
+ 'archivePath' => 'dup-installer/libs/',
637
+ 'label' => 'lib config folder'
638
+ );
639
+
640
+ $result[] = array(
641
+ 'sourcePath' => DUPLICATOR_LITE_PATH . '/src/Libs/Certificates',
642
+ 'archivePath' => 'dup-installer/libs/',
643
+ 'label' => 'SSL certificates'
644
+ );
645
+
646
+ $result[] = array(
647
+ 'sourcePath' => DUPLICATOR_LITE_PATH . '/vendor/requests',
648
+ 'archivePath' => 'dup-installer/vendor/',
649
+ 'label' => 'Requests library'
650
+ );
651
+
652
+ $result[] = array(
653
+ 'sourcePath' => DUPLICATOR_LITE_PATH . '/assets/js/duplicator-tooltip.js',
654
+ 'archivePath' => 'dup-installer/assets/js/duplicator-tooltip.js',
655
+ 'label' => 'Duplicator tooltip script'
656
+ );
657
+
658
+ $result[] = array(
659
+ 'sourcePath' => DUPLICATOR_LITE_PATH . '/assets/js/popper',
660
+ 'archivePath' => 'dup-installer/assets/js/',
661
+ 'label' => 'popper js'
662
+ );
663
+
664
+ $result[] = array(
665
+ 'sourcePath' => DUPLICATOR_LITE_PATH . '/assets/js/tippy',
666
+ 'archivePath' => 'dup-installer/assets/js/',
667
+ 'label' => 'tippy js'
668
+ );
669
+
670
+ $result[] = array(
671
+ 'sourcePath' => $this->origFileManger->getMainFolder(),
672
+ 'archivePath' => 'dup-installer/',
673
+ 'label' => 'original files folder'
674
+ );
675
+
676
+ $result[] = array(
677
+ 'sourcePath' => DUP_Settings::getSsdirTmpPath() . "/{$this->Package->NameHash}_archive.txt",
678
+ 'archivePath' => $this->getArchiveTxtFilePath(),
679
+ 'label' => 'archive descriptor file'
680
+ );
681
+
682
+ $result[] = array(
683
+ 'sourcePath' => DUP_Settings::getSsdirTmpPath() . "/{$this->Package->NameHash}_scan.json",
684
+ 'archivePath' => $this->getEmbeddedScanFilePath(),
685
+ 'label' => 'scan file'
686
+ );
687
+
688
+ $result[] = array(
689
+ 'sourcePath' => DUP_Settings::getSsdirTmpPath() . '/' . $this->Package->NameHash . DUP_Archive::FILES_LIST_FILE_NAME_SUFFIX,
690
+ 'archivePath' => $this->getEmbeddedScanFileList(),
691
+ 'label' => 'files list file'
692
+ );
693
+
694
+ $result[] = array(
695
+ 'sourcePath' => DUP_Settings::getSsdirTmpPath() . '/' . $this->Package->NameHash . DUP_Archive::DIRS_LIST_FILE_NAME_SUFFIX,
696
+ 'archivePath' => $this->getEmbeddedScanDirList(),
697
+ 'label' => 'folders list file'
698
+ );
699
+
700
+ $result[] = array(
701
+ 'sourcePath' => $this->getManualExtractFilePath(),
702
+ 'archivePath' => $this->getEmbeddedManualExtractFilePath(),
703
+ 'label' => 'manual extract file'
704
+ );
705
+
706
+
707
+ if ($checkExists) {
708
+ foreach ($result as $item) {
709
+ if (!is_readable($item['sourcePath'])) {
710
+ throw new Exception('INSTALLER FILES: "' . $item['label'] . '" doesn\'t exist ' . $item['sourcePath']);
711
+ }
712
+ }
713
+ }
714
+
715
+ return $result;
716
+ }
717
+
718
+ public function getInstallerBackupName()
719
+ {
720
+ return $this->Package->NameHash . '_installer-backup.php';
721
+ }
722
+
723
+ private function getEmbeddedScanFileList()
724
+ {
725
+ return 'dup-installer/dup-scanned-files__' . $this->Package->getPackageHash() . '.txt';
726
+ }
727
+
728
+ private function getEmbeddedScanDirList()
729
+ {
730
+ return 'dup-installer/dup-scanned-dirs__' . $this->Package->getPackageHash() . '.txt';
731
+ }
732
+
733
+ /**
734
+ * createZipBackup
735
+ * Puts an installer zip file in the archive for backup purposes.
736
+ */
737
+ private function add_extra_files()
738
+ {
739
+ $success = false;
740
+ $archive_filepath = DUP_Settings::getSsdirTmpPath() . "/{$this->Package->Archive->File}";
741
+
742
+ $this->initConfigFiles();
743
+ $this->createManualExtractCheckFile();
744
+
745
+ if ($this->Package->Archive->file_count != 2) {
746
+ DUP_Log::trace("Doing archive file check");
747
+ // Only way it's 2 is if the root was part of the filter in which case the archive won't be there
748
+ if (file_exists($archive_filepath) == false) {
749
+ $error_text = sprintf(__("Zip archive %1s not present.", 'dup;icator'), $archive_filepath);
750
+ DUP_Log::error($error_text, '', Dup_ErrorBehavior::LogOnly);
751
+ return false;
752
+ }
753
+ }
754
+
755
+ if ($this->Package->Archive->Format == 'DAF') {
756
+ $success = $this->dupArchiveAddExtra();
757
+ } else {
758
+ $success = $this->zipArchiveAddExtra();
759
+ }
760
+
761
+ try {
762
+ $archive_config_filepath = DUP_Settings::getSsdirTmpPath() . "/{$this->Package->NameHash}_archive.txt";
763
+ // No sense keeping these files
764
+ @unlink($archive_config_filepath);
765
+ $this->origFileManger->deleteMainFolder();
766
+ $this->deleteManualExtractCheckFile();
767
+ } catch (Exception $e) {
768
+ DUP_Log::infoTrace("Error clean temp installer file, but continue. Message: " . $e->getMessage());
769
+ }
770
+
771
+ $this->Package->Archive->Size = @filesize($archive_filepath);
772
+ return $success;
773
+ }
774
+
775
+ /**
776
+ *
777
+ * @return boolean
778
+ * @throws \Exception
779
+ */
780
+ private function zipArchiveAddExtra()
781
+ {
782
+ $zipArchive = new ZipArchive();
783
+ $isCompressed = true;
784
+
785
+ if ($zipArchive->open($this->getTmpArchiveFullPath(), ZipArchive::CREATE) !== true) {
786
+ throw new \Exception("Couldn't open zip archive ");
787
+ }
788
+
789
+ DUP_Log::trace("Successfully opened zip");
790
+
791
+ foreach ($this->getExtraFilesLists() as $extraItem) {
792
+ if (is_dir($extraItem['sourcePath'])) {
793
+ if (!DUP_Zip_U::addDirWithZipArchive($zipArchive, $extraItem['sourcePath'], true, $extraItem['archivePath'], $isCompressed)) {
794
+ throw new \Exception('INSTALLER FILES: zip add ' . $extraItem['label'] . ' folder error on folder ' . $extraItem['sourcePath']);
795
+ }
796
+ } else {
797
+ if (!DUP_Zip_U::addFileToZipArchive($zipArchive, $extraItem['sourcePath'], $extraItem['archivePath'], $isCompressed)) {
798
+ throw new \Exception('INSTALLER FILES: zip add ' . $extraItem['label'] . ' file error on file ' . $extraItem['sourcePath']);
799
+ }
800
+ }
801
+ }
802
+
803
+ if ($zipArchive->close() === false) {
804
+ throw new \Exception("Couldn't close zip archive ");
805
+ }
806
+
807
+ DUP_Log::trace('After ziparchive close when adding installer');
808
+
809
+ $this->zipArchiveCheck();
810
+ return true;
811
+ }
812
+
813
+ private function zipArchiveCheck()
814
+ {
815
+ /* ------ ZIP CONSISTENCY CHECK ------ */
816
+ DUP_Log::trace("Running ZipArchive consistency check");
817
+ $zip = new ZipArchive();
818
+
819
+ // ZipArchive::CHECKCONS will enforce additional consistency checks
820
+ $res = $zip->open($this->getTmpArchiveFullPath(), ZipArchive::CHECKCONS);
821
+ if ($res !== true) {
822
+ $consistency_error = sprintf(__('ERROR: Cannot open created archive. Error code = %1$s', 'duplicator'), $res);
823
+ DUP_Log::trace($consistency_error);
824
+ switch ($res) {
825
+ case ZipArchive::ER_NOZIP:
826
+ $consistency_error = __('ERROR: Archive is not valid zip archive.', 'duplicator');
827
+ break;
828
+ case ZipArchive::ER_INCONS:
829
+ $consistency_error = __("ERROR: Archive doesn't pass consistency check.", 'duplicator');
830
+ break;
831
+ case ZipArchive::ER_CRC:
832
+ $consistency_error = __("ERROR: Archive checksum is bad.", 'duplicator');
833
+ break;
834
+ }
835
+
836
+ throw new \Exception($consistency_error);
837
+ }
838
+
839
+ $failed = false;
840
+ foreach ($this->getInstallerPathsForIntegrityCheck() as $path) {
841
+ if ($zip->locateName($path) === false) {
842
+ $failed = true;
843
+ DUP_Log::infoTrace(__("Couldn't find $path in archive", 'duplicator'));
844
+ }
845
+ }
846
+
847
+ if ($failed) {
848
+ DUP_Log::info(__('ARCHIVE CONSISTENCY TEST: FAIL', 'duplicator'));
849
+ throw new \Exception("Zip for package " . $this->Package->ID . " didn't passed consistency test");
850
+ } else {
851
+ DUP_Log::info(__('ARCHIVE CONSISTENCY TEST: PASS', 'duplicator'));
852
+ DUP_Log::trace("Zip for package " . $this->Package->ID . " passed consistency test");
853
+ }
854
+
855
+ $zip->close();
856
+ }
857
+
858
+ public function getInstallerPathsForIntegrityCheck()
859
+ {
860
+ $filesToValidate = array(
861
+ 'dup-installer/assets/index.php',
862
+ 'dup-installer/classes/index.php',
863
+ 'dup-installer/ctrls/index.php',
864
+ 'dup-installer/src/Utils/Autoloader.php',
865
+ 'dup-installer/templates/default/page-help.php',
866
+ 'dup-installer/main.installer.php',
867
+ );
868
+
869
+ foreach ($this->getExtraFilesLists() as $extraItem) {
870
+ if (is_file($extraItem['sourcePath'])) {
871
+ $filesToValidate[] = $extraItem['archivePath'];
872
+ } else {
873
+ if (file_exists(trailingslashit($extraItem['sourcePath']) . '/index.php')) {
874
+ $filesToValidate[] = ltrim(trailingslashit($extraItem['archivePath']), '\\/') . basename($extraItem['sourcePath']) . '/index.php';
875
+ } else {
876
+ // SKIP CHECK
877
+ }
878
+ }
879
+ }
880
+
881
+ return array_unique($filesToValidate);
882
+ }
883
+
884
+ private function dupArchiveAddExtra()
885
+ {
886
+
887
+ $logger = new DUP_DupArchive_Logger();
888
+ DupArchiveEngine::init($logger, null);
889
+
890
+ $archivePath = $this->getTmpArchiveFullPath();
891
+ $extraPoistion = filesize($archivePath);
892
+
893
+ foreach ($this->getExtraFilesLists() as $extraItem) {
894
+ if (is_dir($extraItem['sourcePath'])) {
895
+ $basePath = dirname($extraItem['sourcePath']);
896
+ $destPath = ltrim(trailingslashit($extraItem['archivePath']), '\\/');
897
+ $result = DupArchiveEngine::addDirectoryToArchiveST($archivePath, $extraItem['sourcePath'], $basePath, true, $destPath);
898
+
899
+ $this->numFilesAdded += $result->numFilesAdded;
900
+ $this->numDirsAdded += $result->numDirsAdded;
901
+ } else {
902
+ DupArchiveEngine::addRelativeFileToArchiveST($archivePath, $extraItem['sourcePath'], $extraItem['archivePath']);
903
+ $this->numFilesAdded++;
904
+ }
905
+ }
906
+
907
+ // store extra files position
908
+ $src = json_encode(array(DupArchiveEngine::EXTRA_FILES_POS_KEY => $extraPoistion));
909
+ $src .= str_repeat("\0", DupArchiveEngine::INDEX_FILE_SIZE - strlen($src));
910
+ DupArchiveEngine::replaceFileContent($archivePath, $src, DupArchiveEngine::INDEX_FILE_NAME, 0, 3000);
911
+
912
+ return true;
913
+ }
914
+
915
+ /**
916
+ *
917
+ * @return string
918
+ */
919
+ public function getTmpArchiveFullPath()
920
+ {
921
+ return DUP_Settings::getSsdirTmpPath() . '/' . $this->Package->Archive->File;
922
+ }
923
+
924
+ /**
925
+ * Clear out sensitive database connection information
926
+ *
927
+ * @param $temp_conf_ark_file_path Temp config file path
928
+ * @throws Exception
929
+ */
930
+ private static function cleanTempWPConfArkFilePath($temp_conf_ark_file_path)
931
+ {
932
+ try {
933
+ if (function_exists('token_get_all')) {
934
+ $transformer = new WPConfigTransformer($temp_conf_ark_file_path);
935
+ $constants = array('DB_NAME', 'DB_USER', 'DB_PASSWORD', 'DB_HOST');
936
+ foreach ($constants as $constant) {
937
+ if ($transformer->exists('constant', $constant)) {
938
+ $transformer->update('constant', $constant, '');
939
+ }
940
+ }
941
+ }
942
+ } catch (Exception $e) {
943
+ DUP_Log::infoTrace("Can\'t inizialize wp-config transformer Message: " . $e->getMessage());
944
+ } catch (Error $e) {
945
+ DUP_Log::infoTrace("Can\'t inizialize wp-config transformer Message: " . $e->getMessage());
946
+ }
947
+ }
948
+
949
+ /**
950
+ * Get scan.json file path along with name in archive file
951
+ */
952
+ private function getEmbeddedScanFilePath()
953
+ {
954
+ $package_hash = $this->Package->getPackageHash();
955
+ $embedded_scan_ark_file_path = 'dup-installer/dup-scan__' . $package_hash . '.json';
956
+ return $embedded_scan_ark_file_path;
957
+ }
958
+
959
+ /**
960
+ * Get archive.txt file path along with name in archive file
961
+ */
962
+ private function getArchiveTxtFilePath()
963
+ {
964
+ $package_hash = $this->Package->getPackageHash();
965
+ $archive_txt_file_path = 'dup-installer/dup-archive__' . $package_hash . '.txt';
966
+ return $archive_txt_file_path;
967
+ }
968
+
969
+ /**
970
+ * Creates the original_files_ folder in the tmp directory where all config files are saved
971
+ * to be later added to the archives
972
+ *
973
+ * @throws Exception
974
+ */
975
+ public function initConfigFiles()
976
+ {
977
+ $this->origFileManger = new SnapOrigFileManager(
978
+ DUP_Archive::getArchiveListPaths('home'),
979
+ DUP_Settings::getSsdirTmpPath(),
980
+ $this->Package->getPackageHash()
981
+ );
982
+ $this->origFileManger->init();
983
+ $configFilePaths = $this->getConfigFilePaths();
984
+ foreach ($configFilePaths as $identifier => $path) {
985
+ if ($path !== false) {
986
+ try {
987
+ $this->origFileManger->addEntry($identifier, $path, SnapOrigFileManager::MODE_COPY, self::CONFIG_ORIG_FILE_FOLDER_PREFIX . $identifier);
988
+ } catch (Exception $ex) {
989
+ DUP_Log::Info("Error while handling config files: " . $ex->getMessage());
990
+ }
991
+ }
992
+ }
993
+
994
+ //Clean sensitive information from wp-config.php file.
995
+ self::cleanTempWPConfArkFilePath($this->origFileManger->getEntryStoredPath(self::CONFIG_ORIG_FILE_WPCONFIG_ID));
996
+ }
997
+
998
+ /**
999
+ * Gets config files path
1000
+ *
1001
+ * @return string[] array of config files in identifier => path format
1002
+ */
1003
+ public function getConfigFilePaths()
1004
+ {
1005
+ $home = DUP_Archive::getArchiveListPaths('home');
1006
+ $configFiles = array(
1007
+ self::CONFIG_ORIG_FILE_USERINI_ID => $home . '/.user.ini',
1008
+ self::CONFIG_ORIG_FILE_PHPINI_ID => $home . '/php.ini',
1009
+ self::CONFIG_ORIG_FILE_WEBCONFIG_ID => $home . '/web.config',
1010
+ self::CONFIG_ORIG_FILE_HTACCESS_ID => $home . '/.htaccess',
1011
+ self::CONFIG_ORIG_FILE_WPCONFIG_ID => SnapWP::getWPConfigPath()
1012
+ );
1013
+ foreach ($configFiles as $identifier => $path) {
1014
+ if (!file_exists($path)) {
1015
+ unset($configFiles[$identifier]);
1016
+ }
1017
+ }
1018
+
1019
+ return $configFiles;
1020
+ }
1021
+
1022
+ private function createManualExtractCheckFile()
1023
+ {
1024
+ $file_path = $this->getManualExtractFilePath();
1025
+ return SnapIO::filePutContents($file_path, '');
1026
+ }
1027
+
1028
+ private function getManualExtractFilePath()
1029
+ {
1030
+ return DUP_Settings::getSsdirTmpPath() . '/dup-manual-extract__' . $this->Package->getPackageHash();
1031
+ }
1032
+
1033
+ private function getEmbeddedManualExtractFilePath()
1034
+ {
1035
+ $embedded_filepath = 'dup-installer/dup-manual-extract__' . $this->Package->getPackageHash();
1036
+ return $embedded_filepath;
1037
+ }
1038
+
1039
+ private function deleteManualExtractCheckFile()
1040
+ {
1041
+ SnapIO::rm($this->getManualExtractFilePath());
1042
+ }
1043
+ }
classes/package/class.pack.php CHANGED
@@ -1,1805 +1,1858 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- // Exit if accessed directly
4
- if (! defined('DUPLICATOR_VERSION')) exit;
5
-
6
- require_once (DUPLICATOR_PLUGIN_PATH.'classes/utilities/class.u.php');
7
- require_once (DUPLICATOR_PLUGIN_PATH.'classes/package/class.pack.archive.php');
8
- require_once (DUPLICATOR_PLUGIN_PATH.'classes/package/class.pack.installer.php');
9
- require_once (DUPLICATOR_PLUGIN_PATH.'classes/package/class.pack.database.php');
10
-
11
- /**
12
- * Class used to keep track of the build progress
13
- *
14
- * @package Duplicator\classes
15
- */
16
- class DUP_Build_Progress
17
- {
18
- public $thread_start_time;
19
- public $initialized = false;
20
- public $installer_built = false;
21
- public $archive_started = false;
22
- public $archive_has_database = false;
23
- public $archive_built = false;
24
- public $database_script_built = false;
25
- public $failed = false;
26
- public $retries = 0;
27
- public $build_failures = array();
28
- public $validation_failures = array();
29
-
30
- /**
31
- *
32
- * @var DUP_Package
33
- */
34
- private $package;
35
-
36
- /**
37
- *
38
- * @param DUP_Package $package
39
- */
40
- public function __construct($package)
41
- {
42
- $this->package = $package;
43
- }
44
-
45
- /**
46
- *
47
- * @return bool
48
- */
49
- public function has_completed()
50
- {
51
- return $this->failed || ($this->installer_built && $this->archive_built && $this->database_script_built);
52
- }
53
-
54
- public function timed_out($max_time)
55
- {
56
- if ($max_time > 0) {
57
- $time_diff = time() - $this->thread_start_time;
58
- return ($time_diff >= $max_time);
59
- } else {
60
- return false;
61
- }
62
- }
63
-
64
- public function start_timer()
65
- {
66
- $this->thread_start_time = time();
67
- }
68
-
69
- public function set_validation_failures($failures)
70
- {
71
- $this->validation_failures = array();
72
-
73
- foreach ($failures as $failure) {
74
- $this->validation_failures[] = $failure;
75
- }
76
- }
77
-
78
- public function set_build_failures($failures)
79
- {
80
- $this->build_failures = array();
81
-
82
- foreach ($failures as $failure) {
83
- $this->build_failures[] = $failure->description;
84
- }
85
- }
86
-
87
- public function set_failed($failure_message = null)
88
- {
89
- if($failure_message !== null) {
90
- $failure = new StdClass();
91
- $failure->type = 0;
92
- $failure->subject = '';
93
- $failure->description = $failure_message;
94
- $failure->isCritical = true;
95
- $this->build_failures[] = $failure;
96
- }
97
-
98
- $this->failed = true;
99
- $this->package->Status = DUP_PackageStatus::ERROR;
100
- }
101
- }
102
-
103
- /**
104
- * Class used to emulate and ENUM to give the status of a package from 0 to 100%
105
- *
106
- * @package Duplicator\classes
107
- */
108
- final class DUP_PackageStatus
109
- {
110
- private function __construct()
111
- {
112
- }
113
-
114
- const ERROR = -1;
115
- const CREATED = 0;
116
- const START = 10;
117
- const DBSTART = 20;
118
- const DBDONE = 30;
119
- const ARCSTART = 40;
120
- const ARCVALIDATION = 60;
121
- const ARCDONE = 65;
122
- const COMPLETE = 100;
123
- }
124
-
125
- /**
126
- * Class used to emulate and ENUM to determine how the package was made.
127
- * For lite only the MANUAL type is used.
128
- *
129
- * @package Duplicator\classes
130
- */
131
- final class DUP_PackageType
132
- {
133
- const MANUAL = 0;
134
- const SCHEDULED = 1;
135
- }
136
-
137
- /**
138
- * Class used to emulate and ENUM to determine the various file types used in a package
139
- *
140
- * @package Duplicator\classes
141
- */
142
- abstract class DUP_PackageFileType
143
- {
144
- const Installer = 0;
145
- const Archive = 1;
146
- const SQL = 2;
147
- const Log = 3;
148
- const Scan = 4;
149
- }
150
-
151
- /**
152
- * Class used to store and process all Package logic
153
- *
154
- * Standard: PSR-2
155
- * @link http://www.php-fig.org/psr/psr-2
156
- *
157
- * @package Duplicator\classes
158
- */
159
- class DUP_Package
160
- {
161
- const OPT_ACTIVE = 'duplicator_package_active';
162
-
163
- //Properties
164
- public $Created;
165
- public $Version;
166
- public $VersionWP;
167
- public $VersionDB;
168
- public $VersionPHP;
169
- public $VersionOS;
170
- public $ID;
171
- public $Name;
172
- public $Hash;
173
- public $NameHash;
174
- //Set to DUP_PackageType
175
- public $Type;
176
- public $Notes;
177
- public $ScanFile;
178
- public $TimerStart = -1;
179
- public $Runtime;
180
- public $ExeSize;
181
- public $ZipSize;
182
- public $Status;
183
- public $WPUser;
184
- //Objects
185
- public $Archive;
186
- public $Installer;
187
- public $Database;
188
-
189
- public $BuildProgress;
190
-
191
- /**
192
- * Manages the Package Process
193
- */
194
- function __construct()
195
- {
196
- $this->ID = null;
197
- $this->Version = DUPLICATOR_VERSION;
198
- $this->Type = DUP_PackageType::MANUAL;
199
- $this->Name = self::getDefaultName();
200
- $this->Notes = null;
201
- $this->Database = new DUP_Database($this);
202
- $this->Archive = new DUP_Archive($this);
203
- $this->Installer = new DUP_Installer($this);
204
- $this->BuildProgress = new DUP_Build_Progress($this);
205
- $this->Status = DUP_PackageStatus::CREATED;
206
- }
207
-
208
- /**
209
- * Generates a JSON scan report
210
- *
211
- * @return array of scan results
212
- *
213
- * @notes: Testing = /wp-admin/admin-ajax.php?action=duplicator_package_scan
214
- */
215
- public function runScanner()
216
- {
217
- $timerStart = DUP_Util::getMicrotime();
218
- $report = array();
219
- $this->ScanFile = "{$this->NameHash}_scan.json";
220
-
221
- $report['RPT']['ScanTime'] = "0";
222
- $report['RPT']['ScanFile'] = $this->ScanFile;
223
-
224
- //SERVER
225
- $srv = DUP_Server::getChecks();
226
- $report['SRV'] = $srv['SRV'];
227
-
228
- //FILES
229
- $this->Archive->getScannerData();
230
- $dirCount = count($this->Archive->Dirs);
231
- $fileCount = count($this->Archive->Files);
232
- $fullCount = $dirCount + $fileCount;
233
-
234
- $report['ARC']['Size'] = DUP_Util::byteSize($this->Archive->Size) or "unknown";
235
- $report['ARC']['DirCount'] = number_format($dirCount);
236
- $report['ARC']['FileCount'] = number_format($fileCount);
237
- $report['ARC']['FullCount'] = number_format($fullCount);
238
- $report['ARC']['WarnFileCount'] = count($this->Archive->FilterInfo->Files->Warning);
239
- $report['ARC']['WarnDirCount'] = count($this->Archive->FilterInfo->Dirs->Warning);
240
- $report['ARC']['UnreadableDirCount'] = count($this->Archive->FilterInfo->Dirs->Unreadable);
241
- $report['ARC']['UnreadableFileCount'] = count($this->Archive->FilterInfo->Files->Unreadable);
242
- $report['ARC']['FilterDirsAll'] = $this->Archive->FilterDirsAll;
243
- $report['ARC']['FilterFilesAll'] = $this->Archive->FilterFilesAll;
244
- $report['ARC']['FilterExtsAll'] = $this->Archive->FilterExtsAll;
245
- $report['ARC']['FilterInfo'] = $this->Archive->FilterInfo;
246
- $report['ARC']['RecursiveLinks'] = $this->Archive->RecursiveLinks;
247
- $report['ARC']['UnreadableItems'] = array_merge($this->Archive->FilterInfo->Files->Unreadable,$this->Archive->FilterInfo->Dirs->Unreadable);
248
- $report['ARC']['Status']['Size'] = ($this->Archive->Size > DUPLICATOR_SCAN_SIZE_DEFAULT) ? 'Warn' : 'Good';
249
- $report['ARC']['Status']['Names'] = (count($this->Archive->FilterInfo->Files->Warning) + count($this->Archive->FilterInfo->Dirs->Warning)) ? 'Warn' : 'Good';
250
- $report['ARC']['Status']['UnreadableItems'] = !empty($this->Archive->RecursiveLinks) || !empty($report['ARC']['UnreadableItems'])? 'Warn' : 'Good';
251
- /*
252
- $overwriteInstallerParams = apply_filters('duplicator_overwrite_params_data', array());
253
- $package_can_be_migrate = !(isset($overwriteInstallerParams['mode_chunking']['value'])
254
- && $overwriteInstallerParams['mode_chunking']['value'] == 3
255
- && isset($overwriteInstallerParams['mode_chunking']['formStatus'])
256
- && $overwriteInstallerParams['mode_chunking']['formStatus'] == 'st_infoonly');
257
- */
258
- $package_can_be_migrate = true;
259
- $report['ARC']['Status']['MigratePackage'] = $package_can_be_migrate ? 'Good' : 'Warn';
260
- $report['ARC']['Status']['CanbeMigratePackage'] = $package_can_be_migrate;
261
-
262
- $privileges_to_show_create_proc_func = true;
263
- $procedures = $GLOBALS['wpdb']->get_col("SHOW PROCEDURE STATUS WHERE `Db` = '".$GLOBALS['wpdb']->dbname."'", 1);
264
- if (count($procedures)) {
265
- $create = $GLOBALS['wpdb']->get_row("SHOW CREATE PROCEDURE `".$procedures[0]."`", ARRAY_N);
266
- $privileges_to_show_create_proc_func = isset($create[2]);
267
- }
268
-
269
- $functions = $GLOBALS['wpdb']->get_col("SHOW FUNCTION STATUS WHERE `Db` = '".$GLOBALS['wpdb']->dbname."'", 1);
270
- if (count($functions)) {
271
- $create = $GLOBALS['wpdb']->get_row("SHOW CREATE FUNCTION `".$functions[0]."`", ARRAY_N);
272
- $privileges_to_show_create_proc_func = $privileges_to_show_create_proc_func && isset($create[2]);
273
- }
274
-
275
- $privileges_to_show_create_proc_func = apply_filters('duplicator_privileges_to_show_create_proc_func', $privileges_to_show_create_proc_func);
276
- $report['ARC']['Status']['showCreateProcFuncStatus'] = $privileges_to_show_create_proc_func ? 'Good' : 'Warn';
277
- $report['ARC']['Status']['showCreateProcFunc'] = $privileges_to_show_create_proc_func;
278
-
279
- //$report['ARC']['Status']['Big'] = count($this->Archive->FilterInfo->Files->Size) ? 'Warn' : 'Good';
280
- $report['ARC']['Dirs'] = $this->Archive->Dirs;
281
- $report['ARC']['Files'] = $this->Archive->Files;
282
- $report['ARC']['Status']['AddonSites'] = count($this->Archive->FilterInfo->Dirs->AddonSites) ? 'Warn' : 'Good';
283
-
284
- //DATABASE
285
- $db = $this->Database->getScannerData();
286
- $report['DB'] = $db;
287
-
288
- //Lite Limits
289
- $rawTotalSize = $this->Archive->Size + $report['DB']['RawSize'];
290
- $report['LL']['TotalSize'] = DUP_Util::byteSize($rawTotalSize);
291
- $report['LL']['Status']['TotalSize'] = ($rawTotalSize > DUPLICATOR_MAX_DUPARCHIVE_SIZE) ? 'Fail' : 'Good';
292
-
293
- $warnings = array(
294
- $report['SRV']['SYS']['ALL'],
295
- $report['SRV']['WP']['ALL'],
296
- $report['ARC']['Status']['Size'],
297
- $report['ARC']['Status']['Names'],
298
- $db['Status']['DB_Size'],
299
- $db['Status']['DB_Rows']
300
- );
301
-
302
- //array_count_values will throw a warning message if it has null values,
303
- //so lets replace all nulls with empty string
304
- foreach ($warnings as $i => $value) {
305
- if (is_null($value)) {
306
- $warnings[$i] = '';
307
- }
308
- }
309
-
310
- $warn_counts = is_array($warnings) ? array_count_values($warnings) : 0;
311
- $report['RPT']['Warnings'] = is_null($warn_counts['Warn']) ? 0 : $warn_counts['Warn'];
312
- $report['RPT']['Success'] = is_null($warn_counts['Good']) ? 0 : $warn_counts['Good'];
313
- $report['RPT']['ScanTime'] = DUP_Util::elapsedTime(DUP_Util::getMicrotime(), $timerStart);
314
- $fp = fopen(DUP_Settings::getSsdirTmpPath()."/{$this->ScanFile}", 'w');
315
-
316
- fwrite($fp, DupLiteSnapJsonU::wp_json_encode_pprint($report));
317
- fclose($fp);
318
-
319
- return $report;
320
- }
321
-
322
- /**
323
- * Validates the inputs from the UI for correct data input
324
- *
325
- * @return DUP_Validator
326
- */
327
- public function validateInputs()
328
- {
329
- $validator = new DUP_Validator();
330
-
331
- $validator->filter_custom($this->Name , DUP_Validator::FILTER_VALIDATE_NOT_EMPTY ,
332
- array( 'valkey' => 'Name' ,
333
- 'errmsg' => __('Package name can\'t be empty', 'duplicator'),
334
- )
335
- );
336
-
337
- $validator->explode_filter_custom($this->Archive->FilterDirs, ';' , DUP_Validator::FILTER_VALIDATE_FOLDER ,
338
- array( 'valkey' => 'FilterDirs' ,
339
- 'errmsg' => __('Directories: <b>%1$s</b> isn\'t a valid path', 'duplicator'),
340
- )
341
- );
342
-
343
- $validator->explode_filter_custom($this->Archive->FilterExts, ';' , DUP_Validator::FILTER_VALIDATE_FILE_EXT ,
344
- array( 'valkey' => 'FilterExts' ,
345
- 'errmsg' => __('File extension: <b>%1$s</b> isn\'t a valid extension', 'duplicator'),
346
- )
347
- );
348
-
349
- $validator->explode_filter_custom($this->Archive->FilterFiles, ';' , DUP_Validator::FILTER_VALIDATE_FILE ,
350
- array( 'valkey' => 'FilterFiles' ,
351
- 'errmsg' => __('Files: <b>%1$s</b> isn\'t a valid file name', 'duplicator'),
352
- )
353
- );
354
-
355
- //FILTER_VALIDATE_DOMAIN throws notice message on PHP 5.6
356
- if (defined('FILTER_VALIDATE_DOMAIN')) {
357
- $validator->filter_var($this->Installer->OptsDBHost, FILTER_VALIDATE_DOMAIN , array(
358
- 'valkey' => 'OptsDBHost' ,
359
- 'errmsg' => __('MySQL Server Host: <b>%1$s</b> isn\'t a valid host', 'duplicator'),
360
- 'acc_vals' => array(
361
- '' ,
362
- 'localhost'
363
- )
364
- )
365
- );
366
- }
367
-
368
- $validator->filter_var($this->Installer->OptsDBPort, FILTER_VALIDATE_INT , array(
369
- 'valkey' => 'OptsDBPort' ,
370
- 'errmsg' => __('MySQL Server Port: <b>%1$s</b> isn\'t a valid port', 'duplicator'),
371
- 'acc_vals' => array(
372
- ''
373
- ),
374
- 'options' => array(
375
- 'min_range' => 0
376
- )
377
- )
378
- );
379
-
380
- return $validator;
381
- }
382
-
383
- /**
384
- *
385
- * @return string
386
- */
387
- public function getInstDownloadName()
388
- {
389
- switch (DUP_Settings::Get('installer_name_mode')) {
390
- case DUP_Settings::INSTALLER_NAME_MODE_SIMPLE:
391
- $name = DUP_Installer::DEFAULT_INSTALLER_FILE_NAME_WITHOUT_HASH . DUP_Installer::INSTALLER_SERVER_EXTENSION;
392
- break;
393
- case DUP_Settings::INSTALLER_NAME_MODE_WITH_HASH:
394
- default:
395
- $name = basename($this->getLocalPackageFile(DUP_PackageFileType::Installer));
396
- break;
397
- }
398
- $info = pathinfo($name);
399
- return $info['filename'];
400
- }
401
-
402
- /**
403
- *
404
- * @return bool return true if package is a active_package_id and status is bewteen 0 and 100
405
- */
406
- public function isRunning() {
407
- return DUP_Settings::Get('active_package_id') == $this->ID && $this->Status >= 0 && $this->Status < 100;
408
- }
409
-
410
- protected function cleanObjectBeforeSave()
411
- {
412
- $this->Archive->FilterInfo->reset();
413
- }
414
-
415
- /**
416
- * Saves the active package to the package table
417
- *
418
- * @return void
419
- */
420
- public function save($extension)
421
- {
422
- global $wpdb;
423
-
424
- $this->Archive->Format = strtoupper($extension);
425
- $this->Archive->File = "{$this->NameHash}_archive.{$extension}";
426
- $this->Installer->File = apply_filters(
427
- 'duplicator_installer_file_path',
428
- "{$this->NameHash}_installer" . DUP_Installer::INSTALLER_SERVER_EXTENSION
429
- );
430
- $this->Database->File = "{$this->NameHash}_database.sql";
431
- $this->WPUser = isset($current_user->user_login) ? $current_user->user_login : 'unknown';
432
-
433
- //START LOGGING
434
- DUP_Log::Open($this->NameHash);
435
-
436
- do_action('duplicator_lite_build_before_start' , $this);
437
-
438
- $this->writeLogHeader();
439
-
440
- //CREATE DB RECORD
441
- $this->cleanObjectBeforeSave();
442
- $packageObj = serialize($this);
443
- if (!$packageObj) {
444
- DUP_Log::error("Unable to serialize package object while building record.");
445
- }
446
-
447
- $this->ID = $this->getHashKey($this->Hash);
448
-
449
- if ($this->ID != 0) {
450
- DUP_LOG::Trace("ID non zero so setting to start");
451
- $this->setStatus(DUP_PackageStatus::START);
452
- } else {
453
- DUP_LOG::Trace("ID IS zero so creating another package");
454
- $tablePrefix = DUP_Util::getTablePrefix();
455
- $results = $wpdb->insert($tablePrefix . "duplicator_packages", array(
456
- 'name' => $this->Name,
457
- 'hash' => $this->Hash,
458
- 'status' => DUP_PackageStatus::START,
459
- 'created' => current_time('mysql', get_option('gmt_offset', 1)),
460
- 'owner' => isset($current_user->user_login) ? $current_user->user_login : 'unknown',
461
- 'package' => $packageObj)
462
- );
463
- if ($results === false) {
464
- $wpdb->print_error();
465
- DUP_LOG::Trace("Problem inserting package: {$wpdb->last_error}");
466
-
467
- DUP_Log::error("Duplicator is unable to insert a package record into the database table.", "'{$wpdb->last_error}'");
468
- }
469
- $this->ID = $wpdb->insert_id;
470
- }
471
-
472
- do_action('duplicator_lite_build_start' , $this);
473
- }
474
-
475
- /**
476
- * Delete all files associated with this package ID
477
- *
478
- * @return void
479
- */
480
- public function delete()
481
- {
482
- global $wpdb;
483
-
484
- $tablePrefix = DUP_Util::getTablePrefix();
485
- $tblName = $tablePrefix.'duplicator_packages';
486
- $getResult = $wpdb->get_results($wpdb->prepare("SELECT name, hash FROM `{$tblName}` WHERE id = %d", $this->ID), ARRAY_A);
487
-
488
- if ($getResult) {
489
- $row = $getResult[0];
490
- $nameHash = "{$row['name']}_{$row['hash']}";
491
- $delResult = $wpdb->query($wpdb->prepare("DELETE FROM `{$tblName}` WHERE id = %d", $this->ID));
492
-
493
- if ($delResult != 0) {
494
- $tmpPath = DUP_Settings::getSsdirTmpPath();
495
- $ssdPath = DUP_Settings::getSsdirPath();
496
-
497
- $archiveFile = $this->getArchiveFilename();
498
- $wpConfigFile = "{$this->NameHash}_wp-config.txt";
499
-
500
- //Perms
501
- @chmod($tmpPath."/{$archiveFile}", 0644);
502
- @chmod($tmpPath."/{$nameHash}_database.sql", 0644);
503
- @chmod($tmpPath."/{$nameHash}_installer" . DUP_Installer::INSTALLER_SERVER_EXTENSION, 0644);
504
- @chmod($tmpPath."/{$nameHash}_scan.json", 0644);
505
- @chmod($tmpPath."/{$wpConfigFile}", 0644);
506
- @chmod($tmpPath."/{$nameHash}.log", 0644);
507
-
508
- @chmod($ssdPath."/{$archiveFile}", 0644);
509
- @chmod($ssdPath."/{$nameHash}_database.sql", 0644);
510
- @chmod($ssdPath."/{$nameHash}_installer" . DUP_Installer::INSTALLER_SERVER_EXTENSION, 0644);
511
- @chmod($ssdPath."/{$nameHash}_scan.json", 0644);
512
- // In older version, The plugin was storing [HASH]_wp-config.txt in main storage area. The below line code is for backward compatibility
513
- @chmod($ssdPath."/{$wpConfigFile}", 0644);
514
- @chmod($ssdPath."/{$nameHash}.log", 0644);
515
- //Remove
516
- @unlink($tmpPath."/{$archiveFile}");
517
- @unlink($tmpPath."/{$nameHash}_database.sql");
518
- @unlink($tmpPath."/{$nameHash}_installer" . DUP_Installer::INSTALLER_SERVER_EXTENSION);
519
- @unlink($tmpPath."/{$nameHash}_scan.json");
520
- @unlink($tmpPath."/{$wpConfigFile}");
521
- @unlink($tmpPath."/{$nameHash}.log");
522
-
523
- @unlink($ssdPath."/{$archiveFile}");
524
- @unlink($ssdPath."/{$nameHash}_database.sql");
525
- @unlink($ssdPath."/{$nameHash}_installer" . DUP_Installer::INSTALLER_SERVER_EXTENSION);
526
- @unlink($ssdPath."/{$nameHash}_scan.json");
527
- // In older version, The plugin was storing [HASH]_wp-config.txt in main storage area. The below line code is for backward compatibility
528
- @unlink($ssdPath."/{$wpConfigFile}");
529
- @unlink($ssdPath."/{$nameHash}.log");
530
- }
531
- }
532
- }
533
-
534
- /**
535
- * Get package archive size.
536
- * If package isn't complete it get size from sum of temp files.
537
- *
538
- * @return int size in byte
539
- */
540
- public function getArchiveSize() {
541
- $size = 0;
542
-
543
- if ($this->Status >= DUP_PackageStatus::COMPLETE) {
544
- $size = $this->Archive->Size;
545
- } else {
546
- $tmpSearch = glob(DUP_Settings::getSsdirTmpPath() . "/{$this->NameHash}_*");
547
- if (is_array($tmpSearch)) {
548
- $result = array_map('filesize', $tmpSearch);
549
- $size = array_sum($result);
550
- }
551
- }
552
-
553
- return $size;
554
- }
555
-
556
- /**
557
- * Return true if active package exist and have an active status
558
- *
559
- * @return bool
560
- */
561
- public static function is_active_package_present()
562
- {
563
- $activePakcs = self::get_ids_by_status(array(
564
- array('op' => '>=', 'status' => DUP_PackageStatus::CREATED),
565
- array('op' => '<', 'status' => DUP_PackageStatus::COMPLETE)
566
- ), true);
567
-
568
- return in_array(DUP_Settings::Get('active_package_id'), $activePakcs);
569
- }
570
-
571
- /**
572
- *
573
- * @param array $conditions es. [
574
- * relation = 'AND',
575
- * [ 'op' => '>=' ,
576
- * 'status' => DUP_PackageStatus::START ]
577
- * [ 'op' => '<' ,
578
- * 'status' => DUP_PackageStatus::COMPLETED ]
579
- * ]
580
- * @return string
581
- */
582
- protected static function statusContitionsToWhere($conditions = array())
583
- {
584
- if (empty($conditions)) {
585
- return '';
586
- } else {
587
- $accepted_op = array('<', '>', '=', '<>', '>=', '<=');
588
- $relation = (isset($conditions['relation']) && strtoupper($conditions['relation']) == 'OR') ? ' OR ' : ' AND ';
589
- unset($conditions['relation']);
590
-
591
- $str_conds = array();
592
-
593
- foreach ($conditions as $cond) {
594
- $op = (isset($cond['op']) && in_array($cond['op'], $accepted_op)) ? $cond['op'] : '=';
595
- $status = isset($cond['status']) ? (int) $cond['status'] : 0;
596
- $str_conds[] = 'status '.$op.' '.$status;
597
- }
598
-
599
- return ' WHERE '.implode($relation, $str_conds).' ';
600
- }
601
- }
602
-
603
- /**
604
- * Get packages with status conditions and/or pagination
605
- *
606
- * @global wpdb $wpdb
607
- *
608
- * @param array // $conditions es. [
609
- * relation = 'AND',
610
- * [ 'op' => '>=' ,
611
- * 'status' => DUP_PackageStatus::START ]
612
- * [ 'op' => '<' ,
613
- * 'status' => DUP_PackageStatus::COMPLETED ]
614
- * ]
615
- * if empty get all pacages
616
- * @param int $limit // max row numbers fi false the limit is PHP_INT_MAX
617
- * @param int $offset // offset 0 is at begin
618
- * @param string $orderBy // default `id` ASC if empty no order
619
- * @param string $resultType // ids => int[]
620
- * row => row without backage blob
621
- * fullRow => row with package blob
622
- * objs => array of DUP_Package objects
623
- *
624
- * @return DUP_Package[]|array[]|int[]
625
- */
626
- public static function get_packages_by_status($conditions = array(), $limit = false, $offset = 0, $orderBy = '`id` ASC', $resultType = 'obj')
627
- {
628
- global $wpdb;
629
- $table = $wpdb->base_prefix."duplicator_packages";
630
- $where = self::statusContitionsToWhere($conditions);
631
-
632
- $packages = array();
633
- $offsetStr = ' OFFSET '.(int) $offset;
634
- $limitStr = ' LIMIT '.($limit !== false ? max(0, $limit) : PHP_INT_MAX);
635
- $orderByStr = empty($orderBy) ? '' : ' ORDER BY '.$orderBy.' ';
636
- switch ($resultType) {
637
- case 'ids':
638
- $cols = '`id`';
639
- break;
640
- case 'row':
641
- $cols = '`id`,`name`,`hash`,`status`,`created`,`owner`';
642
- break;
643
- case 'fullRow':
644
- $cols = '*';
645
- break;
646
- case 'objs':
647
- default:
648
- $cols = '`status`,`package`';
649
- break;
650
- }
651
-
652
- $rows = $wpdb->get_results('SELECT '.$cols.' FROM `'.$table.'` '.$where.$orderByStr.$limitStr.$offsetStr);
653
- if ($rows != null) {
654
- switch ($resultType) {
655
- case 'ids':
656
- foreach ($rows as $row) {
657
- $packages[] = $row->id;
658
- }
659
- break;
660
- case 'row':
661
- case 'fullRow':
662
- $packages = $rows;
663
- break;
664
- case 'objs':
665
- default:
666
- foreach ($rows as $row) {
667
- $Package = unserialize($row->package);
668
- if ($Package) {
669
- // We was not storing Status in Lite 1.2.52, so it is for backward compatibility
670
- if (!isset($Package->Status)) {
671
- $Package->Status = $row->status;
672
- }
673
-
674
- $packages[] = $Package;
675
- }
676
- }
677
- }
678
- }
679
- return $packages;
680
- }
681
-
682
- /**
683
- * Get packages row db with status conditions and/or pagination
684
- *
685
- * @param array // $conditions es. [
686
- * relation = 'AND',
687
- * [ 'op' => '>=' ,
688
- * 'status' => DUP_PackageStatus::START ]
689
- * [ 'op' => '<' ,
690
- * 'status' => DUP_PackageStatus::COMPLETED ]
691
- * ]
692
- * if empty get all pacages
693
- * @param int $limit // max row numbers
694
- * @param int $offset // offset 0 is at begin
695
- * @param string $orderBy // default `id` ASC if empty no order
696
- *
697
- * @return array[] // return row database without package blob
698
- */
699
- public static function get_row_by_status($conditions = array(), $limit = false, $offset = 0, $orderBy = '`id` ASC')
700
- {
701
- return self::get_packages_by_status($conditions, $limit, $offset, $orderBy, 'row');
702
- }
703
-
704
- /**
705
- * Get packages ids with status conditions and/or pagination
706
- *
707
- * @param array // $conditions es. [
708
- * relation = 'AND',
709
- * [ 'op' => '>=' ,
710
- * 'status' => DUP_PackageStatus::START ]
711
- * [ 'op' => '<' ,
712
- * 'status' => DUP_PackageStatus::COMPLETED ]
713
- * ]
714
- * if empty get all pacages
715
- * @param int $limit // max row numbers
716
- * @param int $offset // offset 0 is at begin
717
- * @param string $orderBy // default `id` ASC if empty no order
718
- *
719
- * @return array[] // return row database without package blob
720
- */
721
- public static function get_ids_by_status($conditions = array(), $limit = false, $offset = 0, $orderBy = '`id` ASC')
722
- {
723
- return self::get_packages_by_status($conditions, $limit, $offset, $orderBy, 'ids');
724
- }
725
-
726
- /**
727
- * count package with status condition
728
- *
729
- * @global wpdb $wpdb
730
- * @param array $conditions es. [
731
- * relation = 'AND',
732
- * [ 'op' => '>=' ,
733
- * 'status' => DUP_PackageStatus::START ]
734
- * [ 'op' => '<' ,
735
- * 'status' => DUP_PackageStatus::COMPLETED ]
736
- * ]
737
- * @return int
738
- */
739
- public static function count_by_status($conditions = array())
740
- {
741
- global $wpdb;
742
-
743
- $table = $wpdb->base_prefix."duplicator_packages";
744
- $where = self::statusContitionsToWhere($conditions);
745
-
746
- $count = $wpdb->get_var("SELECT count(id) FROM `{$table}` ".$where);
747
- return $count;
748
- }
749
-
750
- /**
751
- * Execute $callback function foreach package result
752
- * For each iteration the memory is released
753
- *
754
- * @param callable $callback // function callback(DUP_Package $package)
755
- * @param array // $conditions es. [
756
- * relation = 'AND',
757
- * [ 'op' => '>=' ,
758
- * 'status' => DUP_PackageStatus::START ]
759
- * [ 'op' => '<' ,
760
- * 'status' => DUP_PackageStatus::COMPLETED ]
761
- * ]
762
- * if empty get all pacages
763
- * @param int $limit // max row numbers
764
- * @param int $offset // offset 0 is at begin
765
- * @param string $orderBy // default `id` ASC if empty no order
766
- *
767
- * @return void
768
- */
769
- public static function by_status_callback($callback, $conditions = array(), $limit = false, $offset = 0, $orderBy = '`id` ASC')
770
- {
771
- if (!is_callable($callback)) {
772
- throw new Exception('No callback function passed');
773
- }
774
-
775
- $offset = max(0, $offset);
776
- $numPackages = self::count_by_status($conditions);
777
- $maxLimit = $offset + ($limit !== false ? max(0, $limit) : PHP_INT_MAX - $offset);
778
- $numPackages = min($maxLimit, $numPackages);
779
- $orderByStr = empty($orderBy) ? '' : ' ORDER BY '.$orderBy.' ';
780
-
781
- global $wpdb;
782
- $table = $wpdb->base_prefix."duplicator_packages";
783
- $where = self::statusContitionsToWhere($conditions);
784
- $sql = 'SELECT * FROM `'.$table.'` '.$where.$orderByStr.' LIMIT 1 OFFSET ';
785
-
786
- for (; $offset < $numPackages; $offset ++) {
787
- $rows = $wpdb->get_results($sql.$offset);
788
- if ($rows != null) {
789
- $Package = @unserialize($rows[0]->package);
790
- if ($Package) {
791
- if (empty($Package->ID)) {
792
- $Package->ID = $rows[0]->id;
793
- }
794
- // We was not storing Status in Lite 1.2.52, so it is for backward compatibility
795
- if (!isset($Package->Status)) {
796
- $Package->Status = $rows[0]->status;
797
- }
798
- call_user_func($callback, $Package);
799
- unset($Package);
800
- }
801
- unset($rows);
802
- }
803
- }
804
- }
805
-
806
- public static function purge_incomplete_package()
807
- {
808
- $packages = self::get_packages_by_status(array(
809
- 'relation' => 'AND',
810
- array('op' => '>=', 'status' => DUP_PackageStatus::CREATED),
811
- array('op' => '<', 'status' => DUP_PackageStatus::COMPLETE)
812
- ), 1, 0, '`id` ASC');
813
-
814
-
815
- if (count($packages) > 0) {
816
- foreach ($packages as $package) {
817
- if (!$package->isRunning()) {
818
- $package->delete();
819
- }
820
- }
821
- }
822
- }
823
-
824
- /**
825
- * Check the DupArchive build to make sure it is good
826
- *
827
- * @return void
828
- */
829
- public function runDupArchiveBuildIntegrityCheck()
830
- {
831
- //INTEGRITY CHECKS
832
- //We should not rely on data set in the serlized object, we need to manually check each value
833
- //indepentantly to have a true integrity check.
834
- DUP_Log::info("\n********************************************************************************");
835
- DUP_Log::info("INTEGRITY CHECKS:");
836
- DUP_Log::info("********************************************************************************");
837
-
838
- //------------------------
839
- //SQL CHECK: File should be at minimum 5K. A base WP install with only Create tables is about 9K
840
- $sql_temp_path = DUP_Settings::getSsdirTmpPath() . '/' . $this->Database->File;
841
- $sql_temp_size = @filesize($sql_temp_path);
842
- $sql_easy_size = DUP_Util::byteSize($sql_temp_size);
843
- $sql_done_txt = DUP_Util::tailFile($sql_temp_path, 3);
844
- DUP_Log::Trace('[DUP ARCHIVE] '.__FUNCTION__.' '.__LINE__);
845
-
846
- // Note: Had to add extra size check of 800 since observed bad sql when filter was on
847
- if (!strstr($sql_done_txt, 'DUPLICATOR_MYSQLDUMP_EOF') || (!$this->Database->FilterOn && $sql_temp_size < 5120) || ($this->Database->FilterOn && $this->Database->info->tablesFinalCount > 0 && $sql_temp_size < 800)) {
848
- DUP_Log::Trace('[DUP ARCHIVE] '.__FUNCTION__.' '.__LINE__);
849
-
850
- $error_text = "ERROR: SQL file not complete. The file {$sql_temp_path} looks too small ($sql_temp_size bytes) or the end of file marker was not found.";
851
- $this->BuildProgress->set_failed($error_text);
852
- $this->setStatus(DUP_PackageStatus::ERROR);
853
- DUP_Log::error("$error_text", '', Dup_ErrorBehavior::LogOnly);
854
- return;
855
- }
856
-
857
- DUP_Log::Trace('[DUP ARCHIVE] '.__FUNCTION__.' '.__LINE__);
858
- DUP_Log::Info("SQL FILE: {$sql_easy_size}");
859
-
860
- //------------------------
861
- //INSTALLER CHECK:
862
- $exe_temp_path = DUP_Settings::getSsdirTmpPath() . '/' . $this->Installer->File;
863
-
864
- $exe_temp_size = @filesize($exe_temp_path);
865
- $exe_easy_size = DUP_Util::byteSize($exe_temp_size);
866
- $exe_done_txt = DUP_Util::tailFile($exe_temp_path, 10);
867
-
868
- if (!strstr($exe_done_txt, 'DUPLICATOR_INSTALLER_EOF') && !$this->BuildProgress->failed) {
869
- //$this->BuildProgress->failed = true;
870
- $error_message = 'ERROR: Installer file not complete. The end of file marker was not found. Please try to re-create the package.';
871
-
872
- $this->BuildProgress->set_failed($error_message);
873
- $this->Status = DUP_PackageStatus::ERROR;
874
- $this->update();
875
- DUP_Log::error($error_message, '', Dup_ErrorBehavior::LogOnly);
876
- return;
877
- }
878
- DUP_Log::info("INSTALLER FILE: {$exe_easy_size}");
879
-
880
- //------------------------
881
- //ARCHIVE CHECK:
882
- DUP_LOG::trace("Archive file count is " . $this->Archive->file_count);
883
-
884
- if ($this->Archive->file_count != -1) {
885
- $zip_easy_size = DUP_Util::byteSize($this->Archive->Size);
886
- if (!($this->Archive->Size)) {
887
- //$this->BuildProgress->failed = true;
888
- $error_message = "ERROR: The archive file contains no size.";
889
-
890
- $this->BuildProgress->set_failed($error_message);
891
- $this->setStatus(DUP_PackageStatus::ERROR);
892
- DUP_Log::error($error_message, "Archive Size: {$zip_easy_size}", Dup_ErrorBehavior::LogOnly);
893
- return;
894
- }
895
-
896
- $scan_filepath = DUP_Settings::getSsdirTmpPath() . "/{$this->NameHash}_scan.json";
897
- $json = '';
898
-
899
- DUP_LOG::Trace("***********Does $scan_filepath exist?");
900
- if (file_exists($scan_filepath)) {
901
- $json = file_get_contents($scan_filepath);
902
- } else {
903
- $error_message = sprintf(__("Can't find Scanfile %s. Please ensure there no non-English characters in the package or schedule name.", 'duplicator'), $scan_filepath);
904
-
905
- //$this->BuildProgress->failed = true;
906
- //$this->setStatus(DUP_PackageStatus::ERROR);
907
- $this->BuildProgress->set_failed($error_message);
908
- $this->setStatus(DUP_PackageStatus::ERROR);
909
-
910
- DUP_Log::error($error_message, '', Dup_ErrorBehavior::LogOnly);
911
- return;
912
- }
913
-
914
- $scanReport = json_decode($json);
915
-
916
- //RSR TODO: rework/simplify the validateion of duparchive
917
- $dirCount = count($scanReport->ARC->Dirs);
918
- $numInstallerDirs = $this->Installer->numDirsAdded;
919
- $fileCount = count($scanReport->ARC->Files);
920
- $numInstallerFiles = $this->Installer->numFilesAdded;
921
-
922
- $expected_filecount = $dirCount + $numInstallerDirs + $fileCount + $numInstallerFiles + 1 -1; // Adding database.sql but subtracting the root dir
923
- //Dup_Log::trace("#### a:{$dirCount} b:{$numInstallerDirs} c:{$fileCount} d:{$numInstallerFiles} = {$expected_filecount}");
924
-
925
- DUP_Log::info("ARCHIVE FILE: {$zip_easy_size} ");
926
- DUP_Log::info(sprintf(__('EXPECTED FILE/DIRECTORY COUNT: %1$s', 'duplicator'), number_format($expected_filecount)));
927
- DUP_Log::info(sprintf(__('ACTUAL FILE/DIRECTORY COUNT: %1$s', 'duplicator'), number_format($this->Archive->file_count)));
928
-
929
- $this->ExeSize = $exe_easy_size;
930
- $this->ZipSize = $zip_easy_size;
931
-
932
- /* ------- ZIP Filecount Check -------- */
933
- // Any zip of over 500 files should be within 2% - this is probably too loose but it will catch gross errors
934
- DUP_LOG::trace("Expected filecount = $expected_filecount and archive filecount=" . $this->Archive->file_count);
935
-
936
- if ($expected_filecount > 500) {
937
- $straight_ratio = (float) $expected_filecount / (float) $this->Archive->file_count;
938
-
939
- $warning_count = $scanReport->ARC->WarnFileCount + $scanReport->ARC->WarnDirCount + $scanReport->ARC->UnreadableFileCount + $scanReport->ARC->UnreadableDirCount;
940
- DUP_LOG::trace("Warn/unread counts) warnfile:{$scanReport->ARC->WarnFileCount} warndir:{$scanReport->ARC->WarnDirCount} unreadfile:{$scanReport->ARC->UnreadableFileCount} unreaddir:{$scanReport->ARC->UnreadableDirCount}");
941
- $warning_ratio = ((float) ($expected_filecount + $warning_count)) / (float) $this->Archive->file_count;
942
- DUP_LOG::trace("Straight ratio is $straight_ratio and warning ratio is $warning_ratio. # Expected=$expected_filecount # Warning=$warning_count and #Archive File {$this->Archive->file_count}");
943
-
944
- // Allow the real file count to exceed the expected by 10% but only allow 1% the other way
945
- if (($straight_ratio < 0.90) || ($straight_ratio > 1.01)) {
946
- // Has to exceed both the straight as well as the warning ratios
947
- if (($warning_ratio < 0.90) || ($warning_ratio > 1.01)) {
948
- $error_message = sprintf('ERROR: File count in archive vs expected suggests a bad archive (%1$d vs %2$d).', $this->Archive->file_count, $expected_filecount);
949
- $this->BuildProgress->set_failed($error_message);
950
- $this->Status = DUP_PackageStatus::ERROR;
951
- $this->update();
952
-
953
- DUP_Log::error($error_message, '');
954
- return;
955
- }
956
- }
957
- }
958
- }
959
-
960
- /* ------ ZIP CONSISTENCY CHECK ------ */
961
- if ($this->Archive->getBuildMode() == DUP_Archive_Build_Mode::ZipArchive) {
962
- DUP_LOG::trace("Running ZipArchive consistency check");
963
- $zipPath = DUP_Settings::getSsdirTmpPath()."/{$this->Archive->File}";
964
-
965
- $zip = new ZipArchive();
966
-
967
- // ZipArchive::CHECKCONS will enforce additional consistency checks
968
- $res = $zip->open($zipPath, ZipArchive::CHECKCONS);
969
-
970
- if ($res !== TRUE) {
971
- $consistency_error = sprintf(__('ERROR: Cannot open created archive. Error code = %1$s', 'duplicator'), $res);
972
-
973
- DUP_LOG::trace($consistency_error);
974
- switch ($res) {
975
- case ZipArchive::ER_NOZIP :
976
- $consistency_error = __('ERROR: Archive is not valid zip archive.', 'duplicator');
977
- break;
978
-
979
- case ZipArchive::ER_INCONS :
980
- $consistency_error = __("ERROR: Archive doesn't pass consistency check.", 'duplicator');
981
- break;
982
-
983
-
984
- case ZipArchive::ER_CRC :
985
- $consistency_error = __("ERROR: Archive checksum is bad.", 'duplicator');
986
- break;
987
- }
988
-
989
- $this->BuildProgress->set_failed($consistency_error);
990
- $this->Status = DUP_PackageStatus::ERROR;
991
- $this->update();
992
-
993
- DUP_LOG::trace($consistency_error);
994
- DUP_Log::error($consistency_error, '');
995
- } else {
996
- DUP_Log::info(__('ARCHIVE CONSISTENCY TEST: Pass', 'duplicator'));
997
- DUP_LOG::trace("Zip for package $this->ID passed consistency test");
998
- }
999
-
1000
- $zip->close();
1001
- }
1002
- }
1003
-
1004
- public function getLocalPackageFile($file_type)
1005
- {
1006
- $file_path = null;
1007
-
1008
- if ($file_type == DUP_PackageFileType::Installer) {
1009
- DUP_Log::Trace("Installer requested");
1010
- $file_name = apply_filters('duplicator_installer_file_path', $this->getInstallerFilename());
1011
- } else if ($file_type == DUP_PackageFileType::Archive) {
1012
- DUP_Log::Trace("Archive requested");
1013
- $file_name = $this->getArchiveFilename();
1014
- } else if ($file_type == DUP_PackageFileType::SQL) {
1015
- DUP_Log::Trace("SQL requested");
1016
- $file_name = $this->getDatabaseFilename();
1017
- } else {
1018
- DUP_Log::Trace("Log requested");
1019
- $file_name = $this->getLogFilename();
1020
- }
1021
-
1022
- $file_path = DUP_Settings::getSsdirPath() . "/$file_name";
1023
- DUP_Log::Trace("File path $file_path");
1024
-
1025
- if (file_exists($file_path)) {
1026
- return $file_path;
1027
- } else {
1028
- return null;
1029
- }
1030
- }
1031
-
1032
- public function getScanFilename()
1033
- {
1034
- return $this->NameHash . '_scan.json';
1035
- }
1036
-
1037
- public function getScanUrl()
1038
- {
1039
- return DUP_Settings::getSsdirUrl()."/".$this->getScanFilename();
1040
- }
1041
-
1042
- public function getLogFilename()
1043
- {
1044
- return $this->NameHash . '.log';
1045
- }
1046
-
1047
- public function getLogUrl()
1048
- {
1049
- return DUP_Settings::getSsdirUrl()."/".$this->getLogFilename();
1050
- }
1051
-
1052
- public function getArchiveFilename()
1053
- {
1054
- $extension = strtolower($this->Archive->Format);
1055
-
1056
- return "{$this->NameHash}_archive.{$extension}";
1057
- }
1058
-
1059
- public function getInstallerFilename()
1060
- {
1061
- return "{$this->NameHash}_installer" . DUP_Installer::INSTALLER_SERVER_EXTENSION;
1062
- }
1063
-
1064
- public function getDatabaseFilename()
1065
- {
1066
- return $this->NameHash . '_database.sql';
1067
- }
1068
-
1069
- /**
1070
- * @param int $type
1071
- * @return array
1072
- */
1073
- public function getPackageFileDownloadInfo($type)
1074
- {
1075
- $result = array(
1076
- "filename" => "",
1077
- "url" => ""
1078
- );
1079
-
1080
- switch ($type){
1081
- case DUP_PackageFileType::Archive;
1082
- $result["filename"] = $this->Archive->File;
1083
- $result["url"] = $this->Archive->getURL();
1084
- break;
1085
- case DUP_PackageFileType::SQL;
1086
- $result["filename"] = $this->Database->File;
1087
- $result["url"] = $this->Database->getURL();
1088
- break;
1089
- case DUP_PackageFileType::Log;
1090
- $result["filename"] = $this->getLogFilename();
1091
- $result["url"] = $this->getLogUrl();
1092
- break;
1093
- case DUP_PackageFileType::Scan;
1094
- $result["filename"] = $this->getScanFilename();
1095
- $result["url"] = $this->getScanUrl();
1096
- break;
1097
- default:
1098
- break;
1099
- }
1100
-
1101
- return $result;
1102
- }
1103
-
1104
- public function getInstallerDownloadInfo()
1105
- {
1106
- return array(
1107
- "id" => $this->ID,
1108
- "hash" => $this->Hash
1109
- );
1110
- }
1111
-
1112
- /**
1113
- * Removes all files except those of active packages
1114
- */
1115
- public static function not_active_files_tmp_cleanup()
1116
- {
1117
- //Check for the 'tmp' folder just for safe measures
1118
- if (! is_dir(DUP_Settings::getSsdirTmpPath()) && (strpos(DUP_Settings::getSsdirTmpPath(), 'tmp') !== false) ) {
1119
- return;
1120
- }
1121
-
1122
- $globs = glob(DUP_Settings::getSsdirTmpPath().'/*.*');
1123
- if (! is_array($globs) || $globs === FALSE) {
1124
- return;
1125
- }
1126
-
1127
- // RUNNING PACKAGES
1128
- $active_pack = self::get_row_by_status(array(
1129
- 'relation' => 'AND',
1130
- array('op' => '>=' , 'status' => DUP_PackageStatus::CREATED ),
1131
- array('op' => '<' , 'status' => DUP_PackageStatus::COMPLETE )
1132
- ));
1133
- $active_files = array();
1134
- foreach($active_pack as $row) {
1135
- $active_files[] = $row->name.'_'.$row->hash;
1136
- }
1137
-
1138
- // ERRORS PACKAGES
1139
- $err_pack = self::get_row_by_status(array(
1140
- array('op' => '<' , 'status' => DUP_PackageStatus::CREATED )
1141
- ));
1142
- $force_del_files = array();
1143
- foreach($err_pack as $row) {
1144
- $force_del_files[] = $row->name.'_'.$row->hash;
1145
- }
1146
-
1147
- // Don't remove json file;
1148
- $extension_filter = array('json');
1149
-
1150
- // Calculate delta time for old files
1151
- $oldTimeToClean = time() - DUPLICATOR_TEMP_CLEANUP_SECONDS;
1152
-
1153
- foreach ($globs as $glob_full_path) {
1154
- // Don't remove sub dir
1155
- if (is_dir($glob_full_path)) {
1156
- continue;
1157
- }
1158
-
1159
- $file_name = basename($glob_full_path);
1160
- // skip all active packages
1161
- foreach ($active_files as $c_nameHash) {
1162
- if (strpos($file_name, $c_nameHash) === 0) {
1163
- continue 2;
1164
- }
1165
- }
1166
-
1167
- // Remove all old files
1168
- if (filemtime($glob_full_path) <= $oldTimeToClean) {
1169
- @unlink($glob_full_path);
1170
- continue;
1171
- }
1172
-
1173
- // remove all error packages files
1174
- foreach ($force_del_files as $c_nameHash) {
1175
- if (strpos($file_name, $c_nameHash) === 0) {
1176
- @unlink($glob_full_path);
1177
- continue 2;
1178
- }
1179
- }
1180
-
1181
- $file_info = pathinfo($glob_full_path);
1182
- // skip json file for pre build packages
1183
- if (in_array($file_info['extension'], $extension_filter) || in_array($file_name, $active_files)) {
1184
- continue;
1185
- }
1186
-
1187
- @unlink($glob_full_path);
1188
- }
1189
- }
1190
-
1191
- /**
1192
- * Cleans up the temp storage folder have a time interval
1193
- *
1194
- * @return void
1195
- */
1196
- public static function safeTmpCleanup($purge_temp_archives = false)
1197
- {
1198
- if ($purge_temp_archives) {
1199
- $dir = DUP_Settings::getSsdirTmpPath() . "/*_archive.zip.*";
1200
- foreach (glob($dir) as $file_path) {
1201
- unlink($file_path);
1202
- }
1203
- $dir = DUP_Settings::getSsdirTmpPath() . "/*_archive.daf.*";
1204
- foreach (glob($dir) as $file_path) {
1205
- unlink($file_path);
1206
- }
1207
- } else {
1208
- //Remove all temp files that are 24 hours old
1209
- $dir = DUP_Settings::getSsdirTmpPath() . "/*";
1210
-
1211
- $files = glob($dir);
1212
-
1213
- if ($files !== false) {
1214
- foreach ($files as $file_path) {
1215
- // Cut back to keeping things around for just an hour 15 min
1216
- if (filemtime($file_path) <= time() - DUPLICATOR_TEMP_CLEANUP_SECONDS) {
1217
- unlink($file_path);
1218
- }
1219
- }
1220
- }
1221
- }
1222
- }
1223
-
1224
- /**
1225
- * Starts the package DupArchive progressive build process - always assumed to only run off active package, NOT one in the package table
1226
- *
1227
- * @return obj Returns a DUP_Package object
1228
- */
1229
- public function runDupArchiveBuild()
1230
- {
1231
- $this->BuildProgress->start_timer();
1232
- DUP_Log::Trace('Called');
1233
-
1234
- if ($this->BuildProgress->failed) {
1235
-
1236
- DUP_LOG::Trace("build progress failed so setting package to failed");
1237
- $this->setStatus(DUP_PackageStatus::ERROR);
1238
- $message = "Package creation failed.";
1239
- DUP_Log::Trace($message);
1240
- return true;
1241
- }
1242
-
1243
- if ($this->BuildProgress->initialized == false) {
1244
- DUP_Log::Trace('[DUP ARCHIVE] INIZIALIZE');
1245
- $this->BuildProgress->initialized = true;
1246
- $this->TimerStart = Dup_Util::getMicrotime();
1247
- $this->update();
1248
- }
1249
-
1250
- //START BUILD
1251
- if (!$this->BuildProgress->database_script_built) {
1252
- DUP_Log::Info('[DUP ARCHIVE] BUILDING DATABASE');
1253
- $this->Database->build($this, Dup_ErrorBehavior::ThrowException);
1254
- DUP_Log::Info('[DUP ARCHIVE] VALIDATING DATABASE');
1255
- $this->Database->validateTableWiseRowCounts();
1256
- $this->BuildProgress->database_script_built = true;
1257
- $this->update();
1258
- DUP_Log::Info('[DUP ARCHIVE] DONE DATABASE');
1259
- } else if (!$this->BuildProgress->archive_built) {
1260
- DUP_Log::Info('[DUP ARCHIVE] BUILDING ARCHIVE');
1261
- $this->Archive->build($this);
1262
- $this->update();
1263
- DUP_Log::Info('[DUP ARCHIVE] DONE ARCHIVE');
1264
- } else if (!$this->BuildProgress->installer_built) {
1265
- DUP_Log::Info('[DUP ARCHIVE] BUILDING INSTALLER');
1266
- // Installer being built is stuffed into the archive build phase
1267
- }
1268
-
1269
- if ($this->BuildProgress->has_completed()) {
1270
- DUP_Log::Info('[DUP ARCHIVE] HAS COMPLETED CLOSING');
1271
-
1272
- if (!$this->BuildProgress->failed) {
1273
- DUP_LOG::Info("[DUP ARCHIVE] DUP ARCHIVE INTEGRITY CHECK");
1274
- // Only makees sense to perform build integrity check on completed archives
1275
- $this->runDupArchiveBuildIntegrityCheck();
1276
- } else {
1277
- DUP_LOG::trace("top of loop build progress failed");
1278
- }
1279
-
1280
- $timerEnd = DUP_Util::getMicrotime();
1281
- $timerSum = DUP_Util::elapsedTime($timerEnd, $this->TimerStart);
1282
- $this->Runtime = $timerSum;
1283
-
1284
- //FINAL REPORT
1285
- $info = "\n********************************************************************************\n";
1286
- $info .= "RECORD ID:[{$this->ID}]\n";
1287
- $info .= "TOTAL PROCESS RUNTIME: {$timerSum}\n";
1288
- $info .= "PEAK PHP MEMORY USED: " . DUP_Server::getPHPMemory(true) . "\n";
1289
- $info .= "DONE PROCESSING => {$this->Name} " . @date("Y-m-d H:i:s") . "\n";
1290
-
1291
- DUP_Log::info($info);
1292
- DUP_LOG::trace("Done package building");
1293
-
1294
- if (!$this->BuildProgress->failed) {
1295
- DUP_Log::Trace('[DUP ARCHIVE] HAS COMPLETED DONE');
1296
- $this->setStatus(DUP_PackageStatus::COMPLETE);
1297
- DUP_LOG::Trace("Cleaning up duparchive temp files");
1298
- //File Cleanup
1299
- $this->buildCleanup();
1300
- do_action('duplicator_lite_build_completed' , $this);
1301
- } else {
1302
- DUP_Log::Trace('[DUP ARCHIVE] HAS COMPLETED ERROR');
1303
- }
1304
- }
1305
- DUP_Log::Close();
1306
- return $this->BuildProgress->has_completed();
1307
- }
1308
-
1309
- /**
1310
- * Starts the package build process
1311
- *
1312
- * @return obj Returns a DUP_Package object
1313
- */
1314
- public function runZipBuild()
1315
- {
1316
- $timerStart = DUP_Util::getMicrotime();
1317
-
1318
- DUP_Log::Trace('#### start of zip build');
1319
- //START BUILD
1320
- //PHPs serialze method will return the object, but the ID above is not passed
1321
- //for one reason or another so passing the object back in seems to do the trick
1322
- $this->Database->build($this, Dup_ErrorBehavior::ThrowException);
1323
- $this->Database->validateTableWiseRowCounts();
1324
- $this->Archive->build($this);
1325
- $this->Installer->build($this);
1326
-
1327
- //INTEGRITY CHECKS
1328
- /*DUP_Log::Info("\n********************************************************************************");
1329
- DUP_Log::Info("INTEGRITY CHECKS:");
1330
- DUP_Log::Info("********************************************************************************");*/
1331
- $this->runDupArchiveBuildIntegrityCheck();
1332
- $dbSizeRead = DUP_Util::byteSize($this->Database->Size);
1333
- $zipSizeRead = DUP_Util::byteSize($this->Archive->Size);
1334
- $exeSizeRead = DUP_Util::byteSize($this->Installer->Size);
1335
-
1336
- $timerEnd = DUP_Util::getMicrotime();
1337
- $timerSum = DUP_Util::elapsedTime($timerEnd, $timerStart);
1338
-
1339
- $this->Runtime = $timerSum;
1340
- $this->ExeSize = $exeSizeRead;
1341
- $this->ZipSize = $zipSizeRead;
1342
-
1343
-
1344
- $this->buildCleanup();
1345
-
1346
- //FINAL REPORT
1347
- $info = "\n********************************************************************************\n";
1348
- $info .= "RECORD ID:[{$this->ID}]\n";
1349
- $info .= "TOTAL PROCESS RUNTIME: {$timerSum}\n";
1350
- $info .= "PEAK PHP MEMORY USED: ".DUP_Server::getPHPMemory(true)."\n";
1351
- $info .= "DONE PROCESSING => {$this->Name} ".@date(get_option('date_format')." ".get_option('time_format'))."\n";
1352
-
1353
-
1354
- DUP_Log::Info($info);
1355
- DUP_Log::Close();
1356
-
1357
- $this->setStatus(DUP_PackageStatus::COMPLETE);
1358
- return $this;
1359
- }
1360
-
1361
- /**
1362
- * Saves the active options associted with the active(latest) package.
1363
- *
1364
- * @see DUP_Package::getActive
1365
- *
1366
- * @param $_POST $post The Post server object
1367
- *
1368
- * @return null
1369
- */
1370
- public function saveActive($post = null)
1371
- {
1372
- global $wp_version;
1373
-
1374
- if (isset($post)) {
1375
- $post = stripslashes_deep($post);
1376
-
1377
- $name = isset($post['package-name']) ? trim($post['package-name']) : self::getDefaultName();
1378
- $name = str_replace(array(' ', '-'), '_', $name);
1379
- $name = str_replace(array('.', ';', ':', "'", '"'), '', $name);
1380
- $name = sanitize_file_name($name);
1381
- $name = substr(trim($name), 0, 40);
1382
-
1383
- if (isset($post['filter-dirs'])) {
1384
- $post_filter_dirs = sanitize_text_field($post['filter-dirs']);
1385
- $filter_dirs = $this->Archive->parseDirectoryFilter($post_filter_dirs);
1386
- } else {
1387
- $filter_dirs = '';
1388
- }
1389
-
1390
- if (isset($post['filter-files'])) {
1391
- $post_filter_files = sanitize_text_field($post['filter-files']);
1392
- $filter_files = $this->Archive->parseFileFilter($post_filter_files);
1393
- } else {
1394
- $filter_files = '';
1395
- }
1396
-
1397
- if (isset($post['filter-exts'])) {
1398
- $post_filter_exts = sanitize_text_field($post['filter-exts']);
1399
- $filter_exts = $this->Archive->parseExtensionFilter($post_filter_exts);
1400
- } else {
1401
- $filter_exts = '';
1402
- }
1403
-
1404
- $tablelist = '';
1405
- if (isset($post['dbtables'])) {
1406
- $tablelist = implode(',', $post['dbtables']);
1407
- }
1408
-
1409
- if (isset($post['dbcompat'])) {
1410
- $post_dbcompat = sanitize_text_field($post['dbcompat']);
1411
- $compatlist = isset($post['dbcompat']) ? implode(',', $post_dbcompat) : '';
1412
- } else {
1413
- $compatlist = '';
1414
- }
1415
-
1416
- $dbversion = DUP_DB::getVersion();
1417
- $dbversion = is_null($dbversion) ? '- unknown -' : sanitize_text_field($dbversion);
1418
- $dbcomments = sanitize_text_field(DUP_DB::getVariable('version_comment'));
1419
- $dbcomments = is_null($dbcomments) ? '- unknown -' : sanitize_text_field($dbcomments);
1420
-
1421
- //PACKAGE
1422
- $this->Created = gmdate("Y-m-d H:i:s");
1423
- $this->Version = DUPLICATOR_VERSION;
1424
- $this->VersionOS = defined('PHP_OS') ? PHP_OS : 'unknown';
1425
- $this->VersionWP = $wp_version;
1426
- $this->VersionPHP = phpversion();
1427
- $this->VersionDB = sanitize_text_field($dbversion);
1428
- $this->Name = sanitize_text_field($name);
1429
- $this->Hash = $this->makeHash();
1430
- $this->NameHash = sanitize_text_field("{$this->Name}_{$this->Hash}");
1431
-
1432
- $this->Notes = sanitize_textarea_field($post['package-notes']);
1433
- //ARCHIVE
1434
- $this->Archive->PackDir = duplicator_get_abs_path();
1435
- $this->Archive->Format = 'ZIP';
1436
- $this->Archive->FilterOn = isset($post['filter-on']) ? 1 : 0;
1437
- $this->Archive->ExportOnlyDB = isset($post['export-onlydb']) ? 1 : 0;
1438
- $this->Archive->FilterDirs = sanitize_textarea_field($filter_dirs);
1439
- $this->Archive->FilterFiles = sanitize_textarea_field($filter_files);
1440
- $this->Archive->FilterExts = str_replace(array('.', ' '), '', $filter_exts);
1441
- //INSTALLER
1442
- $this->Installer->OptsDBHost = sanitize_text_field($post['dbhost']);
1443
- $this->Installer->OptsDBPort = sanitize_text_field($post['dbport']);
1444
- $this->Installer->OptsDBName = sanitize_text_field($post['dbname']);
1445
- $this->Installer->OptsDBUser = sanitize_text_field($post['dbuser']);
1446
- $this->Installer->OptsDBCharset = sanitize_text_field($post['dbcharset']);
1447
- $this->Installer->OptsDBCollation = sanitize_text_field($post['dbcollation']);
1448
- $this->Installer->OptsSecureOn = isset($post['secure-on']) ? 1 : 0;
1449
- $post_secure_pass = sanitize_text_field($post['secure-pass']);
1450
- $this->Installer->OptsSecurePass = DUP_Util::installerScramble($post_secure_pass);
1451
- //DATABASE
1452
- $this->Database->FilterOn = isset($post['dbfilter-on']) ? 1 : 0;
1453
- $this->Database->FilterTables = sanitize_text_field($tablelist);
1454
- $this->Database->Compatible = $compatlist;
1455
- $this->Database->Comments = sanitize_text_field($dbcomments);
1456
-
1457
- update_option(self::OPT_ACTIVE, $this);
1458
- }
1459
- }
1460
-
1461
- /**
1462
- * Update the serialized package and status in the database
1463
- *
1464
- * @return void
1465
- */
1466
- public function update()
1467
- {
1468
- global $wpdb;
1469
-
1470
- $this->Status = number_format($this->Status, 1, '.', '');
1471
- $this->cleanObjectBeforeSave();
1472
- $packageObj = serialize($this);
1473
-
1474
- if (!$packageObj) {
1475
- DUP_Log::error("Package SetStatus was unable to serialize package object while updating record.");
1476
- }
1477
-
1478
- $wpdb->flush();
1479
- $tablePrefix = DUP_Util::getTablePrefix();
1480
- $table = $tablePrefix."duplicator_packages";
1481
- $sql = "UPDATE `{$table}` SET status = {$this->Status},";
1482
- $sql .= "package = '" . esc_sql($packageObj) . "'";
1483
- $sql .= "WHERE ID = {$this->ID}";
1484
-
1485
- DUP_Log::Trace("UPDATE PACKAGE ID = {$this->ID} STATUS = {$this->Status}");
1486
-
1487
- //DUP_Log::Trace('####Executing SQL' . $sql . '-----------');
1488
- $wpdb->query($sql);
1489
- }
1490
-
1491
- /**
1492
- * Save any property of this class through reflection
1493
- *
1494
- * @param $property A valid public property in this class
1495
- * @param $value The value for the new dynamic property
1496
- *
1497
- * @return null
1498
- */
1499
- public function saveActiveItem($property, $value)
1500
- {
1501
- $package = self::getActive();
1502
-
1503
- $reflectionClass = new ReflectionClass($package);
1504
- $reflectionClass->getProperty($property)->setValue($package, $value);
1505
- update_option(self::OPT_ACTIVE, $package);
1506
- }
1507
-
1508
- /**
1509
- * Sets the status to log the state of the build
1510
- * The status level for where the package is
1511
- *
1512
- * @param int $status
1513
- *
1514
- * @return void
1515
- */
1516
- public function setStatus($status)
1517
- {
1518
- if (!isset($status)) {
1519
- DUP_Log::error("Package SetStatus did not receive a proper code.");
1520
- }
1521
- $this->Status = $status;
1522
- $this->update();
1523
- }
1524
-
1525
- /**
1526
- * Does a hash already exists
1527
- * Returns 0 if no hash is found, if found returns the table ID
1528
- *
1529
- * @param string $hash An existing hash value
1530
- *
1531
- * @return int
1532
- */
1533
- public function getHashKey($hash)
1534
- {
1535
- global $wpdb;
1536
-
1537
- $tablePrefix = DUP_Util::getTablePrefix();
1538
- $table = $tablePrefix."duplicator_packages";
1539
- $qry = $wpdb->get_row("SELECT ID, hash FROM `{$table}` WHERE hash = '{$hash}'");
1540
- if (is_null($qry) || strlen($qry->hash) == 0) {
1541
- return 0;
1542
- } else {
1543
- return $qry->ID;
1544
- }
1545
- }
1546
-
1547
- /**
1548
- * Makes the hashkey for the package files
1549
- *
1550
- * @return string // A unique hashkey
1551
- */
1552
- public function makeHash()
1553
- {
1554
- try {
1555
- if (function_exists('random_bytes') && DUP_Util::PHP53()) {
1556
- return bin2hex(random_bytes(8)) . mt_rand(1000, 9999) . '_' . date("YmdHis");
1557
- } else {
1558
- return strtolower(md5(uniqid(rand(), true))) . '_' . date("YmdHis");
1559
- }
1560
- } catch (Exception $exc) {
1561
- return strtolower(md5(uniqid(rand(), true))) . '_' . date("YmdHis");
1562
- }
1563
- }
1564
-
1565
- /**
1566
- * Gets the active package which is defined as the package that was lasted saved.
1567
- * Do to cache issues with the built in WP function get_option moved call to a direct DB call.
1568
- *
1569
- * @see DUP_Package::saveActive
1570
- *
1571
- * @return DUP_Package // A copy of the DUP_Package object
1572
- */
1573
- public static function getActive()
1574
- {
1575
- global $wpdb;
1576
-
1577
- $obj = new DUP_Package();
1578
- $row = $wpdb->get_row($wpdb->prepare("SELECT option_value FROM `{$wpdb->options}` WHERE option_name = %s LIMIT 1", self::OPT_ACTIVE));
1579
- if (is_object($row)) {
1580
- $obj = @unserialize($row->option_value);
1581
- }
1582
- //Incase unserilaize fails
1583
- $obj = (is_object($obj)) ? $obj : new DUP_Package();
1584
-
1585
- return $obj;
1586
- }
1587
-
1588
- /**
1589
- * Gets the Package by ID
1590
- *
1591
- * @param int $id A valid package id form the duplicator_packages table
1592
- *
1593
- * @return DUP_Package // A copy of the DUP_Package object
1594
- */
1595
- public static function getByID($id)
1596
- {
1597
- global $wpdb;
1598
- $obj = new DUP_Package();
1599
- $tablePrefix = DUP_Util::getTablePrefix();
1600
- $sql = $wpdb->prepare("SELECT * FROM `{$tablePrefix}duplicator_packages` WHERE ID = %d", $id);
1601
- $row = $wpdb->get_row($sql);
1602
- if (is_object($row)) {
1603
- $obj = @unserialize($row->package);
1604
- // We was not storing Status in Lite 1.2.52, so it is for backward compatibility
1605
- if (!isset($obj->Status)) {
1606
- $obj->Status = $row->status;
1607
- }
1608
- }
1609
- //Incase unserilaize fails
1610
- $obj = (is_object($obj)) ? $obj : null;
1611
- return $obj;
1612
- }
1613
-
1614
- /**
1615
- * Gets a default name for the package
1616
- *
1617
- * @return string // A default package name such as 20170218_blogname
1618
- */
1619
- public static function getDefaultName($preDate = true)
1620
- {
1621
- //Remove specail_chars from final result
1622
- $special_chars = array(".", "-");
1623
- $name = ($preDate)
1624
- ? date('Ymd') . '_' . sanitize_title(get_bloginfo('name', 'display'))
1625
- : sanitize_title(get_bloginfo('name', 'display')) . '_' . date('Ymd');
1626
- $name = substr(sanitize_file_name($name), 0, 40);
1627
- $name = str_replace($special_chars, '', $name);
1628
- return $name;
1629
- }
1630
-
1631
- /**
1632
- * Cleanup all tmp files
1633
- *
1634
- * @param all empty all contents
1635
- *
1636
- * @return null
1637
- */
1638
- public static function tempFileCleanup($all = false)
1639
- {
1640
- //Delete all files now
1641
- if ($all) {
1642
- $dir = DUP_Settings::getSsdirTmpPath()."/*";
1643
- foreach (glob($dir) as $file) {
1644
- @unlink($file);
1645
- }
1646
- }
1647
- //Remove scan files that are 24 hours old
1648
- else {
1649
- $dir = DUP_Settings::getSsdirTmpPath()."/*_scan.json";
1650
- foreach (glob($dir) as $file) {
1651
- if (filemtime($file) <= time() - 86400) {
1652
- @unlink($file);
1653
- }
1654
- }
1655
- }
1656
- }
1657
-
1658
- /**
1659
- * Provides various date formats
1660
- *
1661
- * @param $utcDate created date in the GMT timezone
1662
- * @param $format Various date formats to apply
1663
- *
1664
- * @return string // a formated date based on the $format
1665
- */
1666
- public static function getCreatedDateFormat($utcDate, $format = 1)
1667
- {
1668
- $date = get_date_from_gmt($utcDate);
1669
- $date = new DateTime($date);
1670
- switch ($format) {
1671
- //YEAR
1672
- case 1: return $date->format('Y-m-d H:i');
1673
- break;
1674
- case 2: return $date->format('Y-m-d H:i:s');
1675
- break;
1676
- case 3: return $date->format('y-m-d H:i');
1677
- break;
1678
- case 4: return $date->format('y-m-d H:i:s');
1679
- break;
1680
- //MONTH
1681
- case 5: return $date->format('m-d-Y H:i');
1682
- break;
1683
- case 6: return $date->format('m-d-Y H:i:s');
1684
- break;
1685
- case 7: return $date->format('m-d-y H:i');
1686
- break;
1687
- case 8: return $date->format('m-d-y H:i:s');
1688
- break;
1689
- //DAY
1690
- case 9: return $date->format('d-m-Y H:i');
1691
- break;
1692
- case 10: return $date->format('d-m-Y H:i:s');
1693
- break;
1694
- case 11: return $date->format('d-m-y H:i');
1695
- break;
1696
- case 12: return $date->format('d-m-y H:i:s');
1697
- break;
1698
- default :
1699
- return $date->format('Y-m-d H:i');
1700
- }
1701
- }
1702
-
1703
- /**
1704
- * Cleans up all the tmp files as part of the package build process
1705
- */
1706
- public function buildCleanup()
1707
- {
1708
- $files = DUP_Util::listFiles(DUP_Settings::getSsdirTmpPath());
1709
- $newPath = DUP_Settings::getSsdirPath();
1710
-
1711
- $filesToStore = array(
1712
- $this->Installer->File,
1713
- $this->Archive->File,
1714
- );
1715
-
1716
- foreach ($files as $file) {
1717
-
1718
- $fileName = basename($file);
1719
- if (!strstr($fileName, $this->NameHash)) {
1720
- continue;
1721
- }
1722
-
1723
- if (in_array($fileName, $filesToStore)) {
1724
- if (function_exists('rename')) {
1725
- rename($file, "{$newPath}/{$fileName}");
1726
- } elseif (function_exists('copy')) {
1727
- copy($file, "{$newPath}/{$fileName}");
1728
- } else {
1729
- throw new Exception('PHP copy and rename functions not found! Contact hosting provider!');
1730
- }
1731
- }
1732
-
1733
- if (file_exists($file)) {
1734
- unlink($file);
1735
- }
1736
- }
1737
-
1738
- }
1739
-
1740
-
1741
-
1742
- /**
1743
- * Get package hash
1744
- *
1745
- * @return string package hash
1746
- */
1747
- public function getPackageHash() {
1748
- $hashParts = explode('_', $this->Hash);
1749
- $firstPart = substr($hashParts[0], 0, 7);
1750
- $secondPart = substr($hashParts[1], -8);
1751
- $package_hash = $firstPart.'-'.$secondPart;
1752
- return $package_hash;
1753
- }
1754
-
1755
- public function getSecondaryPackageHash() {
1756
- $newHash = $this->makeHash();
1757
- $hashParts = explode('_', $newHash);
1758
- $firstPart = substr($hashParts[0], 0, 7);
1759
-
1760
- $hashParts = explode('_', $this->Hash);
1761
- $secondPart = substr($hashParts[1], -8);
1762
-
1763
- $package_hash = $firstPart.'-'.$secondPart;
1764
- return $package_hash;
1765
- }
1766
-
1767
- /**
1768
- * Provides the full sql file path in archive
1769
- *
1770
- * @return the full sql file path in archive
1771
- */
1772
- public function getSqlArkFilePath()
1773
- {
1774
- $package_hash = $this->getPackageHash();
1775
- $sql_ark_file_Path = 'dup-installer/dup-database__'.$package_hash.'.sql';
1776
- return $sql_ark_file_Path;
1777
- }
1778
-
1779
- private function writeLogHeader()
1780
- {
1781
- $php_max_time = @ini_get("max_execution_time");
1782
- if (DupLiteSnapLibUtil::wp_is_ini_value_changeable('memory_limit'))
1783
- $php_max_memory = @ini_set('memory_limit', DUPLICATOR_PHP_MAX_MEMORY);
1784
- else
1785
- $php_max_memory = @ini_get('memory_limit');
1786
-
1787
- $php_max_time = ($php_max_time == 0) ? "(0) no time limit imposed" : "[{$php_max_time}] not allowed";
1788
- $php_max_memory = ($php_max_memory === false) ? "Unabled to set php memory_limit" : DUPLICATOR_PHP_MAX_MEMORY." ({$php_max_memory} default)";
1789
-
1790
- $info = "********************************************************************************\n";
1791
- $info .= "DUPLICATOR-LITE PACKAGE-LOG: ".@date(get_option('date_format')." ".get_option('time_format'))."\n";
1792
- $info .= "NOTICE: Do NOT post to public sites or forums \n";
1793
- $info .= "********************************************************************************\n";
1794
- $info .= "VERSION:\t".DUPLICATOR_VERSION."\n";
1795
- $info .= "WORDPRESS:\t{$GLOBALS['wp_version']}\n";
1796
- $info .= "PHP INFO:\t".phpversion().' | '.'SAPI: '.php_sapi_name()."\n";
1797
- $info .= "SERVER:\t\t{$_SERVER['SERVER_SOFTWARE']} \n";
1798
- $info .= "PHP TIME LIMIT: {$php_max_time} \n";
1799
- $info .= "PHP MAX MEMORY: {$php_max_memory} \n";
1800
- $info .= "MEMORY STACK: ".DUP_Server::getPHPMemory();
1801
- DUP_Log::Info($info);
1802
-
1803
- $info = null;
1804
- }
1805
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ use Duplicator\Libs\Snap\SnapJson;
4
+ use Duplicator\Libs\Snap\SnapUtil;
5
+
6
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
7
+ // Exit if accessed directly
8
+ if (! defined('DUPLICATOR_VERSION')) {
9
+ exit;
10
+ }
11
+
12
+ require_once(DUPLICATOR_PLUGIN_PATH . 'classes/utilities/class.u.php');
13
+ require_once(DUPLICATOR_PLUGIN_PATH . 'classes/package/class.pack.archive.php');
14
+ require_once(DUPLICATOR_PLUGIN_PATH . 'classes/package/class.pack.installer.php');
15
+ require_once(DUPLICATOR_PLUGIN_PATH . 'classes/package/class.pack.database.php');
16
+
17
+ /**
18
+ * Class used to keep track of the build progress
19
+ *
20
+ * @package Duplicator\classes
21
+ */
22
+ class DUP_Build_Progress
23
+ {
24
+ public $thread_start_time;
25
+ public $initialized = false;
26
+ public $installer_built = false;
27
+ public $archive_started = false;
28
+ public $archive_has_database = false;
29
+ public $archive_built = false;
30
+ public $database_script_built = false;
31
+ public $failed = false;
32
+ public $retries = 0;
33
+ public $build_failures = array();
34
+ public $validation_failures = array();
35
+
36
+ /**
37
+ *
38
+ * @var DUP_Package
39
+ */
40
+ private $package;
41
+
42
+ /**
43
+ *
44
+ * @param DUP_Package $package
45
+ */
46
+ public function __construct($package)
47
+ {
48
+ $this->package = $package;
49
+ }
50
+
51
+ /**
52
+ *
53
+ * @return bool
54
+ */
55
+ public function has_completed()
56
+ {
57
+ return $this->failed || ($this->installer_built && $this->archive_built && $this->database_script_built);
58
+ }
59
+
60
+ public function timed_out($max_time)
61
+ {
62
+ if ($max_time > 0) {
63
+ $time_diff = time() - $this->thread_start_time;
64
+ return ($time_diff >= $max_time);
65
+ } else {
66
+ return false;
67
+ }
68
+ }
69
+
70
+ public function start_timer()
71
+ {
72
+ $this->thread_start_time = time();
73
+ }
74
+
75
+ public function set_validation_failures($failures)
76
+ {
77
+ $this->validation_failures = array();
78
+
79
+ foreach ($failures as $failure) {
80
+ $this->validation_failures[] = $failure;
81
+ }
82
+ }
83
+
84
+ public function set_build_failures($failures)
85
+ {
86
+ $this->build_failures = array();
87
+
88
+ foreach ($failures as $failure) {
89
+ $this->build_failures[] = $failure->description;
90
+ }
91
+ }
92
+
93
+ public function set_failed($failure_message = null)
94
+ {
95
+ if ($failure_message !== null) {
96
+ $failure = new StdClass();
97
+ $failure->type = 0;
98
+ $failure->subject = '';
99
+ $failure->description = $failure_message;
100
+ $failure->isCritical = true;
101
+ $this->build_failures[] = $failure;
102
+ }
103
+
104
+ $this->failed = true;
105
+ $this->package->Status = DUP_PackageStatus::ERROR;
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Class used to emulate and ENUM to give the status of a package from 0 to 100%
111
+ *
112
+ * @package Duplicator\classes
113
+ */
114
+ final class DUP_PackageStatus
115
+ {
116
+ private function __construct()
117
+ {
118
+ }
119
+
120
+ const ERROR = -1;
121
+ const CREATED = 0;
122
+ const START = 10;
123
+ const DBSTART = 20;
124
+ const DBDONE = 30;
125
+ const ARCSTART = 40;
126
+ const ARCVALIDATION = 60;
127
+ const ARCDONE = 65;
128
+ const COMPLETE = 100;
129
+ }
130
+
131
+ /**
132
+ * Class used to emulate and ENUM to determine how the package was made.
133
+ * For lite only the MANUAL type is used.
134
+ *
135
+ * @package Duplicator\classes
136
+ */
137
+ final class DUP_PackageType
138
+ {
139
+ const MANUAL = 0;
140
+ const SCHEDULED = 1;
141
+ }
142
+
143
+ /**
144
+ * Class used to emulate and ENUM to determine the various file types used in a package
145
+ *
146
+ * @package Duplicator\classes
147
+ */
148
+ abstract class DUP_PackageFileType
149
+ {
150
+ const Installer = 0;
151
+ const Archive = 1;
152
+ const SQL = 2;
153
+ const Log = 3;
154
+ const Scan = 4;
155
+ }
156
+
157
+ /**
158
+ * Class used to store and process all Package logic
159
+ *
160
+ * Standard: PSR-2
161
+ * @link http://www.php-fig.org/psr/psr-2
162
+ *
163
+ * @package Duplicator\classes
164
+ */
165
+ class DUP_Package
166
+ {
167
+ const OPT_ACTIVE = 'duplicator_package_active';
168
+
169
+ //Properties
170
+ public $Created;
171
+ public $Version;
172
+ public $VersionWP;
173
+ public $VersionDB;
174
+ public $VersionPHP;
175
+ public $VersionOS;
176
+ public $ID;
177
+ public $Name;
178
+ public $Hash;
179
+ public $NameHash;
180
+ //Set to DUP_PackageType
181
+ public $Type;
182
+ public $Notes;
183
+ public $ScanFile;
184
+ public $TimerStart = -1;
185
+ public $Runtime;
186
+ public $ExeSize;
187
+ public $ZipSize;
188
+ public $Status;
189
+ public $WPUser;
190
+ /** @var DUP_Archive */
191
+ public $Archive;
192
+ /** @var DUP_Installer */
193
+ public $Installer;
194
+ /** @var DUP_Database */
195
+ public $Database;
196
+ /** @var DUP_Build_Progress */
197
+ public $BuildProgress;
198
+
199
+ /**
200
+ * Manages the Package Process
201
+ */
202
+ public function __construct()
203
+ {
204
+ $this->ID = null;
205
+ $this->Version = DUPLICATOR_VERSION;
206
+ $this->Type = DUP_PackageType::MANUAL;
207
+ $this->Name = self::getDefaultName();
208
+ $this->Notes = null;
209
+ $this->Database = new DUP_Database($this);
210
+ $this->Archive = new DUP_Archive($this);
211
+ $this->Installer = new DUP_Installer($this);
212
+ $this->BuildProgress = new DUP_Build_Progress($this);
213
+ $this->Status = DUP_PackageStatus::CREATED;
214
+ }
215
+
216
+ /**
217
+ * Generates a JSON scan report
218
+ *
219
+ * @return array of scan results
220
+ *
221
+ * @notes: Testing = /wp-admin/admin-ajax.php?action=duplicator_package_scan
222
+ */
223
+ public function runScanner()
224
+ {
225
+ $timerStart = DUP_Util::getMicrotime();
226
+ $report = array();
227
+ $this->ScanFile = "{$this->NameHash}_scan.json";
228
+
229
+ $report['RPT']['ScanTime'] = "0";
230
+ $report['RPT']['ScanFile'] = $this->ScanFile;
231
+
232
+ //SERVER
233
+ $srv = DUP_Server::getChecks();
234
+ $report['SRV'] = $srv['SRV'];
235
+
236
+ //FILES
237
+ $this->Archive->getScannerData();
238
+ $dirCount = count($this->Archive->Dirs);
239
+ $fileCount = count($this->Archive->Files);
240
+ $fullCount = $dirCount + $fileCount;
241
+
242
+ $report['ARC']['Size'] = DUP_Util::byteSize($this->Archive->Size) or "unknown";
243
+ $report['ARC']['DirCount'] = number_format($dirCount);
244
+ $report['ARC']['FileCount'] = number_format($fileCount);
245
+ $report['ARC']['FullCount'] = number_format($fullCount);
246
+ $report['ARC']['WarnFileCount'] = count($this->Archive->FilterInfo->Files->Warning);
247
+ $report['ARC']['WarnDirCount'] = count($this->Archive->FilterInfo->Dirs->Warning);
248
+ $report['ARC']['UnreadableDirCount'] = count($this->Archive->FilterInfo->Dirs->Unreadable);
249
+ $report['ARC']['UnreadableFileCount'] = count($this->Archive->FilterInfo->Files->Unreadable);
250
+ $report['ARC']['FilterDirsAll'] = $this->Archive->FilterDirsAll;
251
+ $report['ARC']['FilterFilesAll'] = $this->Archive->FilterFilesAll;
252
+ $report['ARC']['FilterExtsAll'] = $this->Archive->FilterExtsAll;
253
+ $report['ARC']['FilterInfo'] = $this->Archive->FilterInfo;
254
+ $report['ARC']['RecursiveLinks'] = $this->Archive->RecursiveLinks;
255
+ $report['ARC']['UnreadableItems'] = array_merge($this->Archive->FilterInfo->Files->Unreadable, $this->Archive->FilterInfo->Dirs->Unreadable);
256
+ $report['ARC']['Status']['Size'] = ($this->Archive->Size > DUPLICATOR_SCAN_SIZE_DEFAULT) ? 'Warn' : 'Good';
257
+ $report['ARC']['Status']['Names'] = (count($this->Archive->FilterInfo->Files->Warning) + count($this->Archive->FilterInfo->Dirs->Warning)) ? 'Warn' : 'Good';
258
+ $report['ARC']['Status']['UnreadableItems'] = !empty($this->Archive->RecursiveLinks) || !empty($report['ARC']['UnreadableItems']) ? 'Warn' : 'Good';
259
+ /*
260
+ $overwriteInstallerParams = apply_filters('duplicator_overwrite_params_data', array());
261
+ $package_can_be_migrate = !(isset($overwriteInstallerParams['mode_chunking']['value'])
262
+ && $overwriteInstallerParams['mode_chunking']['value'] == 3
263
+ && isset($overwriteInstallerParams['mode_chunking']['formStatus'])
264
+ && $overwriteInstallerParams['mode_chunking']['formStatus'] == 'st_infoonly');
265
+ */
266
+ $package_can_be_migrate = true;
267
+ $report['ARC']['Status']['MigratePackage'] = $package_can_be_migrate ? 'Good' : 'Warn';
268
+ $report['ARC']['Status']['CanbeMigratePackage'] = $package_can_be_migrate;
269
+
270
+ $privileges_to_show_create_proc_func = true;
271
+ $procedures = $GLOBALS['wpdb']->get_col("SHOW PROCEDURE STATUS WHERE `Db` = '" . $GLOBALS['wpdb']->dbname . "'", 1);
272
+ if (count($procedures)) {
273
+ $create = $GLOBALS['wpdb']->get_row("SHOW CREATE PROCEDURE `" . $procedures[0] . "`", ARRAY_N);
274
+ $privileges_to_show_create_proc_func = isset($create[2]);
275
+ }
276
+
277
+ $functions = $GLOBALS['wpdb']->get_col("SHOW FUNCTION STATUS WHERE `Db` = '" . $GLOBALS['wpdb']->dbname . "'", 1);
278
+ if (count($functions)) {
279
+ $create = $GLOBALS['wpdb']->get_row("SHOW CREATE FUNCTION `" . $functions[0] . "`", ARRAY_N);
280
+ $privileges_to_show_create_proc_func = $privileges_to_show_create_proc_func && isset($create[2]);
281
+ }
282
+
283
+ $privileges_to_show_create_proc_func = apply_filters('duplicator_privileges_to_show_create_proc_func', $privileges_to_show_create_proc_func);
284
+ $report['ARC']['Status']['showCreateProcFuncStatus'] = $privileges_to_show_create_proc_func ? 'Good' : 'Warn';
285
+ $report['ARC']['Status']['showCreateProcFunc'] = $privileges_to_show_create_proc_func;
286
+
287
+ //$report['ARC']['Status']['Big'] = count($this->Archive->FilterInfo->Files->Size) ? 'Warn' : 'Good';
288
+ $report['ARC']['Dirs'] = $this->Archive->Dirs;
289
+ $report['ARC']['Files'] = $this->Archive->Files;
290
+ $report['ARC']['Status']['AddonSites'] = count($this->Archive->FilterInfo->Dirs->AddonSites) ? 'Warn' : 'Good';
291
+
292
+ //DATABASE
293
+ $db = $this->Database->getScannerData();
294
+ $report['DB'] = $db;
295
+
296
+ //Lite Limits
297
+ $rawTotalSize = $this->Archive->Size + $report['DB']['RawSize'];
298
+ $report['LL']['TotalSize'] = DUP_Util::byteSize($rawTotalSize);
299
+ $report['LL']['Status']['TotalSize'] = ($rawTotalSize > DUPLICATOR_MAX_DUPARCHIVE_SIZE) ? 'Fail' : 'Good';
300
+
301
+ $warnings = array(
302
+ $report['SRV']['SYS']['ALL'],
303
+ $report['SRV']['WP']['ALL'],
304
+ $report['ARC']['Status']['Size'],
305
+ $report['ARC']['Status']['Names'],
306
+ $db['Status']['DB_Size'],
307
+ $db['Status']['DB_Rows']
308
+ );
309
+
310
+ //array_count_values will throw a warning message if it has null values,
311
+ //so lets replace all nulls with empty string
312
+ foreach ($warnings as $i => $value) {
313
+ if (is_null($value)) {
314
+ $warnings[$i] = '';
315
+ }
316
+ }
317
+
318
+ $warn_counts = is_array($warnings) ? array_count_values($warnings) : 0;
319
+ $report['RPT']['Warnings'] = is_null($warn_counts['Warn']) ? 0 : $warn_counts['Warn'];
320
+ $report['RPT']['Success'] = is_null($warn_counts['Good']) ? 0 : $warn_counts['Good'];
321
+ $report['RPT']['ScanTime'] = DUP_Util::elapsedTime(DUP_Util::getMicrotime(), $timerStart);
322
+ $fp = fopen(DUP_Settings::getSsdirTmpPath() . "/{$this->ScanFile}", 'w');
323
+
324
+ fwrite($fp, SnapJson::jsonEncodePPrint($report));
325
+ fclose($fp);
326
+
327
+ return $report;
328
+ }
329
+
330
+ /**
331
+ * Validates the inputs from the UI for correct data input
332
+ *
333
+ * @return DUP_Validator
334
+ */
335
+ public function validateInputs()
336
+ {
337
+ $validator = new DUP_Validator();
338
+
339
+ $validator->filter_custom(
340
+ $this->Name,
341
+ DUP_Validator::FILTER_VALIDATE_NOT_EMPTY,
342
+ array( 'valkey' => 'Name' ,
343
+ 'errmsg' => __('Package name can\'t be empty', 'duplicator'),
344
+ )
345
+ );
346
+
347
+ $validator->explode_filter_custom(
348
+ $this->Archive->FilterDirs,
349
+ ';',
350
+ DUP_Validator::FILTER_VALIDATE_FOLDER,
351
+ array( 'valkey' => 'FilterDirs' ,
352
+ 'errmsg' => __('Directories: <b>%1$s</b> isn\'t a valid path', 'duplicator'),
353
+ )
354
+ );
355
+
356
+ $validator->explode_filter_custom(
357
+ $this->Archive->FilterExts,
358
+ ';',
359
+ DUP_Validator::FILTER_VALIDATE_FILE_EXT,
360
+ array( 'valkey' => 'FilterExts' ,
361
+ 'errmsg' => __('File extension: <b>%1$s</b> isn\'t a valid extension', 'duplicator'),
362
+ )
363
+ );
364
+
365
+ $validator->explode_filter_custom(
366
+ $this->Archive->FilterFiles,
367
+ ';',
368
+ DUP_Validator::FILTER_VALIDATE_FILE,
369
+ array( 'valkey' => 'FilterFiles' ,
370
+ 'errmsg' => __('Files: <b>%1$s</b> isn\'t a valid file name', 'duplicator'),
371
+ )
372
+ );
373
+
374
+ //FILTER_VALIDATE_DOMAIN throws notice message on PHP 5.6
375
+ if (defined('FILTER_VALIDATE_DOMAIN')) {
376
+ $validator->filter_var($this->Installer->OptsDBHost, FILTER_VALIDATE_DOMAIN, array(
377
+ 'valkey' => 'OptsDBHost' ,
378
+ 'errmsg' => __('MySQL Server Host: <b>%1$s</b> isn\'t a valid host', 'duplicator'),
379
+ 'acc_vals' => array(
380
+ '' ,
381
+ 'localhost'
382
+ )
383
+ ));
384
+ }
385
+
386
+ $validator->filter_var($this->Installer->OptsDBPort, FILTER_VALIDATE_INT, array(
387
+ 'valkey' => 'OptsDBPort' ,
388
+ 'errmsg' => __('MySQL Server Port: <b>%1$s</b> isn\'t a valid port', 'duplicator'),
389
+ 'acc_vals' => array(
390
+ ''
391
+ ),
392
+ 'options' => array(
393
+ 'min_range' => 0
394
+ )
395
+ ));
396
+
397
+ return $validator;
398
+ }
399
+
400
+ /**
401
+ *
402
+ * @return string
403
+ */
404
+ public function getInstDownloadName($onlySecureName = false)
405
+ {
406
+ $mode = ($onlySecureName)
407
+ ? DUP_Settings::INSTALLER_NAME_MODE_WITH_HASH
408
+ : DUP_Settings::Get('installer_name_mode');
409
+
410
+ switch ($mode) {
411
+ case DUP_Settings::INSTALLER_NAME_MODE_SIMPLE:
412
+ $name = DUP_Installer::DEFAULT_INSTALLER_FILE_NAME_WITHOUT_HASH . DUP_Installer::INSTALLER_SERVER_EXTENSION;
413
+ break;
414
+ case DUP_Settings::INSTALLER_NAME_MODE_WITH_HASH:
415
+ default:
416
+ $name = basename($this->getLocalPackageFile(DUP_PackageFileType::Installer));
417
+ break;
418
+ }
419
+ $info = pathinfo($name);
420
+ return $info['filename'];
421
+ }
422
+
423
+ /**
424
+ *
425
+ * @return bool return true if package is a active_package_id and status is bewteen 0 and 100
426
+ */
427
+ public function isRunning()
428
+ {
429
+ return DUP_Settings::Get('active_package_id') == $this->ID && $this->Status >= 0 && $this->Status < 100;
430
+ }
431
+
432
+ protected function cleanObjectBeforeSave()
433
+ {
434
+ $this->Archive->FilterInfo->reset();
435
+ }
436
+
437
+ /**
438
+ * Saves the active package to the package table
439
+ *
440
+ * @return void
441
+ */
442
+ public function save($extension)
443
+ {
444
+ global $wpdb;
445
+
446
+ $this->Archive->Format = strtoupper($extension);
447
+ $this->Archive->File = "{$this->NameHash}_archive.{$extension}";
448
+ $this->Installer->File = apply_filters(
449
+ 'duplicator_installer_file_path',
450
+ "{$this->NameHash}_installer" . DUP_Installer::INSTALLER_SERVER_EXTENSION
451
+ );
452
+ $this->Database->File = "{$this->NameHash}_database.sql";
453
+ $current_user = wp_get_current_user();
454
+ $this->WPUser = isset($current_user->user_login) ? $current_user->user_login : 'unknown';
455
+
456
+ //START LOGGING
457
+ DUP_Log::Open($this->NameHash);
458
+
459
+ do_action('duplicator_lite_build_before_start', $this);
460
+
461
+ $this->writeLogHeader();
462
+
463
+ //CREATE DB RECORD
464
+ $this->cleanObjectBeforeSave();
465
+ $packageObj = serialize($this);
466
+ if (!$packageObj) {
467
+ DUP_Log::error("Unable to serialize package object while building record.");
468
+ }
469
+
470
+ $this->ID = $this->getHashKey($this->Hash);
471
+
472
+ if ($this->ID != 0) {
473
+ DUP_LOG::Trace("ID non zero so setting to start");
474
+ $this->setStatus(DUP_PackageStatus::START);
475
+ } else {
476
+ DUP_LOG::Trace("ID IS zero so creating another package");
477
+ $tablePrefix = DUP_Util::getTablePrefix();
478
+ $results = $wpdb->insert($tablePrefix . "duplicator_packages", array(
479
+ 'name' => $this->Name,
480
+ 'hash' => $this->Hash,
481
+ 'status' => DUP_PackageStatus::START,
482
+ 'created' => current_time('mysql', get_option('gmt_offset', 1)),
483
+ 'owner' => isset($current_user->user_login) ? $current_user->user_login : 'unknown',
484
+ 'package' => $packageObj));
485
+ if ($results === false) {
486
+ $wpdb->print_error();
487
+ DUP_LOG::Trace("Problem inserting package: {$wpdb->last_error}");
488
+
489
+ DUP_Log::error("Duplicator is unable to insert a package record into the database table.", "'{$wpdb->last_error}'");
490
+ }
491
+ $this->ID = $wpdb->insert_id;
492
+ }
493
+
494
+ do_action('duplicator_lite_build_start', $this);
495
+ }
496
+
497
+ /**
498
+ * Delete all files associated with this package ID
499
+ *
500
+ * @return void
501
+ */
502
+ public function delete()
503
+ {
504
+ global $wpdb;
505
+
506
+ $tablePrefix = DUP_Util::getTablePrefix();
507
+ $tblName = $tablePrefix . 'duplicator_packages';
508
+ $getResult = $wpdb->get_results($wpdb->prepare("SELECT name, hash FROM `{$tblName}` WHERE id = %d", $this->ID), ARRAY_A);
509
+
510
+ if ($getResult) {
511
+ $row = $getResult[0];
512
+ $nameHash = "{$row['name']}_{$row['hash']}";
513
+ $delResult = $wpdb->query($wpdb->prepare("DELETE FROM `{$tblName}` WHERE id = %d", $this->ID));
514
+
515
+ if ($delResult != 0) {
516
+ $tmpPath = DUP_Settings::getSsdirTmpPath();
517
+ $ssdPath = DUP_Settings::getSsdirPath();
518
+
519
+ $archiveFile = $this->getArchiveFilename();
520
+ $wpConfigFile = "{$this->NameHash}_wp-config.txt";
521
+
522
+ //Perms
523
+ @chmod($tmpPath . "/{$archiveFile}", 0644);
524
+ @chmod($tmpPath . "/{$nameHash}_database.sql", 0644);
525
+ @chmod($tmpPath . "/{$nameHash}_installer" . DUP_Installer::INSTALLER_SERVER_EXTENSION, 0644);
526
+ @chmod($tmpPath . "/{$nameHash}_scan.json", 0644);
527
+ @chmod($tmpPath . "/{$wpConfigFile}", 0644);
528
+ @chmod($tmpPath . "/{$nameHash}.log", 0644);
529
+
530
+ @chmod($ssdPath . "/{$archiveFile}", 0644);
531
+ @chmod($ssdPath . "/{$nameHash}_database.sql", 0644);
532
+ @chmod($ssdPath . "/{$nameHash}_installer" . DUP_Installer::INSTALLER_SERVER_EXTENSION, 0644);
533
+ @chmod($ssdPath . "/{$nameHash}_scan.json", 0644);
534
+ // In older version, The plugin was storing [HASH]_wp-config.txt in main storage area. The below line code is for backward compatibility
535
+ @chmod($ssdPath . "/{$wpConfigFile}", 0644);
536
+ @chmod($ssdPath . "/{$nameHash}.log", 0644);
537
+ //Remove
538
+ @unlink($tmpPath . "/{$archiveFile}");
539
+ @unlink($tmpPath . "/{$nameHash}_database.sql");
540
+ @unlink($tmpPath . "/{$nameHash}_installer" . DUP_Installer::INSTALLER_SERVER_EXTENSION);
541
+ @unlink($tmpPath . "/{$nameHash}_scan.json");
542
+ @unlink($tmpPath . "/{$wpConfigFile}");
543
+ @unlink($tmpPath . "/{$nameHash}.log");
544
+
545
+ @unlink($ssdPath . "/{$archiveFile}");
546
+ @unlink($ssdPath . "/{$nameHash}_database.sql");
547
+ @unlink($ssdPath . "/{$nameHash}_installer" . DUP_Installer::INSTALLER_SERVER_EXTENSION);
548
+ @unlink($ssdPath . "/{$nameHash}_scan.json");
549
+ // In older version, The plugin was storing [HASH]_wp-config.txt in main storage area. The below line code is for backward compatibility
550
+ @unlink($ssdPath . "/{$wpConfigFile}");
551
+ @unlink($ssdPath . "/{$nameHash}.log");
552
+ }
553
+ }
554
+ }
555
+
556
+ /**
557
+ * Get package archive size.
558
+ * If package isn't complete it get size from sum of temp files.
559
+ *
560
+ * @return int size in byte
561
+ */
562
+ public function getArchiveSize()
563
+ {
564
+ $size = 0;
565
+
566
+ if ($this->Status >= DUP_PackageStatus::COMPLETE) {
567
+ $size = $this->Archive->Size;
568
+ } else {
569
+ $tmpSearch = glob(DUP_Settings::getSsdirTmpPath() . "/{$this->NameHash}_*");
570
+ if (is_array($tmpSearch)) {
571
+ $result = array_map('filesize', $tmpSearch);
572
+ $size = array_sum($result);
573
+ }
574
+ }
575
+
576
+ return $size;
577
+ }
578
+
579
+ /**
580
+ * Return true if active package exist and have an active status
581
+ *
582
+ * @return bool
583
+ */
584
+ public static function is_active_package_present()
585
+ {
586
+ $activePakcs = self::get_ids_by_status(array(
587
+ array('op' => '>=', 'status' => DUP_PackageStatus::CREATED),
588
+ array('op' => '<', 'status' => DUP_PackageStatus::COMPLETE)
589
+ ), true);
590
+
591
+ return in_array(DUP_Settings::Get('active_package_id'), $activePakcs);
592
+ }
593
+
594
+ /**
595
+ *
596
+ * @param array $conditions es. [
597
+ * relation = 'AND',
598
+ * [ 'op' => '>=' ,
599
+ * 'status' => DUP_PackageStatus::START ]
600
+ * [ 'op' => '<' ,
601
+ * 'status' => DUP_PackageStatus::COMPLETED ]
602
+ * ]
603
+ * @return string
604
+ */
605
+ protected static function statusContitionsToWhere($conditions = array())
606
+ {
607
+ if (empty($conditions)) {
608
+ return '';
609
+ } else {
610
+ $accepted_op = array('<', '>', '=', '<>', '>=', '<=');
611
+ $relation = (isset($conditions['relation']) && strtoupper($conditions['relation']) == 'OR') ? ' OR ' : ' AND ';
612
+ unset($conditions['relation']);
613
+
614
+ $str_conds = array();
615
+
616
+ foreach ($conditions as $cond) {
617
+ $op = (isset($cond['op']) && in_array($cond['op'], $accepted_op)) ? $cond['op'] : '=';
618
+ $status = isset($cond['status']) ? (int) $cond['status'] : 0;
619
+ $str_conds[] = 'status ' . $op . ' ' . $status;
620
+ }
621
+
622
+ return ' WHERE ' . implode($relation, $str_conds) . ' ';
623
+ }
624
+ }
625
+
626
+ /**
627
+ * Get packages with status conditions and/or pagination
628
+ *
629
+ * @global wpdb $wpdb
630
+ *
631
+ * @param array // $conditions es. [
632
+ * relation = 'AND',
633
+ * [ 'op' => '>=' ,
634
+ * 'status' => DUP_PackageStatus::START ]
635
+ * [ 'op' => '<' ,
636
+ * 'status' => DUP_PackageStatus::COMPLETED ]
637
+ * ]
638
+ * if empty get all pacages
639
+ * @param int $limit // max row numbers fi false the limit is PHP_INT_MAX
640
+ * @param int $offset // offset 0 is at begin
641
+ * @param string $orderBy // default `id` ASC if empty no order
642
+ * @param string $resultType // ids => int[]
643
+ * row => row without backage blob
644
+ * fullRow => row with package blob
645
+ * objs => array of DUP_Package objects
646
+ *
647
+ * @return DUP_Package[]|object[]|int[]
648
+ */
649
+ public static function get_packages_by_status($conditions = array(), $limit = false, $offset = 0, $orderBy = '`id` ASC', $resultType = 'obj')
650
+ {
651
+ global $wpdb;
652
+ $table = $wpdb->base_prefix . "duplicator_packages";
653
+ $where = self::statusContitionsToWhere($conditions);
654
+
655
+ $packages = array();
656
+ $offsetStr = ' OFFSET ' . (int) $offset;
657
+ $limitStr = ' LIMIT ' . ($limit !== false ? max(0, $limit) : PHP_INT_MAX);
658
+ $orderByStr = empty($orderBy) ? '' : ' ORDER BY ' . $orderBy . ' ';
659
+ switch ($resultType) {
660
+ case 'ids':
661
+ $cols = '`id`';
662
+ break;
663
+ case 'row':
664
+ $cols = '`id`,`name`,`hash`,`status`,`created`,`owner`';
665
+ break;
666
+ case 'fullRow':
667
+ $cols = '*';
668
+ break;
669
+ case 'objs':
670
+ default:
671
+ $cols = '`status`,`package`';
672
+ break;
673
+ }
674
+
675
+ $rows = $wpdb->get_results('SELECT ' . $cols . ' FROM `' . $table . '` ' . $where . $orderByStr . $limitStr . $offsetStr);
676
+ if ($rows != null) {
677
+ switch ($resultType) {
678
+ case 'ids':
679
+ foreach ($rows as $row) {
680
+ $packages[] = $row->id;
681
+ }
682
+ break;
683
+ case 'row':
684
+ case 'fullRow':
685
+ $packages = $rows;
686
+ break;
687
+ case 'objs':
688
+ default:
689
+ foreach ($rows as $row) {
690
+ $Package = unserialize($row->package);
691
+ if ($Package) {
692
+ // We was not storing Status in Lite 1.2.52, so it is for backward compatibility
693
+ if (!isset($Package->Status)) {
694
+ $Package->Status = $row->status;
695
+ }
696
+
697
+ $packages[] = $Package;
698
+ }
699
+ }
700
+ }
701
+ }
702
+ return $packages;
703
+ }
704
+
705
+ /**
706
+ * Get packages row db with status conditions and/or pagination
707
+ *
708
+ * @param array // $conditions es. [
709
+ * relation = 'AND',
710
+ * [ 'op' => '>=' ,
711
+ * 'status' => DUP_PackageStatus::START ]
712
+ * [ 'op' => '<' ,
713
+ * 'status' => DUP_PackageStatus::COMPLETED ]
714
+ * ]
715
+ * if empty get all pacages
716
+ * @param int $limit // max row numbers
717
+ * @param int $offset // offset 0 is at begin
718
+ * @param string $orderBy // default `id` ASC if empty no order
719
+ *
720
+ * @return object[] // return row database without package blob
721
+ */
722
+ public static function get_row_by_status($conditions = array(), $limit = false, $offset = 0, $orderBy = '`id` ASC')
723
+ {
724
+ return self::get_packages_by_status($conditions, $limit, $offset, $orderBy, 'row');
725
+ }
726
+
727
+ /**
728
+ * Get packages ids with status conditions and/or pagination
729
+ *
730
+ * @param array // $conditions es. [
731
+ * relation = 'AND',
732
+ * [ 'op' => '>=' ,
733
+ * 'status' => DUP_PackageStatus::START ]
734
+ * [ 'op' => '<' ,
735
+ * 'status' => DUP_PackageStatus::COMPLETED ]
736
+ * ]
737
+ * if empty get all pacages
738
+ * @param int $limit // max row numbers
739
+ * @param int $offset // offset 0 is at begin
740
+ * @param string $orderBy // default `id` ASC if empty no order
741
+ *
742
+ * @return array[] // return row database without package blob
743
+ */
744
+ public static function get_ids_by_status($conditions = array(), $limit = false, $offset = 0, $orderBy = '`id` ASC')
745
+ {
746
+ return self::get_packages_by_status($conditions, $limit, $offset, $orderBy, 'ids');
747
+ }
748
+
749
+ /**
750
+ * count package with status condition
751
+ *
752
+ * @global wpdb $wpdb
753
+ * @param array $conditions es. [
754
+ * relation = 'AND',
755
+ * [ 'op' => '>=' ,
756
+ * 'status' => DUP_PackageStatus::START ]
757
+ * [ 'op' => '<' ,
758
+ * 'status' => DUP_PackageStatus::COMPLETED ]
759
+ * ]
760
+ * @return int
761
+ */
762
+ public static function count_by_status($conditions = array())
763
+ {
764
+ global $wpdb;
765
+
766
+ $table = $wpdb->base_prefix . "duplicator_packages";
767
+ $where = self::statusContitionsToWhere($conditions);
768
+
769
+ $count = $wpdb->get_var("SELECT count(id) FROM `{$table}` " . $where);
770
+ return $count;
771
+ }
772
+
773
+ /**
774
+ * Execute $callback function foreach package result
775
+ * For each iteration the memory is released
776
+ *
777
+ * @param callable $callback // function callback(DUP_Package $package)
778
+ * @param array // $conditions es. [
779
+ * relation = 'AND',
780
+ * [ 'op' => '>=' ,
781
+ * 'status' => DUP_PackageStatus::START ]
782
+ * [ 'op' => '<' ,
783
+ * 'status' => DUP_PackageStatus::COMPLETED ]
784
+ * ]
785
+ * if empty get all pacages
786
+ * @param int $limit // max row numbers
787
+ * @param int $offset // offset 0 is at begin
788
+ * @param string $orderBy // default `id` ASC if empty no order
789
+ *
790
+ * @return void
791
+ */
792
+ public static function by_status_callback($callback, $conditions = array(), $limit = false, $offset = 0, $orderBy = '`id` ASC')
793
+ {
794
+ if (!is_callable($callback)) {
795
+ throw new Exception('No callback function passed');
796
+ }
797
+
798
+ $offset = max(0, $offset);
799
+ $numPackages = self::count_by_status($conditions);
800
+ $maxLimit = $offset + ($limit !== false ? max(0, $limit) : PHP_INT_MAX - $offset);
801
+ $numPackages = min($maxLimit, $numPackages);
802
+ $orderByStr = empty($orderBy) ? '' : ' ORDER BY ' . $orderBy . ' ';
803
+
804
+ global $wpdb;
805
+ $table = $wpdb->base_prefix . "duplicator_packages";
806
+ $where = self::statusContitionsToWhere($conditions);
807
+ $sql = 'SELECT * FROM `' . $table . '` ' . $where . $orderByStr . ' LIMIT 1 OFFSET ';
808
+
809
+ for (; $offset < $numPackages; $offset++) {
810
+ $rows = $wpdb->get_results($sql . $offset);
811
+ if ($rows != null) {
812
+ $Package = @unserialize($rows[0]->package);
813
+ if ($Package) {
814
+ if (empty($Package->ID)) {
815
+ $Package->ID = $rows[0]->id;
816
+ }
817
+ // We was not storing Status in Lite 1.2.52, so it is for backward compatibility
818
+ if (!isset($Package->Status)) {
819
+ $Package->Status = $rows[0]->status;
820
+ }
821
+ call_user_func($callback, $Package);
822
+ unset($Package);
823
+ }
824
+ unset($rows);
825
+ }
826
+ }
827
+ }
828
+
829
+ public static function purge_incomplete_package()
830
+ {
831
+ $packages = self::get_packages_by_status(array(
832
+ 'relation' => 'AND',
833
+ array('op' => '>=', 'status' => DUP_PackageStatus::CREATED),
834
+ array('op' => '<', 'status' => DUP_PackageStatus::COMPLETE)
835
+ ), 1, 0, '`id` ASC');
836
+
837
+
838
+ if (count($packages) > 0) {
839
+ foreach ($packages as $package) {
840
+ if (!$package->isRunning()) {
841
+ $package->delete();
842
+ }
843
+ }
844
+ }
845
+ }
846
+
847
+ /**
848
+ * Check the DupArchive build to make sure it is good
849
+ *
850
+ * @return void
851
+ */
852
+ public function runDupArchiveBuildIntegrityCheck()
853
+ {
854
+ //INTEGRITY CHECKS
855
+ //We should not rely on data set in the serlized object, we need to manually check each value
856
+ //indepentantly to have a true integrity check.
857
+ DUP_Log::info("\n********************************************************************************");
858
+ DUP_Log::info("INTEGRITY CHECKS:");
859
+ DUP_Log::info("********************************************************************************");
860
+
861
+ //------------------------
862
+ //SQL CHECK: File should be at minimum 5K. A base WP install with only Create tables is about 9K
863
+ $sql_temp_path = DUP_Settings::getSsdirTmpPath() . '/' . $this->Database->File;
864
+ $sql_temp_size = @filesize($sql_temp_path);
865
+ $sql_easy_size = DUP_Util::byteSize($sql_temp_size);
866
+ $sql_done_txt = DUP_Util::tailFile($sql_temp_path, 3);
867
+ DUP_Log::Trace('[DUP ARCHIVE] ' . __FUNCTION__ . ' ' . __LINE__);
868
+
869
+ // Note: Had to add extra size check of 800 since observed bad sql when filter was on
870
+ if (
871
+ !strstr($sql_done_txt, 'DUPLICATOR_MYSQLDUMP_EOF') ||
872
+ (
873
+ !$this->Database->FilterOn && $sql_temp_size < 5120) ||
874
+ ($this->Database->FilterOn && $this->Database->info->tablesFinalCount > 0 &&
875
+ $sql_temp_size < 800
876
+ )
877
+ ) {
878
+ DUP_Log::Trace('[DUP ARCHIVE] ' . __FUNCTION__ . ' ' . __LINE__);
879
+
880
+ $error_text = "ERROR: SQL file not complete. The file {$sql_temp_path} looks too small ($sql_temp_size bytes) or the end of file marker was not found.";
881
+ $this->BuildProgress->set_failed($error_text);
882
+ $this->setStatus(DUP_PackageStatus::ERROR);
883
+ DUP_Log::error("$error_text", '', Dup_ErrorBehavior::LogOnly);
884
+ return;
885
+ }
886
+
887
+ DUP_Log::Trace('[DUP ARCHIVE] ' . __FUNCTION__ . ' ' . __LINE__);
888
+ DUP_Log::Info("SQL FILE: {$sql_easy_size}");
889
+
890
+ //------------------------
891
+ //INSTALLER CHECK:
892
+ $exe_temp_path = DUP_Settings::getSsdirTmpPath() . '/' . $this->Installer->File;
893
+
894
+ $exe_temp_size = @filesize($exe_temp_path);
895
+ $exe_easy_size = DUP_Util::byteSize($exe_temp_size);
896
+ $exe_done_txt = DUP_Util::tailFile($exe_temp_path, 10);
897
+
898
+ if (!strstr($exe_done_txt, 'DUPLICATOR_INSTALLER_EOF') && !$this->BuildProgress->failed) {
899
+ //$this->BuildProgress->failed = true;
900
+ $error_message = 'ERROR: Installer file not complete. The end of file marker was not found. Please try to re-create the package.';
901
+
902
+ $this->BuildProgress->set_failed($error_message);
903
+ $this->Status = DUP_PackageStatus::ERROR;
904
+ $this->update();
905
+ DUP_Log::error($error_message, '', Dup_ErrorBehavior::LogOnly);
906
+ return;
907
+ }
908
+ DUP_Log::info("INSTALLER FILE: {$exe_easy_size}");
909
+
910
+ //------------------------
911
+ //ARCHIVE CHECK:
912
+ DUP_LOG::trace("Archive file count is " . $this->Archive->file_count);
913
+
914
+ if ($this->Archive->file_count != -1) {
915
+ $zip_easy_size = DUP_Util::byteSize($this->Archive->Size);
916
+ if (!($this->Archive->Size)) {
917
+ //$this->BuildProgress->failed = true;
918
+ $error_message = "ERROR: The archive file contains no size.";
919
+
920
+ $this->BuildProgress->set_failed($error_message);
921
+ $this->setStatus(DUP_PackageStatus::ERROR);
922
+ DUP_Log::error($error_message, "Archive Size: {$zip_easy_size}", Dup_ErrorBehavior::LogOnly);
923
+ return;
924
+ }
925
+
926
+ $scan_filepath = DUP_Settings::getSsdirTmpPath() . "/{$this->NameHash}_scan.json";
927
+ $json = '';
928
+
929
+ DUP_LOG::Trace("***********Does $scan_filepath exist?");
930
+ if (file_exists($scan_filepath)) {
931
+ $json = file_get_contents($scan_filepath);
932
+ } else {
933
+ $error_message = sprintf(__("Can't find Scanfile %s. Please ensure there no non-English characters in the package or schedule name.", 'duplicator'), $scan_filepath);
934
+
935
+ //$this->BuildProgress->failed = true;
936
+ //$this->setStatus(DUP_PackageStatus::ERROR);
937
+ $this->BuildProgress->set_failed($error_message);
938
+ $this->setStatus(DUP_PackageStatus::ERROR);
939
+
940
+ DUP_Log::error($error_message, '', Dup_ErrorBehavior::LogOnly);
941
+ return;
942
+ }
943
+
944
+ $scanReport = json_decode($json);
945
+
946
+ //RSR TODO: rework/simplify the validateion of duparchive
947
+ $dirCount = count($scanReport->ARC->Dirs);
948
+ $numInstallerDirs = $this->Installer->numDirsAdded;
949
+ $fileCount = count($scanReport->ARC->Files);
950
+ $numInstallerFiles = $this->Installer->numFilesAdded;
951
+
952
+ $expected_filecount = $dirCount + $numInstallerDirs + $fileCount + $numInstallerFiles + 1 - 1; // Adding database.sql but subtracting the root dir
953
+ //Dup_Log::trace("#### a:{$dirCount} b:{$numInstallerDirs} c:{$fileCount} d:{$numInstallerFiles} = {$expected_filecount}");
954
+
955
+ DUP_Log::info("ARCHIVE FILE: {$zip_easy_size} ");
956
+ DUP_Log::info(sprintf(__('EXPECTED FILE/DIRECTORY COUNT: %1$s', 'duplicator'), number_format($expected_filecount)));
957
+ DUP_Log::info(sprintf(__('ACTUAL FILE/DIRECTORY COUNT: %1$s', 'duplicator'), number_format($this->Archive->file_count)));
958
+
959
+ $this->ExeSize = $exe_easy_size;
960
+ $this->ZipSize = $zip_easy_size;
961
+
962
+ /* ------- ZIP Filecount Check -------- */
963
+ // Any zip of over 500 files should be within 2% - this is probably too loose but it will catch gross errors
964
+ DUP_LOG::trace("Expected filecount = $expected_filecount and archive filecount=" . $this->Archive->file_count);
965
+
966
+ if ($expected_filecount > 500) {
967
+ $straight_ratio = (float) $expected_filecount / (float) $this->Archive->file_count;
968
+
969
+ $warning_count = $scanReport->ARC->WarnFileCount + $scanReport->ARC->WarnDirCount + $scanReport->ARC->UnreadableFileCount + $scanReport->ARC->UnreadableDirCount;
970
+ DUP_LOG::trace(
971
+ "Warn/unread counts) warnfile:{$scanReport->ARC->WarnFileCount} warndir:{$scanReport->ARC->WarnDirCount}" .
972
+ " unreadfile:{$scanReport->ARC->UnreadableFileCount} unreaddir:{$scanReport->ARC->UnreadableDirCount}"
973
+ );
974
+ $warning_ratio = ((float) ($expected_filecount + $warning_count)) / (float) $this->Archive->file_count;
975
+ DUP_LOG::trace(
976
+ "Straight ratio is $straight_ratio and warning ratio is $warning_ratio. # Expected=$expected_filecount " .
977
+ "# Warning=$warning_count and #Archive File {$this->Archive->file_count}"
978
+ );
979
+
980
+ // Allow the real file count to exceed the expected by 10% but only allow 1% the other way
981
+ if (($straight_ratio < 0.90) || ($straight_ratio > 1.01)) {
982
+ // Has to exceed both the straight as well as the warning ratios
983
+ if (($warning_ratio < 0.90) || ($warning_ratio > 1.01)) {
984
+ $error_message = sprintf('ERROR: File count in archive vs expected suggests a bad archive (%1$d vs %2$d).', $this->Archive->file_count, $expected_filecount);
985
+ $this->BuildProgress->set_failed($error_message);
986
+ $this->Status = DUP_PackageStatus::ERROR;
987
+ $this->update();
988
+
989
+ DUP_Log::error($error_message, '');
990
+ return;
991
+ }
992
+ }
993
+ }
994
+ }
995
+
996
+ /* ------ ZIP CONSISTENCY CHECK ------ */
997
+ if ($this->Archive->getBuildMode() == DUP_Archive_Build_Mode::ZipArchive) {
998
+ DUP_LOG::trace("Running ZipArchive consistency check");
999
+ $zipPath = DUP_Settings::getSsdirTmpPath() . "/{$this->Archive->File}";
1000
+
1001
+ $zip = new ZipArchive();
1002
+
1003
+ // ZipArchive::CHECKCONS will enforce additional consistency checks
1004
+ $res = $zip->open($zipPath, ZipArchive::CHECKCONS);
1005
+
1006
+ if ($res !== true) {
1007
+ $consistency_error = sprintf(__('ERROR: Cannot open created archive. Error code = %1$s', 'duplicator'), $res);
1008
+
1009
+ DUP_LOG::trace($consistency_error);
1010
+ switch ($res) {
1011
+ case ZipArchive::ER_NOZIP:
1012
+ $consistency_error = __('ERROR: Archive is not valid zip archive.', 'duplicator');
1013
+ break;
1014
+
1015
+ case ZipArchive::ER_INCONS:
1016
+ $consistency_error = __("ERROR: Archive doesn't pass consistency check.", 'duplicator');
1017
+ break;
1018
+
1019
+
1020
+ case ZipArchive::ER_CRC:
1021
+ $consistency_error = __("ERROR: Archive checksum is bad.", 'duplicator');
1022
+ break;
1023
+ }
1024
+
1025
+ $this->BuildProgress->set_failed($consistency_error);
1026
+ $this->Status = DUP_PackageStatus::ERROR;
1027
+ $this->update();
1028
+
1029
+ DUP_LOG::trace($consistency_error);
1030
+ DUP_Log::error($consistency_error, '');
1031
+ } else {
1032
+ DUP_Log::info(__('ARCHIVE CONSISTENCY TEST: Pass', 'duplicator'));
1033
+ DUP_LOG::trace("Zip for package $this->ID passed consistency test");
1034
+ }
1035
+
1036
+ $zip->close();
1037
+ }
1038
+ }
1039
+
1040
+ public function getLocalPackageFile($file_type)
1041
+ {
1042
+ $file_path = null;
1043
+
1044
+ if ($file_type == DUP_PackageFileType::Installer) {
1045
+ DUP_Log::Trace("Installer requested");
1046
+ $file_name = apply_filters('duplicator_installer_file_path', $this->getInstallerFilename());
1047
+ } elseif ($file_type == DUP_PackageFileType::Archive) {
1048
+ DUP_Log::Trace("Archive requested");
1049
+ $file_name = $this->getArchiveFilename();
1050
+ } elseif ($file_type == DUP_PackageFileType::SQL) {
1051
+ DUP_Log::Trace("SQL requested");
1052
+ $file_name = $this->getDatabaseFilename();
1053
+ } else {
1054
+ DUP_Log::Trace("Log requested");
1055
+ $file_name = $this->getLogFilename();
1056
+ }
1057
+
1058
+ $file_path = DUP_Settings::getSsdirPath() . "/$file_name";
1059
+ DUP_Log::Trace("File path $file_path");
1060
+
1061
+ if (file_exists($file_path)) {
1062
+ return $file_path;
1063
+ } else {
1064
+ return null;
1065
+ }
1066
+ }
1067
+
1068
+ public function getScanFilename()
1069
+ {
1070
+ return $this->NameHash . '_scan.json';
1071
+ }
1072
+
1073
+ public function getScanUrl()
1074
+ {
1075
+ return DUP_Settings::getSsdirUrl() . "/" . $this->getScanFilename();
1076
+ }
1077
+
1078
+ public function getLogFilename()
1079
+ {
1080
+ return $this->NameHash . '.log';
1081
+ }
1082
+
1083
+ public function getLogUrl()
1084
+ {
1085
+ return DUP_Settings::getSsdirUrl() . "/" . $this->getLogFilename();
1086
+ }
1087
+
1088
+ public function getArchiveFilename()
1089
+ {
1090
+ $extension = strtolower($this->Archive->Format);
1091
+
1092
+ return "{$this->NameHash}_archive.{$extension}";
1093
+ }
1094
+
1095
+ public function getInstallerFilename()
1096
+ {
1097
+ return "{$this->NameHash}_installer" . DUP_Installer::INSTALLER_SERVER_EXTENSION;
1098
+ }
1099
+
1100
+ public function getDatabaseFilename()
1101
+ {
1102
+ return $this->NameHash . '_database.sql';
1103
+ }
1104
+
1105
+ public function get_files_list_filename()
1106
+ {
1107
+ return $this->NameHash . DUP_Archive::FILES_LIST_FILE_NAME_SUFFIX;
1108
+ }
1109
+
1110
+ public function get_dirs_list_filename()
1111
+ {
1112
+ return $this->NameHash . DUP_Archive::DIRS_LIST_FILE_NAME_SUFFIX;
1113
+ }
1114
+
1115
+ /**
1116
+ * @param int $type
1117
+ * @return array
1118
+ */
1119
+ public function getPackageFileDownloadInfo($type)
1120
+ {
1121
+ $result = array(
1122
+ "filename" => "",
1123
+ "url" => ""
1124
+ );
1125
+
1126
+ switch ($type) {
1127
+ case DUP_PackageFileType::Archive:
1128
+ $result["filename"] = $this->Archive->File;
1129
+ $result["url"] = $this->Archive->getURL();
1130
+ break;
1131
+ case DUP_PackageFileType::SQL:
1132
+ $result["filename"] = $this->Database->File;
1133
+ $result["url"] = $this->Database->getURL();
1134
+ break;
1135
+ case DUP_PackageFileType::Log:
1136
+ $result["filename"] = $this->getLogFilename();
1137
+ $result["url"] = $this->getLogUrl();
1138
+ break;
1139
+ case DUP_PackageFileType::Scan:
1140
+ $result["filename"] = $this->getScanFilename();
1141
+ $result["url"] = $this->getScanUrl();
1142
+ break;
1143
+ default:
1144
+ break;
1145
+ }
1146
+
1147
+ return $result;
1148
+ }
1149
+
1150
+ public function getInstallerDownloadInfo()
1151
+ {
1152
+ return array(
1153
+ "id" => $this->ID,
1154
+ "hash" => $this->Hash
1155
+ );
1156
+ }
1157
+
1158
+ /**
1159
+ * Removes all files except those of active packages
1160
+ */
1161
+ public static function not_active_files_tmp_cleanup()
1162
+ {
1163
+ //Check for the 'tmp' folder just for safe measures
1164
+ if (! is_dir(DUP_Settings::getSsdirTmpPath()) && (strpos(DUP_Settings::getSsdirTmpPath(), 'tmp') !== false)) {
1165
+ return;
1166
+ }
1167
+
1168
+ $globs = glob(DUP_Settings::getSsdirTmpPath() . '/*.*');
1169
+ if (! is_array($globs) || $globs === false) {
1170
+ return;
1171
+ }
1172
+
1173
+ // RUNNING PACKAGES
1174
+ $active_pack = self::get_row_by_status(array(
1175
+ 'relation' => 'AND',
1176
+ array('op' => '>=' , 'status' => DUP_PackageStatus::CREATED ),
1177
+ array('op' => '<' , 'status' => DUP_PackageStatus::COMPLETE )
1178
+ ));
1179
+ $active_files = array();
1180
+ foreach ($active_pack as $row) {
1181
+ $active_files[] = $row->name . '_' . $row->hash;
1182
+ }
1183
+
1184
+ // ERRORS PACKAGES
1185
+ $err_pack = self::get_row_by_status(array(
1186
+ array('op' => '<' , 'status' => DUP_PackageStatus::CREATED )
1187
+ ));
1188
+ $force_del_files = array();
1189
+ foreach ($err_pack as $row) {
1190
+ $force_del_files[] = $row->name . '_' . $row->hash;
1191
+ }
1192
+
1193
+ // Don't remove json file;
1194
+ $extension_filter = array('json', 'txt');
1195
+
1196
+ // Calculate delta time for old files
1197
+ $oldTimeToClean = time() - DUPLICATOR_TEMP_CLEANUP_SECONDS;
1198
+
1199
+ foreach ($globs as $glob_full_path) {
1200
+ // Don't remove sub dir
1201
+ if (is_dir($glob_full_path)) {
1202
+ continue;
1203
+ }
1204
+
1205
+ $file_name = basename($glob_full_path);
1206
+ // skip all active packages
1207
+ foreach ($active_files as $c_nameHash) {
1208
+ if (strpos($file_name, $c_nameHash) === 0) {
1209
+ continue 2;
1210
+ }
1211
+ }
1212
+
1213
+ // Remove all old files
1214
+ if (filemtime($glob_full_path) <= $oldTimeToClean) {
1215
+ @unlink($glob_full_path);
1216
+ continue;
1217
+ }
1218
+
1219
+ // remove all error packages files
1220
+ foreach ($force_del_files as $c_nameHash) {
1221
+ if (strpos($file_name, $c_nameHash) === 0) {
1222
+ @unlink($glob_full_path);
1223
+ continue 2;
1224
+ }
1225
+ }
1226
+
1227
+ $file_info = pathinfo($glob_full_path);
1228
+ // skip json file for pre build packages
1229
+ if (in_array($file_info['extension'], $extension_filter) || in_array($file_name, $active_files)) {
1230
+ continue;
1231
+ }
1232
+
1233
+ @unlink($glob_full_path);
1234
+ }
1235
+ }
1236
+
1237
+ /**
1238
+ * Cleans up the temp storage folder have a time interval
1239
+ *
1240
+ * @return void
1241
+ */
1242
+ public static function safeTmpCleanup($purge_temp_archives = false)
1243
+ {
1244
+ if ($purge_temp_archives) {
1245
+ $dir = DUP_Settings::getSsdirTmpPath() . "/*_archive.zip.*";
1246
+ foreach (glob($dir) as $file_path) {
1247
+ unlink($file_path);
1248
+ }
1249
+ $dir = DUP_Settings::getSsdirTmpPath() . "/*_archive.daf.*";
1250
+ foreach (glob($dir) as $file_path) {
1251
+ unlink($file_path);
1252
+ }
1253
+ } else {
1254
+ //Remove all temp files that are 24 hours old
1255
+ $dir = DUP_Settings::getSsdirTmpPath() . "/*";
1256
+
1257
+ $files = glob($dir);
1258
+
1259
+ if ($files !== false) {
1260
+ foreach ($files as $file_path) {
1261
+ // Cut back to keeping things around for just an hour 15 min
1262
+ if (filemtime($file_path) <= time() - DUPLICATOR_TEMP_CLEANUP_SECONDS) {
1263
+ unlink($file_path);
1264
+ }
1265
+ }
1266
+ }
1267
+ }
1268
+ }
1269
+
1270
+ /**
1271
+ * Starts the package DupArchive progressive build process - always assumed to only run off active package, NOT one in the package table
1272
+ *
1273
+ * @return obj Returns a DUP_Package object
1274
+ */
1275
+ public function runDupArchiveBuild()
1276
+ {
1277
+ $this->BuildProgress->start_timer();
1278
+ DUP_Log::Trace('Called');
1279
+
1280
+ if ($this->BuildProgress->failed) {
1281
+ DUP_LOG::Trace("build progress failed so setting package to failed");
1282
+ $this->setStatus(DUP_PackageStatus::ERROR);
1283
+ $message = "Package creation failed.";
1284
+ DUP_Log::Trace($message);
1285
+ return true;
1286
+ }
1287
+
1288
+ if ($this->BuildProgress->initialized == false) {
1289
+ DUP_Log::Trace('[DUP ARCHIVE] INIZIALIZE');
1290
+ $this->BuildProgress->initialized = true;
1291
+ $this->TimerStart = Dup_Util::getMicrotime();
1292
+ $this->update();
1293
+ }
1294
+
1295
+ //START BUILD
1296
+ if (!$this->BuildProgress->database_script_built) {
1297
+ DUP_Log::Info('[DUP ARCHIVE] BUILDING DATABASE');
1298
+ $this->Database->build($this, Dup_ErrorBehavior::ThrowException);
1299
+ DUP_Log::Info('[DUP ARCHIVE] VALIDATING DATABASE');
1300
+ $this->BuildProgress->database_script_built = true;
1301
+ $this->update();
1302
+ DUP_Log::Info('[DUP ARCHIVE] DONE DATABASE');
1303
+ } elseif (!$this->BuildProgress->archive_built) {
1304
+ DUP_Log::Info('[DUP ARCHIVE] BUILDING ARCHIVE');
1305
+ $this->Archive->build($this);
1306
+ $this->update();
1307
+ DUP_Log::Info('[DUP ARCHIVE] DONE ARCHIVE');
1308
+ } elseif (!$this->BuildProgress->installer_built) {
1309
+ DUP_Log::Info('[DUP ARCHIVE] BUILDING INSTALLER');
1310
+ // Installer being built is stuffed into the archive build phase
1311
+ }
1312
+
1313
+ if ($this->BuildProgress->has_completed()) {
1314
+ DUP_Log::Info('[DUP ARCHIVE] HAS COMPLETED CLOSING');
1315
+
1316
+ if (!$this->BuildProgress->failed) {
1317
+ DUP_LOG::Info("[DUP ARCHIVE] DUP ARCHIVE INTEGRITY CHECK");
1318
+ // Only makees sense to perform build integrity check on completed archives
1319
+ $this->runDupArchiveBuildIntegrityCheck();
1320
+ } else {
1321
+ DUP_LOG::trace("top of loop build progress failed");
1322
+ }
1323
+
1324
+ $timerEnd = DUP_Util::getMicrotime();
1325
+ $timerSum = DUP_Util::elapsedTime($timerEnd, $this->TimerStart);
1326
+ $this->Runtime = $timerSum;
1327
+
1328
+ //FINAL REPORT
1329
+ $info = "\n********************************************************************************\n";
1330
+ $info .= "RECORD ID:[{$this->ID}]\n";
1331
+ $info .= "TOTAL PROCESS RUNTIME: {$timerSum}\n";
1332
+ $info .= "PEAK PHP MEMORY USED: " . DUP_Server::getPHPMemory(true) . "\n";
1333
+ $info .= "DONE PROCESSING => {$this->Name} " . @date("Y-m-d H:i:s") . "\n";
1334
+
1335
+ DUP_Log::info($info);
1336
+ DUP_LOG::trace("Done package building");
1337
+
1338
+ if (!$this->BuildProgress->failed) {
1339
+ DUP_Log::Trace('[DUP ARCHIVE] HAS COMPLETED DONE');
1340
+ $this->setStatus(DUP_PackageStatus::COMPLETE);
1341
+ DUP_LOG::Trace("Cleaning up duparchive temp files");
1342
+ //File Cleanup
1343
+ $this->buildCleanup();
1344
+ do_action('duplicator_lite_build_completed', $this);
1345
+ } else {
1346
+ DUP_Log::Trace('[DUP ARCHIVE] HAS COMPLETED ERROR');
1347
+ }
1348
+ }
1349
+ DUP_Log::Close();
1350
+ return $this->BuildProgress->has_completed();
1351
+ }
1352
+
1353
+ /**
1354
+ * Starts the package build process
1355
+ *
1356
+ * @return obj Returns a DUP_Package object
1357
+ */
1358
+ public function runZipBuild()
1359
+ {
1360
+ $timerStart = DUP_Util::getMicrotime();
1361
+
1362
+ DUP_Log::Trace('#### start of zip build');
1363
+ //START BUILD
1364
+ //PHPs serialze method will return the object, but the ID above is not passed
1365
+ //for one reason or another so passing the object back in seems to do the trick
1366
+ $this->Database->build($this, Dup_ErrorBehavior::ThrowException);
1367
+ $this->Archive->build($this);
1368
+ $this->Installer->build($this);
1369
+
1370
+ //INTEGRITY CHECKS
1371
+ /*DUP_Log::Info("\n********************************************************************************");
1372
+ DUP_Log::Info("INTEGRITY CHECKS:");
1373
+ DUP_Log::Info("********************************************************************************");*/
1374
+ $this->runDupArchiveBuildIntegrityCheck();
1375
+ $dbSizeRead = DUP_Util::byteSize($this->Database->Size);
1376
+ $zipSizeRead = DUP_Util::byteSize($this->Archive->Size);
1377
+ $exeSizeRead = DUP_Util::byteSize($this->Installer->Size);
1378
+
1379
+ $timerEnd = DUP_Util::getMicrotime();
1380
+ $timerSum = DUP_Util::elapsedTime($timerEnd, $timerStart);
1381
+
1382
+ $this->Runtime = $timerSum;
1383
+ $this->ExeSize = $exeSizeRead;
1384
+ $this->ZipSize = $zipSizeRead;
1385
+
1386
+
1387
+ $this->buildCleanup();
1388
+
1389
+ //FINAL REPORT
1390
+ $info = "\n********************************************************************************\n";
1391
+ $info .= "RECORD ID:[{$this->ID}]\n";
1392
+ $info .= "TOTAL PROCESS RUNTIME: {$timerSum}\n";
1393
+ $info .= "PEAK PHP MEMORY USED: " . DUP_Server::getPHPMemory(true) . "\n";
1394
+ $info .= "DONE PROCESSING => {$this->Name} " . @date(get_option('date_format') . " " . get_option('time_format')) . "\n";
1395
+
1396
+
1397
+ DUP_Log::Info($info);
1398
+ DUP_Log::Close();
1399
+
1400
+ $this->setStatus(DUP_PackageStatus::COMPLETE);
1401
+ return $this;
1402
+ }
1403
+
1404
+ /**
1405
+ * Saves the active options associted with the active(latest) package.
1406
+ *
1407
+ * @see DUP_Package::getActive
1408
+ *
1409
+ * @param $_POST $post The Post server object
1410
+ *
1411
+ * @return null
1412
+ */
1413
+ public function saveActive($post = null)
1414
+ {
1415
+ global $wp_version;
1416
+
1417
+ if (isset($post)) {
1418
+ $post = stripslashes_deep($post);
1419
+
1420
+ $name = isset($post['package-name']) ? trim($post['package-name']) : self::getDefaultName();
1421
+ $name = str_replace(array(' ', '-'), '_', $name);
1422
+ $name = str_replace(array('.', ';', ':', "'", '"'), '', $name);
1423
+ $name = sanitize_file_name($name);
1424
+ $name = substr(trim($name), 0, 40);
1425
+
1426
+ if (isset($post['filter-dirs'])) {
1427
+ $post_filter_dirs = sanitize_text_field($post['filter-dirs']);
1428
+ $filter_dirs = $this->Archive->parseDirectoryFilter($post_filter_dirs);
1429
+ } else {
1430
+ $filter_dirs = '';
1431
+ }
1432
+
1433
+ if (isset($post['filter-files'])) {
1434
+ $post_filter_files = sanitize_text_field($post['filter-files']);
1435
+ $filter_files = $this->Archive->parseFileFilter($post_filter_files);
1436
+ } else {
1437
+ $filter_files = '';
1438
+ }
1439
+
1440
+ if (isset($post['filter-exts'])) {
1441
+ $post_filter_exts = sanitize_text_field($post['filter-exts']);
1442
+ $filter_exts = $this->Archive->parseExtensionFilter($post_filter_exts);
1443
+ } else {
1444
+ $filter_exts = '';
1445
+ }
1446
+
1447
+ $tablelist = '';
1448
+ if (isset($post['dbtables'])) {
1449
+ $tablelist = implode(',', $post['dbtables']);
1450
+ }
1451
+
1452
+ if (isset($post['dbcompat'])) {
1453
+ $compatlist = is_array($post['dbcompat']) ? implode(',', $post['dbcompat']) : sanitize_text_field($post['dbcompat']);
1454
+ } else {
1455
+ $compatlist = '';
1456
+ }
1457
+
1458
+ $dbversion = DUP_DB::getVersion();
1459
+ $dbversion = is_null($dbversion) ? '- unknown -' : sanitize_text_field($dbversion);
1460
+ $dbcomments = sanitize_text_field(DUP_DB::getVariable('version_comment'));
1461
+ $dbcomments = is_null($dbcomments) ? '- unknown -' : sanitize_text_field($dbcomments);
1462
+
1463
+ //PACKAGE
1464
+ $this->Created = gmdate("Y-m-d H:i:s");
1465
+ $this->Version = DUPLICATOR_VERSION;
1466
+ $this->VersionOS = defined('PHP_OS') ? PHP_OS : 'unknown';
1467
+ $this->VersionWP = $wp_version;
1468
+ $this->VersionPHP = phpversion();
1469
+ $this->VersionDB = sanitize_text_field($dbversion);
1470
+ $this->Name = sanitize_text_field($name);
1471
+ $this->Hash = $this->makeHash();
1472
+ $this->NameHash = sanitize_text_field("{$this->Name}_{$this->Hash}");
1473
+
1474
+ $this->Notes = sanitize_textarea_field($post['package-notes']);
1475
+ //ARCHIVE
1476
+ $this->Archive->Format = 'ZIP';
1477
+ $this->Archive->FilterOn = isset($post['filter-on']) ? 1 : 0;
1478
+ $this->Archive->ExportOnlyDB = isset($post['export-onlydb']) ? 1 : 0;
1479
+ $this->Archive->FilterDirs = sanitize_textarea_field($filter_dirs);
1480
+ $this->Archive->FilterFiles = sanitize_textarea_field($filter_files);
1481
+ $this->Archive->FilterExts = str_replace(array('.', ' '), '', $filter_exts);
1482
+ //INSTALLER
1483
+ $this->Installer->OptsDBHost = sanitize_text_field($post['dbhost']);
1484
+ $this->Installer->OptsDBPort = sanitize_text_field($post['dbport']);
1485
+ $this->Installer->OptsDBName = sanitize_text_field($post['dbname']);
1486
+ $this->Installer->OptsDBUser = sanitize_text_field($post['dbuser']);
1487
+ $this->Installer->OptsDBCharset = sanitize_text_field($post['dbcharset']);
1488
+ $this->Installer->OptsDBCollation = sanitize_text_field($post['dbcollation']);
1489
+ $this->Installer->OptsSecureOn = isset($post['secure-on']) ? 1 : 0;
1490
+ $post_secure_pass = sanitize_text_field($post['secure-pass']);
1491
+ $this->Installer->OptsSecurePass = DUP_Util::installerScramble($post_secure_pass);
1492
+ //DATABASE
1493
+ $this->Database->FilterOn = isset($post['dbfilter-on']) ? 1 : 0;
1494
+ $this->Database->FilterTables = sanitize_text_field($tablelist);
1495
+ $this->Database->Compatible = $compatlist;
1496
+ $this->Database->Comments = sanitize_text_field($dbcomments);
1497
+
1498
+ update_option(self::OPT_ACTIVE, $this);
1499
+ }
1500
+ }
1501
+
1502
+ /**
1503
+ * Update the serialized package and status in the database
1504
+ *
1505
+ * @return void
1506
+ */
1507
+ public function update()
1508
+ {
1509
+ global $wpdb;
1510
+
1511
+ $this->Status = number_format($this->Status, 1, '.', '');
1512
+ $this->cleanObjectBeforeSave();
1513
+ $packageObj = serialize($this);
1514
+
1515
+ if (!$packageObj) {
1516
+ DUP_Log::error("Package SetStatus was unable to serialize package object while updating record.");
1517
+ }
1518
+
1519
+ $wpdb->flush();
1520
+ $tablePrefix = DUP_Util::getTablePrefix();
1521
+ $table = $tablePrefix . "duplicator_packages";
1522
+ $sql = "UPDATE `{$table}` SET status = {$this->Status},";
1523
+ $sql .= "package = '" . esc_sql($packageObj) . "'";
1524
+ $sql .= "WHERE ID = {$this->ID}";
1525
+
1526
+ DUP_Log::Trace("UPDATE PACKAGE ID = {$this->ID} STATUS = {$this->Status}");
1527
+
1528
+ //DUP_Log::Trace('####Executing SQL' . $sql . '-----------');
1529
+ $wpdb->query($sql);
1530
+ }
1531
+
1532
+ /**
1533
+ * Save any property of this class through reflection
1534
+ *
1535
+ * @param $property A valid public property in this class
1536
+ * @param $value The value for the new dynamic property
1537
+ *
1538
+ * @return null
1539
+ */
1540
+ public function saveActiveItem($property, $value)
1541
+ {
1542
+ $package = self::getActive();
1543
+
1544
+ $reflectionClass = new ReflectionClass($package);
1545
+ $reflectionClass->getProperty($property)->setValue($package, $value);
1546
+ update_option(self::OPT_ACTIVE, $package);
1547
+ }
1548
+
1549
+ /**
1550
+ * Sets the status to log the state of the build
1551
+ * The status level for where the package is
1552
+ *
1553
+ * @param int $status
1554
+ *
1555
+ * @return void
1556
+ */
1557
+ public function setStatus($status)
1558
+ {
1559
+ if (!isset($status)) {
1560
+ DUP_Log::error("Package SetStatus did not receive a proper code.");
1561
+ }
1562
+ $this->Status = $status;
1563
+ $this->update();
1564
+ }
1565
+
1566
+ /**
1567
+ * Does a hash already exists
1568
+ * Returns 0 if no hash is found, if found returns the table ID
1569
+ *
1570
+ * @param string $hash An existing hash value
1571
+ *
1572
+ * @return int
1573
+ */
1574
+ public function getHashKey($hash)
1575
+ {
1576
+ global $wpdb;
1577
+
1578
+ $tablePrefix = DUP_Util::getTablePrefix();
1579
+ $table = $tablePrefix . "duplicator_packages";
1580
+ $qry = $wpdb->get_row("SELECT ID, hash FROM `{$table}` WHERE hash = '{$hash}'");
1581
+ if (is_null($qry) || strlen($qry->hash) == 0) {
1582
+ return 0;
1583
+ } else {
1584
+ return $qry->ID;
1585
+ }
1586
+ }
1587
+
1588
+ /**
1589
+ * Makes the hashkey for the package files
1590
+ *
1591
+ * @return string // A unique hashkey
1592
+ */
1593
+ public function makeHash()
1594
+ {
1595
+ try {
1596
+ if (function_exists('random_bytes') && DUP_Util::PHP53()) {
1597
+ return bin2hex(random_bytes(8)) . mt_rand(1000, 9999) . '_' . date("YmdHis");
1598
+ } else {
1599
+ return strtolower(md5(uniqid(rand(), true))) . '_' . date("YmdHis");
1600
+ }
1601
+ } catch (Exception $exc) {
1602
+ return strtolower(md5(uniqid(rand(), true))) . '_' . date("YmdHis");
1603
+ }
1604
+ }
1605
+
1606
+ /**
1607
+ * Gets the active package which is defined as the package that was lasted saved.
1608
+ * Do to cache issues with the built in WP function get_option moved call to a direct DB call.
1609
+ *
1610
+ * @see DUP_Package::saveActive
1611
+ *
1612
+ * @return DUP_Package // A copy of the DUP_Package object
1613
+ */
1614
+ public static function getActive()
1615
+ {
1616
+ global $wpdb;
1617
+
1618
+ $obj = new DUP_Package();
1619
+ $row = $wpdb->get_row($wpdb->prepare("SELECT option_value FROM `{$wpdb->options}` WHERE option_name = %s LIMIT 1", self::OPT_ACTIVE));
1620
+ if (is_object($row)) {
1621
+ $obj = @unserialize($row->option_value);
1622
+ }
1623
+ //Incase unserilaize fails
1624
+ $obj = (is_object($obj)) ? $obj : new DUP_Package();
1625
+
1626
+ return $obj;
1627
+ }
1628
+
1629
+ /**
1630
+ * Gets the Package by ID
1631
+ *
1632
+ * @param int $id A valid package id form the duplicator_packages table
1633
+ *
1634
+ * @return DUP_Package // A copy of the DUP_Package object
1635
+ */
1636
+ public static function getByID($id)
1637
+ {
1638
+ global $wpdb;
1639
+ $obj = new DUP_Package();
1640
+ $tablePrefix = DUP_Util::getTablePrefix();
1641
+ $sql = $wpdb->prepare("SELECT * FROM `{$tablePrefix}duplicator_packages` WHERE ID = %d", $id);
1642
+ $row = $wpdb->get_row($sql);
1643
+ if (is_object($row)) {
1644
+ $obj = @unserialize($row->package);
1645
+ // We was not storing Status in Lite 1.2.52, so it is for backward compatibility
1646
+ if (!isset($obj->Status)) {
1647
+ $obj->Status = $row->status;
1648
+ }
1649
+ }
1650
+ //Incase unserilaize fails
1651
+ $obj = (is_object($obj)) ? $obj : null;
1652
+ return $obj;
1653
+ }
1654
+
1655
+ /**
1656
+ * Gets a default name for the package
1657
+ *
1658
+ * @return string // A default package name such as 20170218_blogname
1659
+ */
1660
+ public static function getDefaultName($preDate = true)
1661
+ {
1662
+ //Remove specail_chars from final result
1663
+ $special_chars = array(".", "-");
1664
+ $name = ($preDate)
1665
+ ? date('Ymd') . '_' . sanitize_title(get_bloginfo('name', 'display'))
1666
+ : sanitize_title(get_bloginfo('name', 'display')) . '_' . date('Ymd');
1667
+ $name = substr(sanitize_file_name($name), 0, 40);
1668
+ $name = str_replace($special_chars, '', $name);
1669
+ return $name;
1670
+ }
1671
+
1672
+ /**
1673
+ * Cleanup all tmp files
1674
+ *
1675
+ * @param all empty all contents
1676
+ *
1677
+ * @return null
1678
+ */
1679
+ public static function tempFileCleanup($all = false)
1680
+ {
1681
+ if ($all) {
1682
+ //Delete all files now
1683
+ $dir = DUP_Settings::getSsdirTmpPath() . "/*";
1684
+ foreach (glob($dir) as $file) {
1685
+ @unlink($file);
1686
+ }
1687
+ } else {
1688
+ //Remove scan files that are 24 hours old
1689
+ $dir = DUP_Settings::getSsdirTmpPath() . "/*_scan.json";
1690
+ foreach (glob($dir) as $file) {
1691
+ if (filemtime($file) <= time() - 86400) {
1692
+ @unlink($file);
1693
+ }
1694
+ }
1695
+ }
1696
+ }
1697
+
1698
+ /**
1699
+ * Provides various date formats
1700
+ *
1701
+ * @param $utcDate created date in the GMT timezone
1702
+ * @param $format Various date formats to apply
1703
+ *
1704
+ * @return string // a formated date based on the $format
1705
+ */
1706
+ public static function getCreatedDateFormat($utcDate, $format = 1)
1707
+ {
1708
+ $date = get_date_from_gmt($utcDate);
1709
+ $date = new DateTime($date);
1710
+ switch ($format) {
1711
+ //YEAR
1712
+ case 1:
1713
+ return $date->format('Y-m-d H:i');
1714
+ break;
1715
+ case 2:
1716
+ return $date->format('Y-m-d H:i:s');
1717
+ break;
1718
+ case 3:
1719
+ return $date->format('y-m-d H:i');
1720
+ break;
1721
+ case 4:
1722
+ return $date->format('y-m-d H:i:s');
1723
+ break;
1724
+ //MONTH
1725
+ case 5:
1726
+ return $date->format('m-d-Y H:i');
1727
+ break;
1728
+ case 6:
1729
+ return $date->format('m-d-Y H:i:s');
1730
+ break;
1731
+ case 7:
1732
+ return $date->format('m-d-y H:i');
1733
+ break;
1734
+ case 8:
1735
+ return $date->format('m-d-y H:i:s');
1736
+ break;
1737
+ //DAY
1738
+ case 9:
1739
+ return $date->format('d-m-Y H:i');
1740
+ break;
1741
+ case 10:
1742
+ return $date->format('d-m-Y H:i:s');
1743
+ break;
1744
+ case 11:
1745
+ return $date->format('d-m-y H:i');
1746
+ break;
1747
+ case 12:
1748
+ return $date->format('d-m-y H:i:s');
1749
+ break;
1750
+ default:
1751
+ return $date->format('Y-m-d H:i');
1752
+ }
1753
+ }
1754
+
1755
+ /**
1756
+ * Cleans up all the tmp files as part of the package build process
1757
+ */
1758
+ public function buildCleanup()
1759
+ {
1760
+ $files = DUP_Util::listFiles(DUP_Settings::getSsdirTmpPath());
1761
+ $newPath = DUP_Settings::getSsdirPath();
1762
+
1763
+ $filesToStore = array(
1764
+ $this->Installer->File,
1765
+ $this->Archive->File,
1766
+ );
1767
+
1768
+ foreach ($files as $file) {
1769
+ $fileName = basename($file);
1770
+ if (!strstr($fileName, $this->NameHash)) {
1771
+ continue;
1772
+ }
1773
+
1774
+ if (in_array($fileName, $filesToStore)) {
1775
+ if (function_exists('rename')) {
1776
+ rename($file, "{$newPath}/{$fileName}");
1777
+ } elseif (function_exists('copy')) {
1778
+ copy($file, "{$newPath}/{$fileName}");
1779
+ } else {
1780
+ throw new Exception('PHP copy and rename functions not found! Contact hosting provider!');
1781
+ }
1782
+ }
1783
+
1784
+ if (file_exists($file)) {
1785
+ unlink($file);
1786
+ }
1787
+ }
1788
+ }
1789
+
1790
+
1791
+
1792
+ /**
1793
+ * Get package hash
1794
+ *
1795
+ * @return string package hash
1796
+ */
1797
+ public function getPackageHash()
1798
+ {
1799
+ $hashParts = explode('_', $this->Hash);
1800
+ $firstPart = substr($hashParts[0], 0, 7);
1801
+ $secondPart = substr($hashParts[1], -8);
1802
+ $package_hash = $firstPart . '-' . $secondPart;
1803
+ return $package_hash;
1804
+ }
1805
+
1806
+ public function getSecondaryPackageHash()
1807
+ {
1808
+ $newHash = $this->makeHash();
1809
+ $hashParts = explode('_', $newHash);
1810
+ $firstPart = substr($hashParts[0], 0, 7);
1811
+
1812
+ $hashParts = explode('_', $this->Hash);
1813
+ $secondPart = substr($hashParts[1], -8);
1814
+
1815
+ $package_hash = $firstPart . '-' . $secondPart;
1816
+ return $package_hash;
1817
+ }
1818
+
1819
+ /**
1820
+ * Provides the full sql file path in archive
1821
+ *
1822
+ * @return the full sql file path in archive
1823
+ */
1824
+ public function getSqlArkFilePath()
1825
+ {
1826
+ $package_hash = $this->getPackageHash();
1827
+ $sql_ark_file_Path = 'dup-installer/dup-database__' . $package_hash . '.sql';
1828
+ return $sql_ark_file_Path;
1829
+ }
1830
+
1831
+ private function writeLogHeader()
1832
+ {
1833
+ $php_max_time = @ini_get("max_execution_time");
1834
+ if (SnapUtil::isIniValChangeable('memory_limit')) {
1835
+ $php_max_memory = @ini_set('memory_limit', DUPLICATOR_PHP_MAX_MEMORY);
1836
+ } else {
1837
+ $php_max_memory = @ini_get('memory_limit');
1838
+ }
1839
+
1840
+ $php_max_time = ($php_max_time == 0) ? "(0) no time limit imposed" : "[{$php_max_time}] not allowed";
1841
+ $php_max_memory = ($php_max_memory === false) ? "Unabled to set php memory_limit" : DUPLICATOR_PHP_MAX_MEMORY . " ({$php_max_memory} default)";
1842
+
1843
+ $info = "********************************************************************************\n";
1844
+ $info .= "DUPLICATOR-LITE PACKAGE-LOG: " . @date(get_option('date_format') . " " . get_option('time_format')) . "\n";
1845
+ $info .= "NOTICE: Do NOT post to public sites or forums \n";
1846
+ $info .= "********************************************************************************\n";
1847
+ $info .= "VERSION:\t" . DUPLICATOR_VERSION . "\n";
1848
+ $info .= "WORDPRESS:\t{$GLOBALS['wp_version']}\n";
1849
+ $info .= "PHP INFO:\t" . phpversion() . ' | ' . 'SAPI: ' . php_sapi_name() . "\n";
1850
+ $info .= "SERVER:\t\t{$_SERVER['SERVER_SOFTWARE']} \n";
1851
+ $info .= "PHP TIME LIMIT: {$php_max_time} \n";
1852
+ $info .= "PHP MAX MEMORY: {$php_max_memory} \n";
1853
+ $info .= "MEMORY STACK: " . DUP_Server::getPHPMemory();
1854
+ DUP_Log::Info($info);
1855
+
1856
+ $info = null;
1857
+ }
1858
+ }
classes/package/duparchive/class.pack.archive.duparchive.php CHANGED
@@ -1,313 +1,268 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
-
4
- require_once (DUPLICATOR_PLUGIN_PATH.'classes/package/duparchive/class.pack.archive.duparchive.state.expand.php');
5
- require_once (DUPLICATOR_PLUGIN_PATH.'classes/package/duparchive/class.pack.archive.duparchive.state.create.php');
6
- require_once (DUPLICATOR_PLUGIN_PATH.'lib/dup_archive/classes/class.duparchive.loggerbase.php');
7
- require_once (DUPLICATOR_PLUGIN_PATH.'lib/dup_archive/classes/class.duparchive.engine.php');
8
- require_once (DUPLICATOR_PLUGIN_PATH.'lib/dup_archive/classes/states/class.duparchive.state.create.php');
9
- require_once (DUPLICATOR_PLUGIN_PATH.'lib/dup_archive/classes/states/class.duparchive.state.expand.php');
10
- require_once (DUPLICATOR_PLUGIN_PATH.'lib/dup_archive/classes/class.duparchive.processing.failure.php');
11
-
12
- class DUP_DupArchive_Logger extends DupArchiveLoggerBase
13
- {
14
-
15
- public function log($s, $flush = false, $callingFunctionOverride = null)
16
- {
17
- DUP_Log::Trace($s, true, $callingFunctionOverride);
18
- }
19
- }
20
-
21
- class DUP_DupArchive
22
- {
23
-
24
- // Using a worker time override since evidence shorter time works much
25
- const WorkerTimeInSec = 10;
26
-
27
- /**
28
- * CREATE
29
- * Creates the zip file and adds the SQL file to the archive
30
- */
31
- public static function create($archive, $buildProgress, $package)
32
- {
33
- /* @var $buildProgress DUP_Build_Progress */
34
-
35
- DUP_LOG::trace("start");
36
- try {
37
- DUP_Log::Open($package->NameHash);
38
-
39
- if ($buildProgress->retries > DUPLICATOR_MAX_BUILD_RETRIES) {
40
- $error_msg = __('Package build appears stuck so marking package as failed. Is the Max Worker Time set too high?.', 'duplicator');
41
- DUP_Log::error(esc_html__('Build Failure', 'duplicator'), esc_html($error_msg), Dup_ErrorBehavior::LogOnly);
42
- //$buildProgress->failed = true;
43
- $buildProgress->set_failed($error_msg);
44
- $package->setStatus(DUP_PackageStatus::ERROR);
45
- ;
46
- return true;
47
- } else {
48
- // If all goes well retries will be reset to 0 at the end of this function.
49
- $buildProgress->retries++;
50
- $package->update();
51
- }
52
-
53
- $done = false;
54
-
55
- DupArchiveEngine::init(new DUP_DupArchive_Logger(), null, $archive);
56
- DUP_Package::safeTmpCleanup(true);
57
-
58
- $compressDir = rtrim(DUP_Util::safePath($archive->PackDir), '/');
59
- $sqlPath = DUP_Settings::getSsdirTmpPath()."/{$package->Database->File}";
60
- $archivePath = DUP_Settings::getSsdirTmpPath()."/{$archive->File}";
61
- $scanFilepath = DUP_Settings::getSsdirTmpPath()."/{$package->NameHash}_scan.json";
62
- $skipArchiveFinalization = false;
63
- $json = '';
64
-
65
- if (file_exists($scanFilepath)) {
66
-
67
- $json = file_get_contents($scanFilepath);
68
-
69
- if (empty($json)) {
70
- $errorText = __("Scan file $scanFilepath is empty!", 'duplicator');
71
- $fixText = __("Click on \"Resolve This\" button to fix the JSON settings.", 'duplicator');
72
-
73
- DUP_Log::Trace($errorText);
74
- DUP_Log::error(esc_html($errorText)." **RECOMMENDATION: ".esc_html($fixText).".", '', Dup_ErrorBehavior::LogOnly);
75
-
76
- //$buildProgress->failed = true;
77
- $buildProgress->set_failed($errorText);
78
- $package->setStatus(DUP_PackageStatus::ERROR);
79
- return true;
80
- }
81
- } else {
82
- DUP_Log::trace("**** scan file $scanFilepath doesn't exist!!");
83
- $errorMessage = sprintf(__("ERROR: Can't find Scanfile %s. Please ensure there no non-English characters in the package or schedule name.", 'duplicator'), $scanFilepath);
84
-
85
- DUP_Log::error($errorMessage, '', Dup_ErrorBehavior::LogOnly);
86
-
87
- //$buildProgress->failed = true;
88
- $buildProgress->set_failed($errorMessage);
89
- $package->setStatus(DUP_PackageStatus::ERROR);
90
- return true;
91
- }
92
-
93
- Dup_Log::TraceObject("buildprogress object", $buildProgress, false);
94
-
95
- $scanReport = json_decode($json);
96
-
97
- if ($buildProgress->archive_started == false) {
98
-
99
- $filterDirs = empty($archive->FilterDirs) ? 'not set' : $archive->FilterDirs;
100
- $filterExts = empty($archive->FilterExts) ? 'not set' : $archive->FilterExts;
101
- $filterFiles = empty($archive->FilterFiles) ? 'not set' : $archive->FilterFiles;
102
- $filterOn = ($archive->FilterOn) ? 'ON' : 'OFF';
103
- $filterDirsFormat = rtrim(str_replace(';', "\n\t", $filterDirs));
104
- $filterFilesFormat = rtrim(str_replace(';', "\n\t", $filterFiles));
105
-
106
- DUP_Log::info("\n********************************************************************************");
107
- DUP_Log::info("ARCHIVE Type=DUP Mode=DupArchive");
108
- DUP_Log::info("********************************************************************************");
109
- DUP_Log::info("ARCHIVE DIR: ".$compressDir);
110
- DUP_Log::info("ARCHIVE FILE: ".basename($archivePath));
111
- DUP_Log::info("FILTERS: *{$filterOn}*");
112
- DUP_Log::Info("DIRS:\n\t{$filterDirsFormat}");
113
- DUP_Log::Info("FILES:\n\t{$filterFilesFormat}");
114
- DUP_Log::info("EXTS: {$filterExts}");
115
- DUP_Log::info("----------------------------------------");
116
- DUP_Log::info("COMPRESSING");
117
- DUP_Log::info("SIZE:\t".$scanReport->ARC->Size);
118
- DUP_Log::info("STATS:\tDirs ".$scanReport->ARC->DirCount." | Files ".$scanReport->ARC->FileCount." | Total ".$scanReport->ARC->FullCount);
119
-
120
- if (($scanReport->ARC->DirCount == '') || ($scanReport->ARC->FileCount == '') || ($scanReport->ARC->FullCount == '')) {
121
- $error_message = 'Invalid Scan Report Detected';
122
-
123
- DUP_Log::error($error_message, 'Invalid Scan Report Detected', Dup_ErrorBehavior::LogOnly);
124
- $buildProgress->set_failed($error_message);
125
- $package->setStatus(DUP_PackageStatus::ERROR);
126
- return true;
127
- }
128
-
129
- try {
130
- DupArchiveEngine::createArchive($archivePath, true);
131
- $sql_ark_file_path = $package->getSqlArkFilePath();
132
- DupArchiveEngine::addRelativeFileToArchiveST($archivePath, $sqlPath, $sql_ark_file_path);
133
- }
134
- catch (Exception $ex) {
135
- $error_message = 'Error adding database.sql to archive';
136
-
137
- DUP_Log::error($error_message, $ex->getMessage(), Dup_ErrorBehavior::LogOnly);
138
- $buildProgress->set_failed($error_message);
139
- $package->setStatus(DUP_PackageStatus::ERROR);
140
- return true;
141
- }
142
-
143
- $buildProgress->archive_started = true;
144
- $buildProgress->retries = 0;
145
-
146
- $createState = DUP_DupArchive_Create_State::createNew($archivePath, $compressDir, self::WorkerTimeInSec, true, true);
147
- $createState->throttleDelayInUs = 0;
148
-
149
- $createState->save();
150
- $package->Update();
151
- }
152
-
153
- try {
154
- $createState = DUP_DupArchive_Create_State::get_instance();
155
-
156
- if ($buildProgress->retries > 1) {
157
- // Indicates it had problems before so move into robustness mode
158
- $createState->isRobust = true;
159
- $createState->save();
160
- }
161
-
162
- if ($createState->working) {
163
- DUP_LOG::Trace("Create state is working");
164
- //die(0);//rsr
165
- // DupArchiveEngine::addItemsToArchive($createState, $scanReport->ARC, $archive);
166
- DupArchiveEngine::addItemsToArchive($createState, $scanReport->ARC);
167
-
168
- $buildProgress->set_build_failures($createState->failures);
169
-
170
- if ($createState->isCriticalFailurePresent()) {
171
- throw new Exception($createState->getFailureSummary());
172
- }
173
-
174
- $totalFileCount = count($scanReport->ARC->Files);
175
- $package->Status = DupLiteSnapLibUtil::getWorkPercent(DUP_PackageStatus::ARCSTART, DUP_PackageStatus::ARCVALIDATION, $totalFileCount, $createState->currentFileIndex);
176
- $buildProgress->retries = 0;
177
- $createState->save();
178
-
179
- DUP_LOG::TraceObject("Stored Create State", $createState);
180
- DUP_LOG::TraceObject('Stored build_progress', $package->BuildProgress);
181
-
182
- if ($createState->working == false) {
183
- // Want it to do the final cleanup work in an entirely new thread so return immediately
184
- $skipArchiveFinalization = true;
185
- DUP_LOG::TraceObject("Done build phase. Create State=", $createState);
186
- }
187
- }
188
- }
189
- catch (Exception $ex) {
190
- $message = __('Problem adding items to archive.', 'duplicator').' '.$ex->getMessage();
191
-
192
- DUP_Log::error(__('Problems adding items to archive.', 'duplicator'), $message, Dup_ErrorBehavior::LogOnly);
193
- DUP_Log::TraceObject($message." EXCEPTION:", $ex);
194
- //$buildProgress->failed = true;
195
- $buildProgress->set_failed($message);
196
- $package->setStatus(DUP_PackageStatus::ERROR);
197
- return true;
198
- }
199
-
200
- //-- Final Wrapup of the Archive
201
- if ((!$skipArchiveFinalization) && ($createState->working == false)) {
202
-
203
- DUP_LOG::Trace("Create state is not working and not skip archive finalization");
204
-
205
- if (!$buildProgress->installer_built) {
206
-
207
- if ($package->Installer->build($package, false)) {
208
- $package->Runtime = -1;
209
- $package->ExeSize = DUP_Util::byteSize($package->Installer->Size);
210
- $package->ZipSize = DUP_Util::byteSize($package->Archive->Size);
211
- $package->update();
212
- } else {
213
- $package->update();
214
- return;
215
- }
216
-
217
- DUP_Log::Trace("Installer has been built so running expand now");
218
-
219
- $expandState = DUP_DupArchive_Expand_State::getInstance(true);
220
-
221
- $expandState->archivePath = $archivePath;
222
- $expandState->working = true;
223
- $expandState->timeSliceInSecs = self::WorkerTimeInSec;
224
- $expandState->basePath = DUP_Settings::getSsdirTmpPath().'/validate';
225
- $expandState->throttleDelayInUs = 0; // RSR TODO
226
- $expandState->validateOnly = true;
227
- $expandState->validationType = DupArchiveValidationTypes::Standard;
228
- $expandState->working = true;
229
- $expandState->expectedDirectoryCount = count($scanReport->ARC->Dirs) - $createState->skippedDirectoryCount + $package->Installer->numDirsAdded;
230
- $expandState->expectedFileCount = count($scanReport->ARC->Files) + 1 - $createState->skippedFileCount + $package->Installer->numFilesAdded; // database.sql will be in there
231
-
232
- $expandState->save();
233
-
234
- $sfc = count($scanReport->ARC->Files);
235
- $nfa = $package->Installer->numFilesAdded;
236
- Dup_Log::trace("####scan files {$sfc} skipped files {$createState->skippedFileCount} num files added {$nfa}");
237
- DUP_LOG::traceObject("EXPAND STATE AFTER SAVE", $expandState);
238
- } else {
239
-
240
- try {
241
-
242
- $expandState = DUP_DupArchive_Expand_State::getInstance();
243
-
244
- if ($buildProgress->retries > 1) {
245
- // Indicates it had problems before so move into robustness mode
246
- $expandState->isRobust = true;
247
- $expandState->save();
248
- }
249
-
250
- DUP_Log::traceObject('Resumed validation expand state', $expandState);
251
- DupArchiveEngine::expandArchive($expandState);
252
- $buildProgress->set_validation_failures($expandState->failures);
253
- $totalFileCount = count($scanReport->ARC->Files);
254
- $archiveSize = @filesize($expandState->archivePath);
255
-
256
- $package->Status = DupLiteSnapLibUtil::getWorkPercent(DUP_PackageStatus::ARCVALIDATION, DUP_PackageStatus::COMPLETE, $archiveSize,
257
- $expandState->archiveOffset);
258
- DUP_LOG::TraceObject("package status after expand=", $package->Status);
259
- DUP_LOG::Trace("archive size:{$archiveSize} expand offset:{$expandState->archiveOffset}");
260
- }
261
- catch (Exception $ex) {
262
- DUP_Log::Trace('Exception:'.$ex->getMessage().':'.$ex->getTraceAsString());
263
- $buildProgress->set_failed('Error validating archive');
264
- $package->setStatus(DUP_PackageStatus::ERROR);
265
- return true;
266
- }
267
-
268
- if ($expandState->isCriticalFailurePresent()) {
269
- // Fail immediately if critical failure present - even if havent completed processing the entire archive.
270
- $error_message = __('Critical failure present in validation', 'duplicator');
271
- DUP_Log::error($error_message, $expandState->getFailureSummary(), Dup_ErrorBehavior::LogOnly);
272
- $buildProgress->set_failed($error_message);
273
- return true;
274
- } else if (!$expandState->working) {
275
-
276
- $buildProgress->archive_built = true;
277
- $buildProgress->retries = 0;
278
- $package->update();
279
-
280
- $timerAllEnd = DUP_Util::getMicrotime();
281
- $timerAllSum = DUP_Util::elapsedTime($timerAllEnd, $package->TimerStart);
282
-
283
- DUP_LOG::traceObject("create state", $createState);
284
-
285
- $archiveFileSize = @filesize($archivePath);
286
- DUP_Log::info("COMPRESSED SIZE: ".DUP_Util::byteSize($archiveFileSize));
287
- DUP_Log::info("ARCHIVE RUNTIME: {$timerAllSum}");
288
- DUP_Log::info("MEMORY STACK: ".DUP_Server::getPHPMemory());
289
- DUP_Log::info("CREATE WARNINGS: ".$createState->getFailureSummary(false, true));
290
- DUP_Log::info("VALIDATION WARNINGS: ".$expandState->getFailureSummary(false, true));
291
-
292
- $archive->file_count = $expandState->fileWriteCount + $expandState->directoryWriteCount;
293
- $package->update();
294
- $done = true;
295
- } else {
296
- $expandState->save();
297
- }
298
- }
299
- }
300
- }
301
- catch (Exception $ex) {
302
- // Have to have a catchall since the main system that calls this function is not prepared to handle exceptions
303
- DUP_Log::trace('Top level create Exception:'.$ex->getMessage().':'.$ex->getTraceAsString());
304
- //$buildProgress->failed = true;
305
- $buildProgress->set_failed('Error encoundtered creating archive. See package log');
306
- return true;
307
- }
308
-
309
- $buildProgress->retries = 0;
310
-
311
- return $done;
312
- }
313
- }
1
+ <?php
2
+
3
+ use Duplicator\Libs\DupArchive\DupArchiveEngine;
4
+ use Duplicator\Libs\DupArchive\DupArchiveLoggerBase;
5
+ use Duplicator\Libs\DupArchive\States\DupArchiveExpandState;
6
+ use Duplicator\Libs\Snap\SnapUtil;
7
+
8
+ require_once(DUPLICATOR_PLUGIN_PATH . 'classes/package/duparchive/class.pack.archive.duparchive.state.expand.php');
9
+ require_once(DUPLICATOR_PLUGIN_PATH . 'classes/package/duparchive/class.pack.archive.duparchive.state.create.php');
10
+
11
+ class DUP_DupArchive_Logger extends DupArchiveLoggerBase
12
+ {
13
+ public function log($s, $flush = false, $callingFunctionOverride = null)
14
+ {
15
+ DUP_Log::Trace($s, true, $callingFunctionOverride);
16
+ }
17
+ }
18
+
19
+ class DUP_DupArchive
20
+ {
21
+ // Using a worker time override since evidence shorter time works much
22
+ const WORKER_TIME_IN_SEC = 10;
23
+ /**
24
+ * CREATE
25
+ * Creates the zip file and adds the SQL file to the archive
26
+ */
27
+ public static function create(DUP_Archive $archive, $buildProgress, $package)
28
+ {
29
+ /* @var $buildProgress DUP_Build_Progress */
30
+
31
+ DUP_LOG::trace("start");
32
+ try {
33
+ DUP_Log::Open($package->NameHash);
34
+ if ($buildProgress->retries > DUPLICATOR_MAX_BUILD_RETRIES) {
35
+ $error_msg = __('Package build appears stuck so marking package as failed. Is the Max Worker Time set too high?.', 'duplicator');
36
+ DUP_Log::error(esc_html__('Build Failure', 'duplicator'), esc_html($error_msg), Dup_ErrorBehavior::LogOnly);
37
+ //$buildProgress->failed = true;
38
+ $buildProgress->set_failed($error_msg);
39
+ $package->setStatus(DUP_PackageStatus::ERROR);
40
+ ;
41
+ return true;
42
+ } else {
43
+ // If all goes well retries will be reset to 0 at the end of this function.
44
+ $buildProgress->retries++;
45
+ $package->update();
46
+ }
47
+
48
+ $done = false;
49
+ DupArchiveEngine::init(new DUP_DupArchive_Logger(), $archive->getTargetRootPath());
50
+ DUP_Package::safeTmpCleanup(true);
51
+ $compressDir = rtrim(DUP_Util::safePath($archive->PackDir), '/');
52
+ $sqlPath = DUP_Settings::getSsdirTmpPath() . "/{$package->Database->File}";
53
+ $archivePath = DUP_Settings::getSsdirTmpPath() . "/{$archive->File}";
54
+ $scanFilepath = DUP_Settings::getSsdirTmpPath() . "/{$package->NameHash}_scan.json";
55
+ $skipArchiveFinalization = false;
56
+ $json = '';
57
+ if (file_exists($scanFilepath)) {
58
+ $json = file_get_contents($scanFilepath);
59
+ if (empty($json)) {
60
+ $errorText = __("Scan file $scanFilepath is empty!", 'duplicator');
61
+ $fixText = __("Click on \"Resolve This\" button to fix the JSON settings.", 'duplicator');
62
+ DUP_Log::Trace($errorText);
63
+ DUP_Log::error(esc_html($errorText) . " **RECOMMENDATION: " . esc_html($fixText) . ".", '', Dup_ErrorBehavior::LogOnly);
64
+ //$buildProgress->failed = true;
65
+ $buildProgress->set_failed($errorText);
66
+ $package->setStatus(DUP_PackageStatus::ERROR);
67
+ return true;
68
+ }
69
+ } else {
70
+ DUP_Log::trace("**** scan file $scanFilepath doesn't exist!!");
71
+ $errorMessage = sprintf(__("ERROR: Can't find Scanfile %s. Please ensure there no non-English characters in the package or schedule name.", 'duplicator'), $scanFilepath);
72
+ DUP_Log::error($errorMessage, '', Dup_ErrorBehavior::LogOnly);
73
+ //$buildProgress->failed = true;
74
+ $buildProgress->set_failed($errorMessage);
75
+ $package->setStatus(DUP_PackageStatus::ERROR);
76
+ return true;
77
+ }
78
+
79
+ Dup_Log::TraceObject("buildprogress object", $buildProgress, false);
80
+ $scanReport = json_decode($json);
81
+ if ($buildProgress->archive_started == false) {
82
+ $filterDirs = empty($archive->FilterDirs) ? 'not set' : $archive->FilterDirs;
83
+ $filterExts = empty($archive->FilterExts) ? 'not set' : $archive->FilterExts;
84
+ $filterFiles = empty($archive->FilterFiles) ? 'not set' : $archive->FilterFiles;
85
+ $filterOn = ($archive->FilterOn) ? 'ON' : 'OFF';
86
+ $filterDirsFormat = rtrim(str_replace(';', "\n\t", $filterDirs));
87
+ $filterFilesFormat = rtrim(str_replace(';', "\n\t", $filterFiles));
88
+ DUP_Log::info("\n********************************************************************************");
89
+ DUP_Log::info("ARCHIVE Type=DUP Mode=DupArchive");
90
+ DUP_Log::info("********************************************************************************");
91
+ DUP_Log::info("ARCHIVE DIR: " . $compressDir);
92
+ DUP_Log::info("ARCHIVE FILE: " . basename($archivePath));
93
+ DUP_Log::info("FILTERS: *{$filterOn}*");
94
+ DUP_Log::Info("DIRS:\n\t{$filterDirsFormat}");
95
+ DUP_Log::Info("FILES:\n\t{$filterFilesFormat}");
96
+ DUP_Log::info("EXTS: {$filterExts}");
97
+ DUP_Log::info("----------------------------------------");
98
+ DUP_Log::info("COMPRESSING");
99
+ DUP_Log::info("SIZE:\t" . $scanReport->ARC->Size);
100
+ DUP_Log::info("STATS:\tDirs " . $scanReport->ARC->DirCount . " | Files " . $scanReport->ARC->FileCount . " | Total " . $scanReport->ARC->FullCount);
101
+ if (($scanReport->ARC->DirCount == '') || ($scanReport->ARC->FileCount == '') || ($scanReport->ARC->FullCount == '')) {
102
+ $error_message = 'Invalid Scan Report Detected';
103
+ DUP_Log::error($error_message, 'Invalid Scan Report Detected', Dup_ErrorBehavior::LogOnly);
104
+ $buildProgress->set_failed($error_message);
105
+ $package->setStatus(DUP_PackageStatus::ERROR);
106
+ return true;
107
+ }
108
+
109
+ try {
110
+ DupArchiveEngine::createArchive($archivePath, true);
111
+ $sql_ark_file_path = $package->getSqlArkFilePath();
112
+ DupArchiveEngine::addRelativeFileToArchiveST($archivePath, $sqlPath, $sql_ark_file_path);
113
+ } catch (Exception $ex) {
114
+ $error_message = 'Error adding database.sql to archive';
115
+ DUP_Log::error($error_message, $ex->getMessage(), Dup_ErrorBehavior::LogOnly);
116
+ $buildProgress->set_failed($error_message);
117
+ $package->setStatus(DUP_PackageStatus::ERROR);
118
+ return true;
119
+ }
120
+
121
+ $buildProgress->archive_started = true;
122
+ $buildProgress->retries = 0;
123
+ $createState = DUP_DupArchive_Create_State::createNew($archivePath, $compressDir, self::WORKER_TIME_IN_SEC, true, true);
124
+ $createState->throttleDelayInUs = 0;
125
+ $createState->save();
126
+ $package->Update();
127
+ }
128
+
129
+ try {
130
+ $createState = DUP_DupArchive_Create_State::get_instance();
131
+ if ($buildProgress->retries > 1) {
132
+ $createState->isRobust = true;
133
+ $createState->save();
134
+ }
135
+
136
+ if ($createState->working) {
137
+ DUP_LOG::Trace("Create state is working");
138
+ DupArchiveEngine::addItemsToArchive($createState, $scanReport->ARC);
139
+ $buildProgress->set_build_failures($createState->failures);
140
+ if ($createState->isCriticalFailurePresent()) {
141
+ throw new Exception($createState->getFailureSummary());
142
+ }
143
+
144
+ $totalFileCount = count($scanReport->ARC->Files);
145
+ $package->Status = SnapUtil::getWorkPercent(DUP_PackageStatus::ARCSTART, DUP_PackageStatus::ARCVALIDATION, $totalFileCount, $createState->currentFileIndex);
146
+ $buildProgress->retries = 0;
147
+ $createState->save();
148
+ DUP_LOG::TraceObject("Stored Create State", $createState);
149
+ DUP_LOG::TraceObject('Stored build_progress', $package->BuildProgress);
150
+ if ($createState->working == false) {
151
+ // Want it to do the final cleanup work in an entirely new thread so return immediately
152
+ $skipArchiveFinalization = true;
153
+ DUP_LOG::TraceObject("Done build phase. Create State=", $createState);
154
+ }
155
+ }
156
+ } catch (Exception $ex) {
157
+ $message = __('Problem adding items to archive.', 'duplicator') . ' ' . $ex->getMessage();
158
+ DUP_Log::error(__('Problems adding items to archive.', 'duplicator'), $message, Dup_ErrorBehavior::LogOnly);
159
+ DUP_Log::TraceObject($message . " EXCEPTION:", $ex);
160
+ //$buildProgress->failed = true;
161
+ $buildProgress->set_failed($message);
162
+ $package->setStatus(DUP_PackageStatus::ERROR);
163
+ return true;
164
+ }
165
+
166
+ //-- Final Wrapup of the Archive
167
+ if ((!$skipArchiveFinalization) && ($createState->working == false)) {
168
+ DUP_LOG::Trace("Create state is not working and not skip archive finalization");
169
+ if (!$buildProgress->installer_built) {
170
+ if ($package->Installer->build($package, false)) {
171
+ $package->Runtime = -1;
172
+ $package->ExeSize = DUP_Util::byteSize($package->Installer->Size);
173
+ $package->ZipSize = DUP_Util::byteSize($package->Archive->Size);
174
+ $package->update();
175
+ } else {
176
+ $package->update();
177
+ return;
178
+ }
179
+
180
+ DUP_Log::Trace("Installer has been built so running expand now");
181
+ $expandState = DUP_DupArchive_Expand_State::getInstance(true);
182
+ $expandState->archivePath = $archivePath;
183
+ $expandState->working = true;
184
+ $expandState->timeSliceInSecs = self::WORKER_TIME_IN_SEC;
185
+ $expandState->basePath = DUP_Settings::getSsdirTmpPath() . '/validate';
186
+ $expandState->throttleDelayInUs = 0;
187
+ // RSR TODO
188
+ $expandState->validateOnly = true;
189
+ $expandState->validationType = DupArchiveExpandState::VALIDATION_NONE;
190
+ $expandState->working = true;
191
+ $expandState->expectedDirectoryCount = count($scanReport->ARC->Dirs) - $createState->skippedDirectoryCount + $package->Installer->numDirsAdded;
192
+ $expandState->expectedFileCount = 1 + count($scanReport->ARC->Files) + 1 - $createState->skippedFileCount + $package->Installer->numFilesAdded;
193
+ // database.sql will be in there
194
+
195
+ $expandState->save();
196
+ $sfc = count($scanReport->ARC->Files);
197
+ $nfa = $package->Installer->numFilesAdded;
198
+ Dup_Log::trace("####scan files {$sfc} skipped files {$createState->skippedFileCount} num files added {$nfa}");
199
+ DUP_LOG::traceObject("EXPAND STATE AFTER SAVE", $expandState);
200
+ } else {
201
+ try {
202
+ $expandState = DUP_DupArchive_Expand_State::getInstance();
203
+ if ($buildProgress->retries > 1) {
204
+ // Indicates it had problems before so move into robustness mode
205
+ $expandState->isRobust = true;
206
+ $expandState->save();
207
+ }
208
+
209
+ DUP_Log::traceObject('Resumed validation expand state', $expandState);
210
+ DupArchiveEngine::expandArchive($expandState);
211
+ $buildProgress->set_validation_failures($expandState->failures);
212
+ $totalFileCount = count($scanReport->ARC->Files);
213
+ $archiveSize = @filesize($expandState->archivePath);
214
+
215
+ $package->Status = SnapUtil::getWorkPercent(
216
+ DUP_PackageStatus::ARCVALIDATION,
217
+ DUP_PackageStatus::COMPLETE,
218
+ $archiveSize,
219
+ $expandState->archiveOffset
220
+ );
221
+ DUP_LOG::TraceObject("package status after expand=", $package->Status);
222
+ DUP_LOG::Trace("archive size:{$archiveSize} expand offset:{$expandState->archiveOffset}");
223
+ } catch (Exception $ex) {
224
+ DUP_Log::Trace('Exception:' . $ex->getMessage() . ':' . $ex->getTraceAsString());
225
+ $buildProgress->set_failed('Error validating archive');
226
+ $package->setStatus(DUP_PackageStatus::ERROR);
227
+ return true;
228
+ }
229
+
230
+ if ($expandState->isCriticalFailurePresent()) {
231
+ // Fail immediately if critical failure present - even if havent completed processing the entire archive.
232
+ $error_message = __('Critical failure present in validation', 'duplicator');
233
+ DUP_Log::error($error_message, $expandState->getFailureSummary(), Dup_ErrorBehavior::LogOnly);
234
+ $buildProgress->set_failed($error_message);
235
+ return true;
236
+ } elseif (!$expandState->working) {
237
+ $buildProgress->archive_built = true;
238
+ $buildProgress->retries = 0;
239
+ $package->update();
240
+ $timerAllEnd = DUP_Util::getMicrotime();
241
+ $timerAllSum = DUP_Util::elapsedTime($timerAllEnd, $package->TimerStart);
242
+ DUP_LOG::traceObject("create state", $createState);
243
+ $archiveFileSize = @filesize($archivePath);
244
+ DUP_Log::info("COMPRESSED SIZE: " . DUP_Util::byteSize($archiveFileSize));
245
+ DUP_Log::info("ARCHIVE RUNTIME: {$timerAllSum}");
246
+ DUP_Log::info("MEMORY STACK: " . DUP_Server::getPHPMemory());
247
+ DUP_Log::info("CREATE WARNINGS: " . $createState->getFailureSummary(false, true));
248
+ DUP_Log::info("VALIDATION WARNINGS: " . $expandState->getFailureSummary(false, true));
249
+ $archive->file_count = $expandState->fileWriteCount + $expandState->directoryWriteCount;
250
+ $package->update();
251
+ $done = true;
252
+ } else {
253
+ $expandState->save();
254
+ }
255
+ }
256
+ }
257
+ } catch (Exception $ex) {
258
+ // Have to have a catchall since the main system that calls this function is not prepared to handle exceptions
259
+ DUP_Log::trace('Top level create Exception:' . $ex->getMessage() . ':' . $ex->getTraceAsString());
260
+ //$buildProgress->failed = true;
261
+ $buildProgress->set_failed('Error encoundtered creating archive. See package log');
262
+ return true;
263
+ }
264
+
265
+ $buildProgress->retries = 0;
266
+ return $done;
267
+ }
268
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classes/package/duparchive/class.pack.archive.duparchive.state.create.php CHANGED
@@ -1,80 +1,66 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- /*
4
- * To change this license header, choose License Headers in Project Properties.
5
- * To change this template file, choose Tools | Templates
6
- * and open the template in the editor.
7
- */
8
-
9
- require_once (DUPLICATOR_PLUGIN_PATH.'lib/dup_archive/classes/states/class.duparchive.state.create.php');
10
- require_once (DUPLICATOR_PLUGIN_PATH.'lib/dup_archive/classes/class.duparchive.processing.failure.php');
11
-
12
- class DUP_DupArchive_Create_State extends DupArchiveCreateState
13
- {
14
- /* @var $package DUP_Package */
15
- // private $package;
16
-
17
- // public function setPackage(&$package)
18
- public function setPackage(&$package)
19
- {
20
- // $this->package = &$package;
21
- }
22
-
23
- // Only one active package so straightforward
24
- // public static function createFromPackage(&$package)
25
- public static function get_instance()
26
- {
27
- $instance = new DUP_DupArchive_Create_State();
28
-
29
- $data = DUP_Settings::Get('duparchive_create_state');
30
-
31
- DUP_Util::objectCopy($data, $instance);
32
-
33
- $instance->startTimestamp = time();
34
-
35
- DUP_Log::TraceObject("retrieving create state", $instance);
36
-
37
- return $instance;
38
- }
39
-
40
- public static function createNew($archivePath, $basePath, $timeSliceInSecs, $isCompressed, $setArchiveOffsetToEndOfArchive)
41
- {
42
- $instance = new DUP_DupArchive_Create_State();
43
-
44
- if ($setArchiveOffsetToEndOfArchive) {
45
- $instance->archiveOffset = filesize($archivePath);
46
- } else {
47
- $instance->archiveOffset = 0;
48
- }
49
-
50
- $instance->archivePath = $archivePath;
51
- $instance->basePath = $basePath;
52
- $instance->currentDirectoryIndex = 0;
53
- $instance->currentFileOffset = 0;
54
- $instance->currentFileIndex = 0;
55
- $instance->failures = array();
56
- $instance->globSize = DupArchiveCreateState::DEFAULT_GLOB_SIZE;
57
- $instance->isCompressed = $isCompressed;
58
- $instance->timeSliceInSecs = $timeSliceInSecs;
59
- $instance->working = true;
60
- $instance->skippedDirectoryCount = 0;
61
- $instance->skippedFileCount = 0;
62
-
63
- $instance->startTimestamp = time();
64
-
65
- return $instance;
66
- }
67
-
68
- public function addFailure($type, $subject, $description, $isCritical = false)
69
- {
70
- parent::addFailure($type, $subject, $description, $isCritical);
71
- }
72
-
73
- public function save()
74
- {
75
- DUP_Log::TraceObject("Saving create state", $this);
76
- DUP_Settings::Set('duparchive_create_state', $this);
77
-
78
- DUP_Settings::Save();
79
- }
80
- }
1
+ <?php
2
+
3
+ use Duplicator\Libs\DupArchive\States\DupArchiveCreateState;
4
+
5
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
6
+
7
+ class DUP_DupArchive_Create_State extends DupArchiveCreateState
8
+ {
9
+ /**
10
+ * Class constructor
11
+ */
12
+ public function __construct()
13
+ {
14
+ parent::__construct();
15
+ $this->throttleDelayInUs = 10;
16
+ }
17
+
18
+ // Only one active package so straightforward
19
+ // public static function createFromPackage(&$package)
20
+ public static function get_instance()
21
+ {
22
+ $instance = new DUP_DupArchive_Create_State();
23
+ $data = DUP_Settings::Get('duparchive_create_state');
24
+ DUP_Util::objectCopy($data, $instance);
25
+ $instance->startTimestamp = time();
26
+ DUP_Log::TraceObject("retrieving create state", $instance);
27
+ return $instance;
28
+ }
29
+
30
+ public static function createNew($archivePath, $basePath, $timeSliceInSecs, $isCompressed, $setArchiveOffsetToEndOfArchive)
31
+ {
32
+ $instance = new DUP_DupArchive_Create_State();
33
+ if ($setArchiveOffsetToEndOfArchive) {
34
+ $instance->archiveOffset = filesize($archivePath);
35
+ } else {
36
+ $instance->archiveOffset = 0;
37
+ }
38
+
39
+ $instance->archivePath = $archivePath;
40
+ $instance->basePath = $basePath;
41
+ $instance->currentDirectoryIndex = 0;
42
+ $instance->currentFileOffset = 0;
43
+ $instance->currentFileIndex = 0;
44
+ $instance->failures = array();
45
+ $instance->globSize = DupArchiveCreateState::DEFAULT_GLOB_SIZE;
46
+ $instance->isCompressed = $isCompressed;
47
+ $instance->timeSliceInSecs = $timeSliceInSecs;
48
+ $instance->working = true;
49
+ $instance->skippedDirectoryCount = 0;
50
+ $instance->skippedFileCount = 0;
51
+ $instance->startTimestamp = time();
52
+ return $instance;
53
+ }
54
+
55
+ public function addFailure($type, $subject, $description, $isCritical = false)
56
+ {
57
+ parent::addFailure($type, $subject, $description, $isCritical);
58
+ }
59
+
60
+ public function save()
61
+ {
62
+ DUP_Log::TraceObject("Saving create state", $this);
63
+ DUP_Settings::Set('duparchive_create_state', $this);
64
+ DUP_Settings::Save();
65
+ }
66
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classes/package/duparchive/class.pack.archive.duparchive.state.expand.php CHANGED
@@ -1,133 +1,88 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- /*
4
- * To change this license header, choose License Headers in Project Properties.
5
- * To change this template file, choose Tools | Templates
6
- * and open the template in the editor.
7
- */
8
-
9
- require_once (DUPLICATOR_PLUGIN_PATH.'lib/dup_archive/classes/states/class.duparchive.state.expand.php');
10
-
11
- class DUP_DupArchive_Expand_State extends DupArchiveExpandState
12
- {
13
- public static function getInstance($reset = false)
14
- {
15
- $instance = new DUP_DupArchive_Expand_State();
16
-
17
- if ($reset) {
18
- $instance->initMembers();
19
- } else {
20
- $instance->loadMembers();
21
- }
22
-
23
- return $instance;
24
- }
25
-
26
- private function loadMembers()
27
- {
28
- $data = DUP_Settings::Get('duparchive_expand_state');
29
-
30
- DUP_LOG::traceObject("****RAW EXPAND STATE LOADED****", $data);
31
-
32
- if($data->currentFileHeaderString != null) {
33
- $this->currentFileHeader = DUP_JSON::decode($data->currentFileHeaderString);
34
- } else {
35
- $this->currentFileHeader = null;
36
- }
37
-
38
- if($data->archiveHeaderString != null) {
39
- $this->archiveHeader = DUP_JSON::decode($data->archiveHeaderString);
40
- } else {
41
- $this->archiveHeader = null;
42
- }
43
-
44
- if ($data->failuresString) {
45
- $this->failures = DUP_JSON::decode($data->failuresString);
46
- } else {
47
- $this->failures = array();
48
- }
49
-
50
- DUP_Util::objectCopy($data, $this, array('archiveHeaderString', 'currentFileHeaderString', 'failuresString'));
51
-
52
- //
53
- // $this->archiveOffset = $data->archiveOffset;
54
- // $this->archivePath = $data->archivePath;
55
- // $this->basePath = $data->basePath;
56
- // $this->currentFileOffset = $data->currentFileOffset;
57
- // $this->failures = $data->failures;
58
- // $this->isCompressed = $data->isCompressed;
59
- // $this->startTimestamp = $data->startTimestamp;
60
- // $this->timeSliceInSecs = $data->timeSliceInSecs;
61
- // $this->fileWriteCount = $data->fileWriteCount;
62
- // $this->directoryWriteCount = $data->directoryWriteCount;
63
- // $this->working = $data->working;
64
- // $this->directoryModeOverride = $data->directoryModeOverride;
65
- // $this->fileModeOverride = $data->fileModeOverride;
66
- // $this->throttleDelayInUs = $data->throttleDelayInUs;
67
- // $this->validateOnly = $data->validateOnly;
68
- // $this->validationType = $data->validationType;
69
- }
70
-
71
- public function save()
72
- {
73
- $data = new stdClass();
74
-
75
- if($this->currentFileHeader != null) {
76
- $data->currentFileHeaderString = DupLiteSnapJsonU::wp_json_encode($this->currentFileHeader);
77
- } else {
78
- $data->currentFileHeaderString = null;
79
- }
80
-
81
- if($this->archiveHeader != null) {
82
- $data->archiveHeaderString = DupLiteSnapJsonU::wp_json_encode($this->archiveHeader);
83
- } else {
84
- $data->archiveHeaderString = null;
85
- }
86
-
87
- $data->failuresString = DupLiteSnapJsonU::wp_json_encode($this->failures);
88
-
89
- // Object members auto skipped
90
- DUP_Util::objectCopy($this, $data);
91
-
92
- // $data->archiveOffset = $this->archiveOffset;
93
- // $data->archivePath = $this->archivePath;
94
- // $data->basePath = $this->basePath;
95
- // $data->currentFileOffset = $this->currentFileOffset;
96
- // $data->failures = $this->failures;
97
- // $data->isCompressed = $this->isCompressed;
98
- // $data->startTimestamp = $this->startTimestamp;
99
- // $data->timeSliceInSecs = $this->timeSliceInSecs;
100
- // $data->fileWriteCount = $this->fileWriteCount;
101
- // $data->directoryWriteCount = $this->directoryWriteCount;
102
- // $data->working = $this->working;
103
- // $data->directoryModeOverride = $this->directoryModeOverride;
104
- // $data->fileModeOverride = $this->fileModeOverride;
105
- // $data->throttleDelayInUs = $this->throttleDelayInUs;
106
- // $data->validateOnly = $this->validateOnly;
107
- // $data->validationType = $this->validationType;
108
-
109
- DUP_LOG::traceObject("****SAVING EXPAND STATE****", $this);
110
- DUP_LOG::traceObject("****SERIALIZED STATE****", $data);
111
- DUP_Settings::Set('duparchive_expand_state', $data);
112
- DUP_Settings::Save();
113
- }
114
-
115
- private function initMembers()
116
- {
117
- $this->currentFileHeader = null;
118
- $this->archiveOffset = 0;
119
- $this->archiveHeader = null;
120
- $this->archivePath = null;
121
- $this->basePath = null;
122
- $this->currentFileOffset = 0;
123
- $this->failures = array();
124
- $this->isCompressed = false;
125
- $this->startTimestamp = time();
126
- $this->timeSliceInSecs = -1;
127
- $this->working = false;
128
- $this->validateOnly = false;
129
- $this->directoryModeOverride = -1;
130
- $this->fileModeOverride = -1;
131
- $this->throttleDelayInUs = 0;
132
- }
133
- }
1
+ <?php
2
+
3
+ use Duplicator\Libs\DupArchive\States\DupArchiveExpandState;
4
+ use Duplicator\Libs\Snap\SnapJson;
5
+
6
+ class DUP_DupArchive_Expand_State extends DupArchiveExpandState
7
+ {
8
+ public static function getInstance($reset = false)
9
+ {
10
+ $instance = new DUP_DupArchive_Expand_State();
11
+ if ($reset) {
12
+ $instance->initMembers();
13
+ } else {
14
+ $instance->loadMembers();
15
+ }
16
+
17
+ return $instance;
18
+ }
19
+
20
+ private function loadMembers()
21
+ {
22
+ /** @var object $data */
23
+ $data = DUP_Settings::Get('duparchive_expand_state');
24
+ DUP_LOG::traceObject("****RAW EXPAND STATE LOADED****", $data);
25
+ if ($data->currentFileHeaderString != null) {
26
+ $this->currentFileHeader = DUP_JSON::decode($data->currentFileHeaderString);
27
+ } else {
28
+ $this->currentFileHeader = null;
29
+ }
30
+
31
+ if ($data->archiveHeaderString != null) {
32
+ $this->archiveHeader = DUP_JSON::decode($data->archiveHeaderString);
33
+ } else {
34
+ $this->archiveHeader = null;
35
+ }
36
+
37
+ if ($data->failuresString) {
38
+ $this->failures = DUP_JSON::decode($data->failuresString);
39
+ } else {
40
+ $this->failures = array();
41
+ }
42
+
43
+ DUP_Util::objectCopy($data, $this, array('archiveHeaderString', 'currentFileHeaderString', 'failuresString'));
44
+ }
45
+
46
+ public function save()
47
+ {
48
+ $data = new stdClass();
49
+ if ($this->currentFileHeader != null) {
50
+ $data->currentFileHeaderString = SnapJson::jsonEncode($this->currentFileHeader);
51
+ } else {
52
+ $data->currentFileHeaderString = null;
53
+ }
54
+
55
+ if ($this->archiveHeader != null) {
56
+ $data->archiveHeaderString = SnapJson::jsonEncode($this->archiveHeader);
57
+ } else {
58
+ $data->archiveHeaderString = null;
59
+ }
60
+
61
+ $data->failuresString = SnapJson::jsonEncode($this->failures);
62
+ // Object members auto skipped
63
+ DUP_Util::objectCopy($this, $data);
64
+ DUP_LOG::traceObject("****SAVING EXPAND STATE****", $this);
65
+ DUP_LOG::traceObject("****SERIALIZED STATE****", $data);
66
+ DUP_Settings::Set('duparchive_expand_state', $data);
67
+ DUP_Settings::Save();
68
+ }
69
+
70
+ private function initMembers()
71
+ {
72
+ $this->currentFileHeader = null;
73
+ $this->archiveOffset = 0;
74
+ $this->archiveHeader = null;
75
+ $this->archivePath = null;
76
+ $this->basePath = null;
77
+ $this->currentFileOffset = 0;
78
+ $this->failures = array();
79
+ $this->isCompressed = false;
80
+ $this->startTimestamp = time();
81
+ $this->timeSliceInSecs = -1;
82
+ $this->working = false;
83
+ $this->validateOnly = false;
84
+ $this->directoryModeOverride = -1;
85
+ $this->fileModeOverride = -1;
86
+ $this->throttleDelayInUs = 0;
87
+ }
88
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classes/package/duparchive/index.php CHANGED
@@ -1,2 +1,3 @@
1
  <?php
2
- //silent
 
1
  <?php
2
+
3
+ //silent
classes/package/index.php CHANGED
@@ -1,2 +1,3 @@
1
  <?php
2
- //silent
 
1
  <?php
2
+
3
+ //silent
classes/ui/class.ui.dialog.php CHANGED
@@ -1,235 +1,219 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- /**
4
- * Used to generate a thick-box inline dialog such as an alert or confirm pop-up
5
- *
6
- * Standard: PSR-2
7
- * @link http://www.php-fig.org/psr/psr-2
8
- *
9
- * @package Duplicator
10
- * @subpackage classes/ui
11
- * @copyright (c) 2017, Snapcreek LLC
12
- *
13
- */
14
-
15
- // Exit if accessed directly
16
- if (! defined('DUPLICATOR_VERSION')) exit;
17
-
18
- class DUP_UI_Dialog
19
- {
20
- /**
21
- * The title that shows up in the dialog
22
- */
23
- public $title;
24
-
25
- /**
26
- * The message displayed in the body of the dialog
27
- */
28
- public $message;
29
-
30
- /**
31
- * The width of the dialog the default is used if not set
32
- * Alert = 475px (default) | Confirm = 500px (default)
33
- */
34
- public $width;
35
-
36
- /**
37
- * The height of the dialog the default is used if not set
38
- * Alert = 125px (default) | Confirm = 150px (default)
39
- */
40
- public $height;
41
-
42
- /**
43
- * When the progress meter is running show this text
44
- * Available only on confirm dialogs
45
- */
46
- public $progressText;
47
-
48
- /**
49
- * When true a progress meter will run until page is reloaded
50
- * Available only on confirm dialogs
51
- */
52
- public $progressOn = true;
53
-
54
- /**
55
- * The javascript call back method to call when the 'Yes' button is clicked
56
- * Available only on confirm dialogs
57
- */
58
- public $jscallback;
59
-
60
- /**
61
- *
62
- * @var string
63
- */
64
- public $okText;
65
-
66
- /**
67
- *
68
- * @var string
69
- */
70
- public $cancelText;
71
-
72
- /**
73
- * If true close dialog on confirm
74
- * @var bool
75
- */
76
- public $closeOnConfirm = false;
77
-
78
-
79
- /**
80
- * The id given to the full dialog
81
- */
82
- private $id;
83
-
84
- /**
85
- * A unique id that is added to all id elements
86
- */
87
- private $uniqid;
88
-
89
- /**
90
- * Init this object when created
91
- */
92
- public function __construct()
93
- {
94
- add_thickbox();
95
- $this->progressText = __('Processing please wait...', 'duplicator');
96
- $this->uniqid = substr(uniqid('',true),0,14) . rand();
97
- $this->id = 'dup-dlg-'.$this->uniqid;
98
- $this->okText = __('OK', 'duplicator');
99
- $this->cancelText = __('Cancel', 'duplicator');
100
- }
101
-
102
- /**
103
- * Gets the unique id that is assigned to each instance of a dialog
104
- *
105
- * @return int The unique ID of this dialog
106
- */
107
- public function getID()
108
- {
109
- return $this->id;
110
- }
111
-
112
- /**
113
- * Gets the unique id that is assigned to each instance of a dialogs message text
114
- *
115
- * @return int The unique ID of the message
116
- */
117
- public function getMessageID()
118
- {
119
- return "{$this->id}_message";
120
- }
121
-
122
- /**
123
- * Initialize the alert base html code used to display when needed
124
- *
125
- * @return string The html content used for the alert dialog
126
- */
127
- public function initAlert()
128
- {
129
- $onClickClose = '';
130
- if (!is_null($this->jscallback)) {
131
- $onClickClose .= $this->jscallback.';';
132
- }
133
- $onClickClose .= 'tb_remove();';
134
-
135
- if (strlen($this->okText) == 0) {
136
- $hideButton = "style='display:none'";
137
- }
138
-
139
- $html = '
140
- <div id="'.esc_attr($this->id).'" style="display:none">
141
- <div class="dup-dlg-alert-txt">
142
- '.$this->message.'
143
- <br/><br/>
144
- </div>
145
- <div class="dup-dlg-alert-btns">
146
- <input type="button" class="button button-large" value="'.esc_attr($this->okText).'" onclick="'.$onClickClose.'" '. $hideButton . '/>
147
- </div>
148
- </div>';
149
-
150
- echo $html;
151
- }
152
-
153
- /**
154
- * Shows the alert base JS code used to display when needed
155
- *
156
- * @return string The javascript content used for the alert dialog
157
- */
158
- public function showAlert()
159
- {
160
- $this->width = is_numeric($this->width) ? $this->width : 500;
161
- $this->height = is_numeric($this->height) ? $this->height : 175;
162
-
163
- $html = "tb_show('".esc_js($this->title)."', '#TB_inline?width=".esc_js($this->width)."&height=".esc_js($this->height)."&inlineId=".esc_js($this->id)."');\n" .
164
- "var styleData = jQuery('#TB_window').attr('style') + 'height: ".esc_js($this->height)."px !important';\n" .
165
- "jQuery('#TB_window').attr('style', styleData);";
166
-
167
- echo $html;
168
- }
169
-
170
- /**
171
- * Shows the confirm base JS code used to display when needed
172
- *
173
- * @return string The javascript content used for the confirm dialog
174
- */
175
- public function initConfirm()
176
- {
177
- $progress_data = '';
178
- $progress_func2 = '';
179
-
180
- $onClickConfirm = '';
181
- if (!is_null($this->jscallback)) {
182
- $onClickConfirm .= $this->jscallback.';';
183
- }
184
-
185
- //Enable the progress spinner
186
- if ($this->progressOn) {
187
- $progress_func1 = "__DUP_UI_Dialog_".$this->uniqid;
188
- $progress_func2 = ";{$progress_func1}(this)";
189
- $progress_data = "<div class='dup-dlg-confirm-progress'><i class='fas fa-circle-notch fa-spin fa-lg fa-fw'></i> ".esc_js($this->progressText)."</div>
190
- <script>
191
- function {$progress_func1}(obj)
192
- {
193
- jQuery(obj).parent().parent().find('.dup-dlg-confirm-progress').show();
194
- jQuery(obj).closest('.dup-dlg-confirm-btns').find('input').attr('disabled', 'true');
195
- }
196
- </script>";
197
- $onClickConfirm .= $progress_func2.';';
198
- }
199
-
200
- if ($this->closeOnConfirm) {
201
- $onClickConfirm .= 'tb_remove();';
202
- }
203
-
204
- $html =
205
- '<div id="'.esc_attr($this->id).'" style="display:none">
206
- <div class="dup-dlg-confirm-txt">
207
- <span id="'.esc_attr($this->id).'_message">'.esc_html($this->message).'</span>
208
- <br/><br/>
209
- '.$progress_data.'
210
- </div>
211
- <div class="dup-dlg-confirm-btns">
212
- <input type="button" class="button button-large" value="'.esc_attr($this->okText).'" onclick="'.$onClickConfirm.'" />
213
- <input type="button" class="button button-large" value="'.esc_attr($this->cancelText).'" onclick="tb_remove()" />
214
- </div>
215
- </div>';
216
-
217
- echo $html;
218
- }
219
-
220
- /**
221
- * Shows the confirm base JS code used to display when needed
222
- *
223
- * @return string The javascript content used for the confirm dialog
224
- */
225
- public function showConfirm()
226
- {
227
- $this->width = is_numeric($this->width) ? $this->width : 500;
228
- $this->height = is_numeric($this->height) ? $this->height : 225;
229
- $html = "tb_show('".esc_js($this->title)."', '#TB_inline?width=".esc_js($this->width)."&height=".esc_js($this->height)."&inlineId=".esc_js($this->id)."');\n" .
230
- "var styleData = jQuery('#TB_window').attr('style') + 'height: ".esc_js($this->height)."px !important';\n" .
231
- "jQuery('#TB_window').attr('style', styleData);";
232
-
233
- echo $html;
234
- }
235
- }
1
+ <?php
2
+
3
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
4
+ /**
5
+ * Used to generate a thick-box inline dialog such as an alert or confirm pop-up
6
+ *
7
+ * Standard: PSR-2
8
+ * @link http://www.php-fig.org/psr/psr-2
9
+ *
10
+ * @package Duplicator
11
+ * @subpackage classes/ui
12
+ * @copyright (c) 2017, Snapcreek LLC
13
+ *
14
+ */
15
+
16
+ // Exit if accessed directly
17
+ if (! defined('DUPLICATOR_VERSION')) {
18
+ exit;
19
+ }
20
+
21
+ class DUP_UI_Dialog
22
+ {
23
+ /**
24
+ * The title that shows up in the dialog
25
+ */
26
+ public $title;
27
+ /**
28
+ * The message displayed in the body of the dialog
29
+ */
30
+ public $message;
31
+ /**
32
+ * The width of the dialog the default is used if not set
33
+ * Alert = 475px (default) | Confirm = 500px (default)
34
+ */
35
+ public $width;
36
+ /**
37
+ * The height of the dialog the default is used if not set
38
+ * Alert = 125px (default) | Confirm = 150px (default)
39
+ */
40
+ public $height;
41
+ /**
42
+ * When the progress meter is running show this text
43
+ * Available only on confirm dialogs
44
+ */
45
+ public $progressText;
46
+ /**
47
+ * When true a progress meter will run until page is reloaded
48
+ * Available only on confirm dialogs
49
+ */
50
+ public $progressOn = true;
51
+ /**
52
+ * The javascript call back method to call when the 'Yes' button is clicked
53
+ * Available only on confirm dialogs
54
+ */
55
+ public $jscallback;
56
+ /**
57
+ *
58
+ * @var string
59
+ */
60
+ public $okText;
61
+ /**
62
+ *
63
+ * @var string
64
+ */
65
+ public $cancelText;
66
+ /**
67
+ * If true close dialog on confirm
68
+ * @var bool
69
+ */
70
+ public $closeOnConfirm = false;
71
+ /**
72
+ * The id given to the full dialog
73
+ */
74
+ private $id;
75
+ /**
76
+ * A unique id that is added to all id elements
77
+ */
78
+ private $uniqid;
79
+ /**
80
+ * Init this object when created
81
+ */
82
+ public function __construct()
83
+ {
84
+ add_thickbox();
85
+ $this->progressText = __('Processing please wait...', 'duplicator');
86
+ $this->uniqid = substr(uniqid('', true), 0, 14) . rand();
87
+ $this->id = 'dup-dlg-' . $this->uniqid;
88
+ $this->okText = __('OK', 'duplicator');
89
+ $this->cancelText = __('Cancel', 'duplicator');
90
+ }
91
+
92
+ /**
93
+ * Gets the unique id that is assigned to each instance of a dialog
94
+ *
95
+ * @return int The unique ID of this dialog
96
+ */
97
+ public function getID()
98
+ {
99
+ return $this->id;
100
+ }
101
+
102
+ /**
103
+ * Gets the unique id that is assigned to each instance of a dialogs message text
104
+ *
105
+ * @return int The unique ID of the message
106
+ */
107
+ public function getMessageID()
108
+ {
109
+ return "{$this->id}_message";
110
+ }
111
+
112
+ /**
113
+ * Initialize the alert base html code used to display when needed
114
+ *
115
+ * @return string The html content used for the alert dialog
116
+ */
117
+ public function initAlert()
118
+ {
119
+ $onClickClose = '';
120
+ if (!is_null($this->jscallback)) {
121
+ $onClickClose .= $this->jscallback . ';';
122
+ }
123
+ $onClickClose .= 'tb_remove();';
124
+ $hideButton = "";
125
+ if (strlen($this->okText) == 0) {
126
+ $hideButton = "style='display:none'";
127
+ }
128
+
129
+ $html = '
130
+ <div id="' . esc_attr($this->id) . '" style="display:none">
131
+ <div class="dup-dlg-alert-txt">
132
+ ' . $this->message . '
133
+ <br/><br/>
134
+ </div>
135
+ <div class="dup-dlg-alert-btns">
136
+ <input type="button" class="button button-large" value="' . esc_attr($this->okText) . '" onclick="' . $onClickClose . '" ' . $hideButton . '/>
137
+ </div>
138
+ </div>';
139
+ echo $html;
140
+ }
141
+
142
+ /**
143
+ * Shows the alert base JS code used to display when needed
144
+ *
145
+ * @return string The javascript content used for the alert dialog
146
+ */
147
+ public function showAlert()
148
+ {
149
+ $this->width = is_numeric($this->width) ? $this->width : 500;
150
+ $this->height = is_numeric($this->height) ? $this->height : 175;
151
+ $html = "tb_show('" . esc_js($this->title) . "', '#TB_inline?width=" . esc_js($this->width) . "&height=" . esc_js($this->height) . "&inlineId=" . esc_js($this->id) . "');\n" .
152
+ "var styleData = jQuery('#TB_window').attr('style') + 'height: " . esc_js($this->height) . "px !important';\n" .
153
+ "jQuery('#TB_window').attr('style', styleData);";
154
+ echo $html;
155
+ }
156
+
157
+ /**
158
+ * Shows the confirm base JS code used to display when needed
159
+ *
160
+ * @return string The javascript content used for the confirm dialog
161
+ */
162
+ public function initConfirm()
163
+ {
164
+ $progress_data = '';
165
+ $progress_func2 = '';
166
+ $onClickConfirm = '';
167
+ if (!is_null($this->jscallback)) {
168
+ $onClickConfirm .= $this->jscallback . ';';
169
+ }
170
+
171
+ //Enable the progress spinner
172
+ if ($this->progressOn) {
173
+ $progress_func1 = "__DUP_UI_Dialog_" . $this->uniqid;
174
+ $progress_func2 = ";{$progress_func1}(this)";
175
+ $progress_data = "<div class='dup-dlg-confirm-progress'><i class='fas fa-circle-notch fa-spin fa-lg fa-fw'></i> " . esc_js($this->progressText) . "</div>
176
+ <script>
177
+ function {$progress_func1}(obj)
178
+ {
179
+ jQuery(obj).parent().parent().find('.dup-dlg-confirm-progress').show();
180
+ jQuery(obj).closest('.dup-dlg-confirm-btns').find('input').attr('disabled', 'true');
181
+ }
182
+ </script>";
183
+ $onClickConfirm .= $progress_func2 . ';';
184
+ }
185
+
186
+ if ($this->closeOnConfirm) {
187
+ $onClickConfirm .= 'tb_remove();';
188
+ }
189
+
190
+ $html =
191
+ '<div id="' . esc_attr($this->id) . '" style="display:none">
192
+ <div class="dup-dlg-confirm-txt">
193
+ <span id="' . esc_attr($this->id) . '_message">' . esc_html($this->message) . '</span>
194
+ <br/><br/>
195
+ ' . $progress_data . '
196
+ </div>
197
+ <div class="dup-dlg-confirm-btns">
198
+ <input type="button" class="button button-large" value="' . esc_attr($this->okText) . '" onclick="' . $onClickConfirm . '" />
199
+ <input type="button" class="button button-large" value="' . esc_attr($this->cancelText) . '" onclick="tb_remove()" />
200
+ </div>
201
+ </div>';
202
+ echo $html;
203
+ }
204
+
205
+ /**
206
+ * Shows the confirm base JS code used to display when needed
207
+ *
208
+ * @return string The javascript content used for the confirm dialog
209
+ */
210
+ public function showConfirm()
211
+ {
212
+ $this->width = is_numeric($this->width) ? $this->width : 500;
213
+ $this->height = is_numeric($this->height) ? $this->height : 225;
214
+ $html = "tb_show('" . esc_js($this->title) . "', '#TB_inline?width=" . esc_js($this->width) . "&height=" . esc_js($this->height) . "&inlineId=" . esc_js($this->id) . "');\n" .
215
+ "var styleData = jQuery('#TB_window').attr('style') + 'height: " . esc_js($this->height) . "px !important';\n" .
216
+ "jQuery('#TB_window').attr('style', styleData);";
217
+ echo $html;
218
+ }
219
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classes/ui/class.ui.messages.php CHANGED
@@ -1,117 +1,111 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
-
4
- /**
5
- * Used to generate a thick box inline dialog such as an alert or confirm pop-up
6
- *
7
- *
8
- * Standard: PSR-2
9
- * @link http://www.php-fig.org/psr/psr-2
10
- *
11
- * @package Duplicator
12
- * @subpackage classes/ui
13
- * @copyright (c) 2017, Snapcreek LLC
14
- */
15
-
16
- class DUP_UI_Messages
17
- {
18
- const UNIQUE_ID_PREFIX = 'dup_ui_msg_';
19
- const NOTICE = 'updated';
20
- const WARNING = 'update-nag';
21
- const ERROR = 'error';
22
-
23
- private static $unique_id = 0;
24
- private $id;
25
- public $type = self::NOTICE;
26
- public $content = '';
27
- public $wrap_cont_tag = 'p';
28
- public $hide_on_init = true;
29
- public $is_dismissible = false;
30
- /**
31
- *
32
- * @var int delay in milliseconds
33
- */
34
- public $auto_hide_delay = 0;
35
- public $callback_on_show = null;
36
- public $callback_on_hide = null;
37
-
38
- public function __construct($content = '', $type = self::NOTICE)
39
- {
40
- self::$unique_id ++;
41
- $this->id = self::UNIQUE_ID_PREFIX.self::$unique_id;
42
-
43
- $this->content = (string) $content;
44
- $this->type = $type;
45
- }
46
-
47
- protected function get_notice_classes($classes = array())
48
- {
49
- if (is_string($classes)) {
50
- $classes = explode(' ', $classes);
51
- } else if (is_array($classes)) {
52
-
53
- } else {
54
- $classes = array();
55
- }
56
-
57
- if ($this->is_dismissible) {
58
- $classes[] = 'is-dismissible';
59
- }
60
-
61
- $result = array_merge(array('notice', $this->type), $classes);
62
- return trim(implode(' ', $result));
63
- }
64
-
65
- public function initMessage()
66
- {
67
- $classes = array();
68
- if ($this->hide_on_init) {
69
- $classes[] = 'no_display';
70
- }
71
-
72
- $this->wrap_tag = empty($this->wrap_tag) ? 'p' : $this->wrap_tag;
73
- echo '<div id="'.$this->id.'" class="'.$this->get_notice_classes($classes).'">'.
74
- '<'.$this->wrap_cont_tag.' class="msg-content">'.
75
- $this->content.
76
- '</'.$this->wrap_cont_tag.'>'.
77
- '</div>';
78
- }
79
-
80
- public function updateMessage($jsVarName, $echo = true)
81
- {
82
- $result = 'jQuery("#'.$this->id.' > .msg-content").html('.$jsVarName.');';
83
-
84
- if ($echo) {
85
- echo $result;
86
- } else {
87
- return $result;
88
- }
89
- }
90
-
91
- public function showMessage($echo = true)
92
- {
93
- $callStr = !empty($this->callback_on_show) ? $this->callback_on_show.';' : '';
94
- $result = 'jQuery("#'.$this->id.'").fadeIn( "slow", function() { $(this).removeClass("no_display");'.$callStr.' });';
95
- if ($this->auto_hide_delay > 0) {
96
- $result .= 'setTimeout(function () { '.$this->hideMessage(false).' }, '.$this->auto_hide_delay.');';
97
- }
98
-
99
- if ($echo) {
100
- echo $result;
101
- } else {
102
- return $result;
103
- }
104
- }
105
-
106
- public function hideMessage($echo = true)
107
- {
108
- $callStr = !empty($this->callback_on_hide) ? $this->callback_on_hide.';' : '';
109
- $result = 'jQuery("#'.$this->id.'").fadeOut( "slow", function() { $(this).addClass("no_display");'.$callStr.' });';
110
-
111
- if ($echo) {
112
- echo $result;
113
- } else {
114
- return $result;
115
- }
116
- }
117
- }
1
+ <?php
2
+
3
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
4
+ /**
5
+ * Used to generate a thick box inline dialog such as an alert or confirm pop-up
6
+ *
7
+ *
8
+ * Standard: PSR-2
9
+ * @link http://www.php-fig.org/psr/psr-2
10
+ *
11
+ * @package Duplicator
12
+ * @subpackage classes/ui
13
+ * @copyright (c) 2017, Snapcreek LLC
14
+ */
15
+
16
+ class DUP_UI_Messages
17
+ {
18
+ const UNIQUE_ID_PREFIX = 'dup_ui_msg_';
19
+ const NOTICE = 'updated';
20
+ const WARNING = 'update-nag';
21
+ const ERROR = 'error';
22
+ private static $unique_id = 0;
23
+ private $id;
24
+ public $type = self::NOTICE;
25
+ public $content = '';
26
+ public $wrap_cont_tag = 'p';
27
+ public $hide_on_init = true;
28
+ public $is_dismissible = false;
29
+ /**
30
+ *
31
+ * @var int delay in milliseconds
32
+ */
33
+ public $auto_hide_delay = 0;
34
+ public $callback_on_show = null;
35
+ public $callback_on_hide = null;
36
+ public function __construct($content = '', $type = self::NOTICE)
37
+ {
38
+ self::$unique_id++;
39
+ $this->id = self::UNIQUE_ID_PREFIX . self::$unique_id;
40
+ $this->content = (string) $content;
41
+ $this->type = $type;
42
+ }
43
+
44
+ protected function get_notice_classes($classes = array())
45
+ {
46
+ if (is_string($classes)) {
47
+ $classes = explode(' ', $classes);
48
+ } elseif (is_array($classes)) {
49
+ } else {
50
+ $classes = array();
51
+ }
52
+
53
+ if ($this->is_dismissible) {
54
+ $classes[] = 'is-dismissible';
55
+ }
56
+
57
+ $result = array_merge(array('notice', $this->type), $classes);
58
+ return trim(implode(' ', $result));
59
+ }
60
+
61
+ public function initMessage()
62
+ {
63
+ $classes = array();
64
+ if ($this->hide_on_init) {
65
+ $classes[] = 'no_display';
66
+ }
67
+
68
+ $this->wrap_tag = empty($this->wrap_tag) ? 'p' : $this->wrap_tag;
69
+ echo '<div id="' . $this->id . '" class="' . $this->get_notice_classes($classes) . '">' .
70
+ '<' . $this->wrap_cont_tag . ' class="msg-content">' .
71
+ $this->content .
72
+ '</' . $this->wrap_cont_tag . '>' .
73
+ '</div>';
74
+ }
75
+
76
+ public function updateMessage($jsVarName, $echo = true)
77
+ {
78
+ $result = 'jQuery("#' . $this->id . ' > .msg-content").html(' . $jsVarName . ');';
79
+ if ($echo) {
80
+ echo $result;
81
+ } else {
82
+ return $result;
83
+ }
84
+ }
85
+
86
+ public function showMessage($echo = true)
87
+ {
88
+ $callStr = !empty($this->callback_on_show) ? $this->callback_on_show . ';' : '';
89
+ $result = 'jQuery("#' . $this->id . '").fadeIn( "slow", function() { $(this).removeClass("no_display");' . $callStr . ' });';
90
+ if ($this->auto_hide_delay > 0) {
91
+ $result .= 'setTimeout(function () { ' . $this->hideMessage(false) . ' }, ' . $this->auto_hide_delay . ');';
92
+ }
93
+
94
+ if ($echo) {
95
+ echo $result;
96
+ } else {
97
+ return $result;
98
+ }
99
+ }
100
+
101
+ public function hideMessage($echo = true)
102
+ {
103
+ $callStr = !empty($this->callback_on_hide) ? $this->callback_on_hide . ';' : '';
104
+ $result = 'jQuery("#' . $this->id . '").fadeOut( "slow", function() { $(this).addClass("no_display");' . $callStr . ' });';
105
+ if ($echo) {
106
+ echo $result;
107
+ } else {
108
+ return $result;
109
+ }
110
+ }
111
+ }
 
 
 
 
 
 
classes/ui/class.ui.notice.php CHANGED
@@ -1,4 +1,8 @@
1
  <?php
 
 
 
 
2
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
  /**
4
  * Used to display notices in the WordPress Admin area
@@ -15,13 +19,13 @@ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
15
 
16
  class DUP_UI_Notice
17
  {
18
-
19
- const OPTION_KEY_ACTIVATE_PLUGINS_AFTER_INSTALL = 'duplicator_reactivate_plugins_after_installation';
20
 
21
  //TEMPLATE VALUE: This is a just a simple example for setting up quick notices
22
- const OPTION_KEY_NEW_NOTICE_TEMPLATE = 'duplicator_new_template_notice';
23
- const OPTION_KEY_IS_PRO_ENABLE_NOTICE_DISMISSED = 'duplicator_is_pro_enable_notice_dismissed';
24
- const OPTION_KEY_IS_MU_NOTICE_DISMISSED = 'duplicator_is_mu_notice_dismissed';
25
 
26
  const GEN_INFO_NOTICE = 0;
27
  const GEN_SUCCESS_NOTICE = 1;
@@ -33,49 +37,83 @@ class DUP_UI_Notice
33
  */
34
  public static function init()
35
  {
36
- $methods = array(
37
- 'showReservedFilesNotice',
38
- 'installAutoDeactivatePlugins',
39
- 'showFeedBackNotice',
40
- 'showNoExportCapabilityNotice',
41
- //FUTURE NOTICE TEMPLATE
42
- //'newNotice_TEMPLATE',
43
- );
44
- foreach ($methods as $method) {
45
- add_action('admin_notices', array(__CLASS__, $method));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  }
47
  }
48
 
49
- /**
50
- * NOTICE SAMPLE: This method serves as a quick example for how to quickly setup a new notice
51
- * Please do not edit this method, but simply use to copy a new setup.
 
52
  */
53
- public static function newNotice_TEMPLATE()
54
  {
55
- if (get_option(self::OPTION_KEY_NEW_NOTICE_TEMPLATE) != true) {
 
56
  return;
57
  }
58
 
59
- $screen = get_current_screen();
60
- if (!in_array($screen->parent_base, array('plugins', 'duplicator'))) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  return;
62
  }
63
 
64
- ?>
65
- <div class="notice notice-success duplicator-admin-notice is-dismissible" data-to-dismiss="<?php echo esc_attr(self::OPTION_KEY_NEW_NOTICE_TEMPLATE); ?>" >
66
- <p>
67
- <?php esc_html_e('NOTICE: This is a sample message notice demo.', 'duplicator'); ?><br>
68
- <?php
69
- echo sprintf(__('Example for passing dynamic data to notice message <i>%s</i> to <i>%s</i>', 'duplicator'),
70
- esc_html("test 1"),
71
- esc_html(time()));
72
- ?>
73
- </p>
74
- <p>
75
- <?php echo sprintf(__('More info here: Goto <a href="%s">General Settings</a>', 'duplicator'), 'admin.php?page=duplicator-settings'); ?>
76
- </p>
77
- </div>
78
- <?php
79
  }
80
 
81
  /**
@@ -88,16 +126,18 @@ class DUP_UI_Notice
88
  //Show only on Duplicator pages and Dashboard when plugin is active
89
  $dup_active = is_plugin_active('duplicator/duplicator.php');
90
  $dup_perm = current_user_can('manage_options');
91
- if (!$dup_active || !$dup_perm)
92
  return;
 
93
 
94
  $screen = get_current_screen();
95
- if (!isset($screen))
96
  return;
 
97
 
98
  $is_installer_cleanup_req = ($screen->id == 'duplicator_page_duplicator-tools' && isset($_GET['action']) && $_GET['action'] == 'installer');
99
  if (DUP_Server::hasInstallerFiles() && !$is_installer_cleanup_req) {
100
- DUP_Migration::renameInstallersPhpFiles();
101
 
102
  $on_active_tab = isset($_GET['section']) ? $_GET['section'] : '';
103
  echo '<div class="dup-updated notice notice-success dup-global-error-reserved-files" id="message"><p>';
@@ -113,26 +153,24 @@ class DUP_UI_Notice
113
 
114
  //On Tools > Cleanup Page
115
  if ($screen->id == 'duplicator_page_duplicator-tools' && ($on_active_tab == "info" || $on_active_tab == '')) {
116
-
117
  $title = __('This site has been successfully migrated!', 'duplicator');
118
  $msg1 = __('Final step(s):', 'duplicator');
119
  $msg2 = __('This message will be removed after all installer files are removed. Installer files must be removed to maintain a secure site. '
120
- .'Click the link above or button below to remove all installer files and complete the migration.', 'duplicator');
121
 
122
- echo "<b class='pass-msg'><i class='fa fa-check-circle'></i> ".esc_html($title)."</b> <br/> {$safe_html} <b>".esc_html($msg1)."</b> <br/>";
123
  printf("1. <a href='javascript:void(0)' onclick='jQuery(\"#dup-remove-installer-files-btn\").click()'>%s</a><br/>", esc_html__('Remove Installation Files Now!', 'duplicator'));
124
  printf("2. <a href='https://wordpress.org/support/plugin/duplicator/reviews/?filter=5' target='wporg'>%s</a> <br/> ", esc_html__('Optionally, Review Duplicator at WordPress.org...', 'duplicator'));
125
- echo "<div class='pass-msg'>".esc_html($msg2)."</div>";
126
 
127
  //All other Pages
128
  } else {
129
-
130
  $title = __('Migration Almost Complete!', 'duplicator');
131
  $msg = __('Reserved Duplicator installation files have been detected in the root directory. Please delete these installation files to '
132
- .'avoid security issues. <br/> Go to:Duplicator > Tools > Information >Stored Data and click the "Remove Installation Files" button', 'duplicator');
133
 
134
  $nonce = wp_create_nonce('duplicator_cleanup_page');
135
- $url = self_admin_url('admin.php?page=duplicator-tools&tab=diagnostics&section=info&_wpnonce='.$nonce);
136
  echo "<b>{$title}</b><br/> {$safe_html} {$msg}";
137
  @printf("<br/><a href='{$url}'>%s</a>", __('Take me there now!', 'duplicator'));
138
  }
@@ -160,40 +198,56 @@ class DUP_UI_Notice
160
  public static function installAutoDeactivatePlugins()
161
  {
162
  $reactivatePluginsAfterInstallation = get_option(self::OPTION_KEY_ACTIVATE_PLUGINS_AFTER_INSTALL, false);
163
- if (is_array($reactivatePluginsAfterInstallation)) {
164
- $installedPlugins = array_keys(get_plugins());
165
- $shouldBeActivated = array();
166
- foreach ($reactivatePluginsAfterInstallation as $pluginSlug => $pluginTitle) {
167
- if (in_array($pluginSlug, $installedPlugins) && !is_plugin_active($pluginSlug)) {
168
- $shouldBeActivated[$pluginSlug] = $pluginTitle;
169
- }
 
 
 
 
 
170
  }
171
 
172
- if (empty($shouldBeActivated)) {
173
- delete_option(self::OPTION_KEY_ACTIVATE_PLUGINS_AFTER_INSTALL);
 
 
174
  } else {
175
- $activatePluginsAnchors = array();
176
- foreach ($shouldBeActivated as $slug => $title) {
177
- $activateURL = wp_nonce_url(admin_url('plugins.php?action=activate&plugin='.$slug), 'activate-plugin_'.$slug);
178
- $anchorTitle = sprintf(esc_html__('Activate %s', 'duplicator'), $title);
179
- $activatePluginsAnchors[] = '<a href="'.$activateURL.'"
180
- title="'.esc_attr($anchorTitle).'">'.
181
- $title.'</a>';
182
- }
183
- ?>
184
- <div class="update-nag duplicator-plugin-activation-admin-notice notice notice-warning duplicator-admin-notice is-dismissible"
185
- data-to-dismiss="<?php echo esc_attr(self::OPTION_KEY_ACTIVATE_PLUGINS_AFTER_INSTALL); ?>" >
186
- <p>
187
- <?php
188
- echo "<b>".esc_html__("Warning!", "duplicator")."</b> ".esc_html__("Migration Almost Complete!", "duplicator")." <br/>";
189
- echo esc_html__("Plugin(s) listed here have been deactivated during installation to help prevent issues. Please activate them to finish this migration: ", "duplicator")."<br/>";
190
- echo implode(' ,', $activatePluginsAnchors);
191
- ?>
192
- </p>
193
- </div>
194
- <?php
195
  }
196
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  }
198
 
199
  /**
@@ -224,8 +278,8 @@ class DUP_UI_Notice
224
  }
225
 
226
  // not using DUP_Util::getTablePrefix() in place of $tablePrefix because DUP_UI_Notice included initially (Duplicator\Lite\Requirement is depended on the DUP_UI_Notice)
227
- $tablePrefix = (is_multisite() && is_plugin_active_for_network('duplicator/duplicator.php')) ? $GLOBALS['wpdb']->base_prefix : $GLOBALS['wpdb']->prefix;
228
- $packagesCount = $GLOBALS['wpdb']->get_var('SELECT count(id) FROM '.$tablePrefix.'duplicator_packages WHERE status=100');
229
 
230
  if ($packagesCount < DUPLICATOR_FEEDBACK_NOTICE_SHOW_AFTER_NO_PACKAGE) {
231
  return;
@@ -245,7 +299,7 @@ class DUP_UI_Notice
245
  <div class="notice updated duplicator-message duplicator-message-dismissed" data-notice_id="<?php echo esc_attr($notice_id); ?>">
246
  <div class="duplicator-message-inner">
247
  <div class="duplicator-message-icon">
248
- <img src="<?php echo esc_url(DUPLICATOR_PLUGIN_URL."assets/img/logo.png"); ?>" style="text-align:top; margin:0; height:60px; width:60px;" alt="Duplicator">
249
  </div>
250
  <div class="duplicator-message-content">
251
  <p><strong><?php echo __('Congrats!', 'duplicator'); ?></strong> <?php printf(esc_html__('You created over %d packages with Duplicator. Great job! If you can spare a minute, please help us by leaving a five star review on WordPress.org.', 'duplicator'), DUPLICATOR_FEEDBACK_NOTICE_SHOW_AFTER_NO_PACKAGE); ?></p>
@@ -267,8 +321,8 @@ class DUP_UI_Notice
267
  public static function showNoExportCapabilityNotice()
268
  {
269
  if (is_admin() && in_array('administrator', $GLOBALS['current_user']->roles) && !current_user_can('export')) {
270
- $errorMessage = __('<strong>Duplicator</strong><hr> Your logged-in user role does not have export capability so you don\'t have access to Duplicator functionality.', 'duplicator').
271
- "<br>".
272
  sprintf(__('<strong>RECOMMENDATION:</strong> Add export capability to your role. See FAQ: <a target="_blank" href="%s">%s</a>', 'duplicator'), 'https://snapcreek.com/duplicator/docs/faqs-tech/#faq-licensing-040-q', __('Why is the Duplicator/Packages menu missing from my admin menu?', 'duplicator'));
273
  DUP_UI_Notice::displayGeneralAdminNotice($errorMessage, self::GEN_ERROR_NOTICE, true);
274
  }
@@ -279,7 +333,7 @@ class DUP_UI_Notice
279
  *
280
  * @param string $htmlMsg html code to be printed
281
  * @param integer $noticeType constant value of SELF::GEN_
282
- * @param boolean $isDismissible whether the notice is dismissable or not. Default is true
283
  * @param array|string $extraClasses add more classes to the notice div
284
  * @return void
285
  */
1
  <?php
2
+
3
+ use Duplicator\Core\MigrationMng;
4
+ use Duplicator\Libs\Snap\SnapUtil;
5
+
6
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
7
  /**
8
  * Used to display notices in the WordPress Admin area
19
 
20
  class DUP_UI_Notice
21
  {
22
+ const OPTION_KEY_MIGRATION_SUCCESS_NOTICE = 'duplicator_migration_success';
23
+ const OPTION_KEY_ACTIVATE_PLUGINS_AFTER_INSTALL = 'duplicator_activate_plugins_after_installation';
24
 
25
  //TEMPLATE VALUE: This is a just a simple example for setting up quick notices
26
+ const OPTION_KEY_NEW_NOTICE_TEMPLATE = 'duplicator_new_template_notice';
27
+ const OPTION_KEY_IS_ENABLE_NOTICE_DISMISSED = 'duplicator_is_enable_notice_dismissed';
28
+ const OPTION_KEY_IS_MU_NOTICE_DISMISSED = 'duplicator_is_mu_notice_dismissed';
29
 
30
  const GEN_INFO_NOTICE = 0;
31
  const GEN_SUCCESS_NOTICE = 1;
37
  */
38
  public static function init()
39
  {
40
+ add_action('admin_init', array(__CLASS__, 'adminInit'));
41
+ }
42
+
43
+ public static function adminInit()
44
+ {
45
+ $notices = array();
46
+ if (is_multisite()) {
47
+ $noCapabilitiesNotice = is_super_admin() && !current_user_can('export');
48
+ } else {
49
+ $noCapabilitiesNotice = in_array('administrator', $GLOBALS['current_user']->roles) && !current_user_can('export');
50
+ }
51
+
52
+ if ($noCapabilitiesNotice) {
53
+ $notices[] = array(__CLASS__, 'showNoExportCapabilityNotice');
54
+ }
55
+
56
+ if (is_multisite()) {
57
+ $displayNotices = is_super_admin() && current_user_can('export');
58
+ } else {
59
+ $displayNotices = current_user_can('export');
60
+ }
61
+
62
+ if ($displayNotices) {
63
+ $notices[] = array(__CLASS__, 'clearInstallerFilesAction'); // BEFORE MIGRATION SUCCESS NOTICE
64
+ $notices[] = array(__CLASS__, 'migrationSuccessNotice');
65
+ $notices[] = array(__CLASS__, 'installAutoDeactivatePlugins');
66
+ }
67
+
68
+ $action = is_multisite() ? 'network_admin_notices' : 'admin_notices';
69
+ foreach ($notices as $notice) {
70
+ add_action($action, $notice);
71
  }
72
  }
73
 
74
+ /**
75
+ * Clear installer file action
76
+ *
77
+ * @return void
78
  */
79
+ public static function clearInstallerFilesAction()
80
  {
81
+
82
+ if (!DUP_CTRL_Tools::isDiagnosticPage() || get_option(self::OPTION_KEY_MIGRATION_SUCCESS_NOTICE) == true) {
83
  return;
84
  }
85
 
86
+
87
+ if (SnapUtil::filterInputRequest('action', FILTER_DEFAULT) === 'installer') {
88
+ if (! wp_verify_nonce($_REQUEST['_wpnonce'], 'duplicator_cleanup_page')) {
89
+ echo '<p>' . __('Security issue', 'duplicator') . '</p>';
90
+ exit; // Get out of here bad nounce!
91
+ }
92
+
93
+ ?>
94
+ <div id="message" class="notice notice-success">
95
+ <?php require DUPLICATOR_LITE_PATH . '/views/parts/migration-clean-installation-files.php'; ?>
96
+ </div>
97
+ <?php
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Shows a display message in the wp-admin if any reserved files are found
103
+ *
104
+ * @return void
105
+ */
106
+ public static function migrationSuccessNotice()
107
+ {
108
+ if (get_option(self::OPTION_KEY_MIGRATION_SUCCESS_NOTICE) != true) {
109
  return;
110
  }
111
 
112
+ if (DUP_CTRL_Tools::isDiagnosticPage()) {
113
+ require DUPLICATOR_LITE_PATH . '/views/parts/migration-message.php';
114
+ } else {
115
+ require DUPLICATOR_LITE_PATH . '/views/parts/migration-almost-complete.php';
116
+ }
 
 
 
 
 
 
 
 
 
 
117
  }
118
 
119
  /**
126
  //Show only on Duplicator pages and Dashboard when plugin is active
127
  $dup_active = is_plugin_active('duplicator/duplicator.php');
128
  $dup_perm = current_user_can('manage_options');
129
+ if (!$dup_active || !$dup_perm) {
130
  return;
131
+ }
132
 
133
  $screen = get_current_screen();
134
+ if (!isset($screen)) {
135
  return;
136
+ }
137
 
138
  $is_installer_cleanup_req = ($screen->id == 'duplicator_page_duplicator-tools' && isset($_GET['action']) && $_GET['action'] == 'installer');
139
  if (DUP_Server::hasInstallerFiles() && !$is_installer_cleanup_req) {
140
+ MigrationMng::renameInstallersPhpFiles();
141
 
142
  $on_active_tab = isset($_GET['section']) ? $_GET['section'] : '';
143
  echo '<div class="dup-updated notice notice-success dup-global-error-reserved-files" id="message"><p>';
153
 
154
  //On Tools > Cleanup Page
155
  if ($screen->id == 'duplicator_page_duplicator-tools' && ($on_active_tab == "info" || $on_active_tab == '')) {
 
156
  $title = __('This site has been successfully migrated!', 'duplicator');
157
  $msg1 = __('Final step(s):', 'duplicator');
158
  $msg2 = __('This message will be removed after all installer files are removed. Installer files must be removed to maintain a secure site. '
159
+ . 'Click the link above or button below to remove all installer files and complete the migration.', 'duplicator');
160
 
161
+ echo "<b class='pass-msg'><i class='fa fa-check-circle'></i> " . esc_html($title) . "</b> <br/> {$safe_html} <b>" . esc_html($msg1) . "</b> <br/>";
162
  printf("1. <a href='javascript:void(0)' onclick='jQuery(\"#dup-remove-installer-files-btn\").click()'>%s</a><br/>", esc_html__('Remove Installation Files Now!', 'duplicator'));
163
  printf("2. <a href='https://wordpress.org/support/plugin/duplicator/reviews/?filter=5' target='wporg'>%s</a> <br/> ", esc_html__('Optionally, Review Duplicator at WordPress.org...', 'duplicator'));
164
+ echo "<div class='pass-msg'>" . esc_html($msg2) . "</div>";
165
 
166
  //All other Pages
167
  } else {
 
168
  $title = __('Migration Almost Complete!', 'duplicator');
169
  $msg = __('Reserved Duplicator installation files have been detected in the root directory. Please delete these installation files to '
170
+ . 'avoid security issues. <br/> Go to:Duplicator > Tools > Information >Stored Data and click the "Remove Installation Files" button', 'duplicator');
171
 
172
  $nonce = wp_create_nonce('duplicator_cleanup_page');
173
+ $url = self_admin_url('admin.php?page=duplicator-tools&tab=diagnostics&section=info&_wpnonce=' . $nonce);
174
  echo "<b>{$title}</b><br/> {$safe_html} {$msg}";
175
  @printf("<br/><a href='{$url}'>%s</a>", __('Take me there now!', 'duplicator'));
176
  }
198
  public static function installAutoDeactivatePlugins()
199
  {
200
  $reactivatePluginsAfterInstallation = get_option(self::OPTION_KEY_ACTIVATE_PLUGINS_AFTER_INSTALL, false);
201
+
202
+ $pluginsToActive = get_option(self::OPTION_KEY_ACTIVATE_PLUGINS_AFTER_INSTALL, false);
203
+ if (!is_array($pluginsToActive) || empty($pluginsToActive)) {
204
+ return false;
205
+ }
206
+
207
+ $shouldBeActivated = array();
208
+ $allPlugins = get_plugins();
209
+ foreach ($pluginsToActive as $index => $pluginSlug) {
210
+ if (!isset($allPlugins[$pluginSlug])) {
211
+ unset($pluginsToActive[$index]);
212
+ continue;
213
  }
214
 
215
+ $isActive = is_plugin_active($pluginSlug);
216
+
217
+ if (!$isActive && isset($allPlugins[$pluginSlug])) {
218
+ $shouldBeActivated[$pluginSlug] = $allPlugins[$pluginSlug]['Name'];
219
  } else {
220
+ unset($pluginsToActive[$index]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  }
222
  }
223
+
224
+ if (empty($shouldBeActivated)) {
225
+ delete_option(self::OPTION_KEY_ACTIVATE_PLUGINS_AFTER_INSTALL);
226
+ return;
227
+ } else {
228
+ update_option(self::OPTION_KEY_ACTIVATE_PLUGINS_AFTER_INSTALL, $pluginsToActive);
229
+ }
230
+
231
+ $activatePluginsAnchors = array();
232
+ foreach ($shouldBeActivated as $slug => $title) {
233
+ $activateURL = wp_nonce_url(admin_url('plugins.php?action=activate&plugin=' . $slug), 'activate-plugin_' . $slug);
234
+ $anchorTitle = sprintf(esc_html__('Activate %s', 'duplicator'), $title);
235
+ $activatePluginsAnchors[] = '<a href="' . $activateURL . '"
236
+ title="' . esc_attr($anchorTitle) . '">' .
237
+ $title . '</a>';
238
+ }
239
+ ?>
240
+ <div class="update-nag duplicator-plugin-activation-admin-notice notice notice-warning duplicator-admin-notice is-dismissible"
241
+ data-to-dismiss="<?php echo esc_attr(self::OPTION_KEY_ACTIVATE_PLUGINS_AFTER_INSTALL); ?>" >
242
+ <p>
243
+ <?php
244
+ echo "<b>" . esc_html__("Warning!", "duplicator") . "</b> " . esc_html__("Migration Almost Complete!", "duplicator") . " <br/>";
245
+ echo esc_html__("Plugin(s) listed here have been deactivated during installation to help prevent issues. Please activate them to finish this migration: ", "duplicator") . "<br/>";
246
+ echo implode(' ,', $activatePluginsAnchors);
247
+ ?>
248
+ </p>
249
+ </div>
250
+ <?php
251
  }
252
 
253
  /**
278
  }
279
 
280
  // not using DUP_Util::getTablePrefix() in place of $tablePrefix because DUP_UI_Notice included initially (Duplicator\Lite\Requirement is depended on the DUP_UI_Notice)
281
+ $tablePrefix = (is_multisite() && is_plugin_active_for_network('duplicator/duplicator.php')) ? $GLOBALS['wpdb']->base_prefix : $GLOBALS['wpdb']->prefix;
282
+ $packagesCount = $GLOBALS['wpdb']->get_var('SELECT count(id) FROM ' . $tablePrefix . 'duplicator_packages WHERE status=100');
283
 
284
  if ($packagesCount < DUPLICATOR_FEEDBACK_NOTICE_SHOW_AFTER_NO_PACKAGE) {
285
  return;
299
  <div class="notice updated duplicator-message duplicator-message-dismissed" data-notice_id="<?php echo esc_attr($notice_id); ?>">
300
  <div class="duplicator-message-inner">
301
  <div class="duplicator-message-icon">
302
+ <img src="<?php echo esc_url(DUPLICATOR_PLUGIN_URL . "assets/img/logo.png"); ?>" style="text-align:top; margin:0; height:60px; width:60px;" alt="Duplicator">
303
  </div>
304
  <div class="duplicator-message-content">
305
  <p><strong><?php echo __('Congrats!', 'duplicator'); ?></strong> <?php printf(esc_html__('You created over %d packages with Duplicator. Great job! If you can spare a minute, please help us by leaving a five star review on WordPress.org.', 'duplicator'), DUPLICATOR_FEEDBACK_NOTICE_SHOW_AFTER_NO_PACKAGE); ?></p>
321
  public static function showNoExportCapabilityNotice()
322
  {
323
  if (is_admin() && in_array('administrator', $GLOBALS['current_user']->roles) && !current_user_can('export')) {
324
+ $errorMessage = __('<strong>Duplicator</strong><hr> Your logged-in user role does not have export capability so you don\'t have access to Duplicator functionality.', 'duplicator') .
325
+ "<br>" .
326
  sprintf(__('<strong>RECOMMENDATION:</strong> Add export capability to your role. See FAQ: <a target="_blank" href="%s">%s</a>', 'duplicator'), 'https://snapcreek.com/duplicator/docs/faqs-tech/#faq-licensing-040-q', __('Why is the Duplicator/Packages menu missing from my admin menu?', 'duplicator'));
327
  DUP_UI_Notice::displayGeneralAdminNotice($errorMessage, self::GEN_ERROR_NOTICE, true);
328
  }
333
  *
334
  * @param string $htmlMsg html code to be printed
335
  * @param integer $noticeType constant value of SELF::GEN_
336
+ * @param boolean $isDismissible whether the notice is dismissable or not. Default is true
337
  * @param array|string $extraClasses add more classes to the notice div
338
  * @return void
339
  */
classes/ui/class.ui.screen.base.php CHANGED
@@ -1,4 +1,7 @@
1
  <?php
 
 
 
2
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
  /**
4
  * The base class for all screen.php files. This class is used to control items that are common
@@ -14,12 +17,12 @@ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
14
  *
15
  */
16
  // Exit if accessed directly
17
- if (!defined('DUPLICATOR_VERSION'))
18
  exit;
 
19
 
20
  class DUP_UI_Screen
21
  {
22
-
23
  /**
24
  * Used as a placeholder for the current screen object
25
  */
@@ -30,17 +33,18 @@ class DUP_UI_Screen
30
  */
31
  public function __construct()
32
  {
33
-
34
  }
35
 
36
  public static function getCustomCss()
37
  {
38
  $screen = get_current_screen();
39
- if (!in_array($screen->id, array(
 
40
  'toplevel_page_duplicator',
41
  'duplicator_page_duplicator-tools',
42
  'duplicator_page_duplicator-settings',
43
- 'duplicator_page_duplicator-gopro'))) {
 
44
  return;
45
  }
46
 
@@ -63,7 +67,7 @@ class DUP_UI_Screen
63
  public static function getCurrentColorScheme()
64
  {
65
  global $_wp_admin_css_colors;
66
- if(!isset($_wp_admin_css_colors) || !is_array($_wp_admin_css_colors)){
67
  return false;
68
  }
69
 
@@ -72,32 +76,31 @@ class DUP_UI_Screen
72
  if (isset($_wp_admin_css_colors[$colorScheme])) {
73
  return $_wp_admin_css_colors[$colorScheme];
74
  } else {
75
- return $_wp_admin_css_colors[DupLiteSnapLibUtil::arrayKeyFirst($_wp_admin_css_colors)];
76
  }
77
  }
78
 
79
  /**
80
  * Get the help support tab view content shown in the help system
81
  *
82
- * @param string $guide The target URL to navigate to on the online user guide
83
- * @param string $faq The target URL to navigate to on the online user tech FAQ
84
  *
85
  * @return null
86
  */
87
  public function getSupportTab($guide, $faq)
88
  {
89
  $content = __("<b>Need Help?</b> Please check out these resources first:"
90
- ."<ul>"
91
- ."<li><a href='https://snapcreek.com/duplicator/docs/guide{$guide}' target='_sc-faq'>Full Online User Guide</a></li>"
92
- ."<li><a href='https://snapcreek.com/duplicator/docs/faqs-tech{$faq}' target='_sc-faq'>Frequently Asked Questions</a></li>"
93
- ."</ul>", 'duplicator');
94
 
95
  $this->screen->add_help_tab(array(
96
  'id' => 'dup_help_tab_callback',
97
  'title' => esc_html__('Support', 'duplicator'),
98
  'content' => "<p>{$content}</p>"
99
- )
100
- );
101
  }
102
 
103
  /**
@@ -113,11 +116,11 @@ class DUP_UI_Screen
113
  $txt_faq = __("Technical FAQs", 'duplicator');
114
  $txt_sets = __("Package Settings", 'duplicator');
115
  $this->screen->set_help_sidebar(
116
- "<div class='dup-screen-hlp-info'><b>".esc_html($txt_title).":</b> <br/>"
117
- ."<i class='fa fa-home'></i> <a href='https://snapcreek.com/duplicator/docs/' target='_sc-home'>".esc_html($txt_home)."</a> <br/>"
118
- ."<i class='fa fa-book'></i> <a href='https://snapcreek.com/duplicator/docs/guide/' target='_sc-guide'>".esc_html($txt_guide)."</a> <br/>"
119
- ."<i class='far fa-file-code'></i> <a href='https://snapcreek.com/duplicator/docs/faqs-tech/' target='_sc-faq'>".esc_html($txt_faq)."</a> <br/>"
120
- ."<i class='fa fa-cog'></i> <a href='admin.php?page=duplicator-settings&tab=package'>".esc_html($txt_sets)."</a></div>"
121
  );
122
  }
123
  }
1
  <?php
2
+
3
+ use Duplicator\Libs\Snap\SnapUtil;
4
+
5
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
6
  /**
7
  * The base class for all screen.php files. This class is used to control items that are common
17
  *
18
  */
19
  // Exit if accessed directly
20
+ if (!defined('DUPLICATOR_VERSION')) {
21
  exit;
22
+ }
23
 
24
  class DUP_UI_Screen
25
  {
 
26
  /**
27
  * Used as a placeholder for the current screen object
28
  */
33
  */
34
  public function __construct()
35
  {
 
36
  }
37
 
38
  public static function getCustomCss()
39
  {
40
  $screen = get_current_screen();
41
+ if (
42
+ !in_array($screen->id, array(
43
  'toplevel_page_duplicator',
44
  'duplicator_page_duplicator-tools',
45
  'duplicator_page_duplicator-settings',
46
+ 'duplicator_page_duplicator-gopro'))
47
+ ) {
48
  return;
49
  }
50
 
67
  public static function getCurrentColorScheme()
68
  {
69
  global $_wp_admin_css_colors;
70
+ if (!isset($_wp_admin_css_colors) || !is_array($_wp_admin_css_colors)) {
71
  return false;
72
  }
73
 
76
  if (isset($_wp_admin_css_colors[$colorScheme])) {
77
  return $_wp_admin_css_colors[$colorScheme];
78
  } else {
79
+ return $_wp_admin_css_colors[SnapUtil::arrayKeyFirst($_wp_admin_css_colors)];
80
  }
81
  }
82
 
83
  /**
84
  * Get the help support tab view content shown in the help system
85
  *
86
+ * @param string $guide The target URL to navigate to on the online user guide
87
+ * @param string $faq The target URL to navigate to on the online user tech FAQ
88
  *
89
  * @return null
90
  */
91
  public function getSupportTab($guide, $faq)
92
  {
93
  $content = __("<b>Need Help?</b> Please check out these resources first:"
94
+ . "<ul>"
95
+ . "<li><a href='https://snapcreek.com/duplicator/docs/guide{$guide}' target='_sc-faq'>Full Online User Guide</a></li>"
96
+ . "<li><a href='https://snapcreek.com/duplicator/docs/faqs-tech{$faq}' target='_sc-faq'>Frequently Asked Questions</a></li>"
97
+ . "</ul>", 'duplicator');
98
 
99
  $this->screen->add_help_tab(array(
100
  'id' => 'dup_help_tab_callback',
101
  'title' => esc_html__('Support', 'duplicator'),
102
  'content' => "<p>{$content}</p>"
103
+ ));
 
104
  }
105
 
106
  /**
116
  $txt_faq = __("Technical FAQs", 'duplicator');
117
  $txt_sets = __("Package Settings", 'duplicator');
118
  $this->screen->set_help_sidebar(
119
+ "<div class='dup-screen-hlp-info'><b>" . esc_html($txt_title) . ":</b> <br/>"
120
+ . "<i class='fa fa-home'></i> <a href='https://snapcreek.com/duplicator/docs/' target='_sc-home'>" . esc_html($txt_home) . "</a> <br/>"
121
+ . "<i class='fa fa-book'></i> <a href='https://snapcreek.com/duplicator/docs/guide/' target='_sc-guide'>" . esc_html($txt_guide) . "</a> <br/>"
122
+ . "<i class='far fa-file-code'></i> <a href='https://snapcreek.com/duplicator/docs/faqs-tech/' target='_sc-faq'>" . esc_html($txt_faq) . "</a> <br/>"
123
+ . "<i class='fa fa-cog'></i> <a href='admin.php?page=duplicator-settings&tab=package'>" . esc_html($txt_sets) . "</a></div>"
124
  );
125
  }
126
  }
classes/ui/class.ui.viewstate.php CHANGED
@@ -1,83 +1,85 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- /**
4
- * Gets the view state of UI elements to remember its viewable state
5
- *
6
- * Standard: PSR-2
7
- * @link http://www.php-fig.org/psr/psr-2
8
- *
9
- * @package Duplicator
10
- * @subpackage classes/ui
11
- * @copyright (c) 2017, Snapcreek LLC
12
- *
13
- */
14
-
15
- // Exit if accessed directly
16
- if (! defined('DUPLICATOR_VERSION')) exit;
17
-
18
- class DUP_UI_ViewState
19
- {
20
- /**
21
- * The key used in the wp_options table
22
- *
23
- * @var string
24
- */
25
- private static $optionsViewStateKey = 'duplicator_ui_view_state';
26
-
27
- /**
28
- * Save the view state of UI elements
29
- *
30
- * @param string $key A unique key to define the UI element
31
- * @param string $value A generic value to use for the view state
32
- *
33
- * @return bool Returns true if the value was successfully saved
34
- */
35
- public static function save($key, $value)
36
- {
37
- $view_state = array();
38
- $view_state = get_option(self::$optionsViewStateKey);
39
- $view_state[$key] = $value;
40
- $success = update_option(self::$optionsViewStateKey, $view_state);
41
- return $success;
42
- }
43
-
44
- /**
45
- * Gets all the values from the settings array
46
- *
47
- * @return array Returns and array of all the values stored in the settings array
48
- */
49
- public static function getArray()
50
- {
51
- return get_option(self::$optionsViewStateKey);
52
- }
53
-
54
- /**
55
- * Sets all the values from the settings array
56
- * @param array $view_state states
57
- *
58
- * @return boolean Returns whether updated or not
59
- */
60
- public static function setArray($view_state)
61
- {
62
- return update_option(self::$optionsViewStateKey, $view_state);
63
- }
64
-
65
- /**
66
- * Return the value of the of view state item
67
- *
68
- * @param type $searchKey The key to search on
69
- * @return string Returns the value of the key searched or null if key is not found
70
- */
71
- public static function getValue($searchKey)
72
- {
73
- $view_state = get_option(self::$optionsViewStateKey);
74
- if (is_array($view_state)) {
75
- foreach ($view_state as $key => $value) {
76
- if ($key == $searchKey) {
77
- return $value;
78
- }
79
- }
80
- }
81
- return null;
82
- }
83
- }
 
 
1
+ <?php
2
+
3
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
4
+ /**
5
+ * Gets the view state of UI elements to remember its viewable state
6
+ *
7
+ * Standard: PSR-2
8
+ * @link http://www.php-fig.org/psr/psr-2
9
+ *
10
+ * @package Duplicator
11
+ * @subpackage classes/ui
12
+ * @copyright (c) 2017, Snapcreek LLC
13
+ *
14
+ */
15
+
16
+ // Exit if accessed directly
17
+ if (! defined('DUPLICATOR_VERSION')) {
18
+ exit;
19
+ }
20
+
21
+ class DUP_UI_ViewState
22
+ {
23
+ /**
24
+ * The key used in the wp_options table
25
+ *
26
+ * @var string
27
+ */
28
+ private static $optionsViewStateKey = 'duplicator_ui_view_state';
29
+ /**
30
+ * Save the view state of UI elements
31
+ *
32
+ * @param string $key A unique key to define the UI element
33
+ * @param string $value A generic value to use for the view state
34
+ *
35
+ * @return bool Returns true if the value was successfully saved
36
+ */
37
+ public static function save($key, $value)
38
+ {
39
+ $view_state = array();
40
+ $view_state = get_option(self::$optionsViewStateKey);
41
+ $view_state[$key] = $value;
42
+ $success = update_option(self::$optionsViewStateKey, $view_state);
43
+ return $success;
44
+ }
45
+
46
+ /**
47
+ * Gets all the values from the settings array
48
+ *
49
+ * @return array Returns and array of all the values stored in the settings array
50
+ */
51
+ public static function getArray()
52
+ {
53
+ return get_option(self::$optionsViewStateKey);
54
+ }
55
+
56
+ /**
57
+ * Sets all the values from the settings array
58
+ * @param array $view_state states
59
+ *
60
+ * @return boolean Returns whether updated or not
61
+ */
62
+ public static function setArray($view_state)
63
+ {
64
+ return update_option(self::$optionsViewStateKey, $view_state);
65
+ }
66
+
67
+ /**
68
+ * Return the value of the of view state item
69
+ *
70
+ * @param type $searchKey The key to search on
71
+ * @return string Returns the value of the key searched or null if key is not found
72
+ */
73
+ public static function getValue($searchKey)
74
+ {
75
+ $view_state = get_option(self::$optionsViewStateKey);
76
+ if (is_array($view_state)) {
77
+ foreach ($view_state as $key => $value) {
78
+ if ($key == $searchKey) {
79
+ return $value;
80
+ }
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+ }
classes/ui/index.php CHANGED
@@ -1,2 +1,3 @@
1
  <?php
2
- //silent
 
1
  <?php
2
+
3
+ //silent
classes/utilities/class.u.json.php CHANGED
@@ -1,167 +1,166 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- /**
4
- * Utility class for working with JSON data
5
- *
6
- * Standard: PSR-2
7
- * @link http://www.php-fig.org/psr/psr-2
8
- *
9
- * @subpackage classes/utilities
10
- * @copyright (c) 2017, Snapcreek LLC
11
- * @license https://opensource.org/licenses/GPL-3.0 GNU Public License
12
- */
13
-
14
- // Exit if accessed directly
15
- if (! defined('DUPLICATOR_VERSION')) exit;
16
-
17
- class DUP_JSON
18
- {
19
- protected static $_messages = array(
20
- JSON_ERROR_NONE => 'No error has occurred',
21
- JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded',
22
- JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
23
- JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded',
24
- JSON_ERROR_SYNTAX => 'Syntax error',
25
- JSON_ERROR_UTF8 => 'Malformed UTF-8 characters. To resolve see https://snapcreek.com/duplicator/docs/faqs-tech/?utm_source=duplicator_free&utm_medium=wordpress_plugin&utm_campaign=problem_resolution&utm_content=malformed_utf8#faq-package-170-q'
26
- );
27
-
28
- /**
29
- * Used on PHP 5.3+ to better handle calling the json_encode method
30
- *
31
- * Returns a string containing the JSON representation of the supplied value
32
- *
33
- * @return string
34
- */
35
- public static function customEncode($value, $iteration = 1)
36
- {
37
- if (DUP_Util::$on_php_53_plus) {
38
- $encoded = DupLiteSnapJsonU::wp_json_encode_pprint($value);
39
-
40
- switch (json_last_error()) {
41
- case JSON_ERROR_NONE:
42
- return $encoded;
43
- case JSON_ERROR_DEPTH:
44
- throw new RuntimeException('Maximum stack depth exceeded');
45
- case JSON_ERROR_STATE_MISMATCH:
46
- throw new RuntimeException('Underflow or the modes mismatch');
47
- case JSON_ERROR_CTRL_CHAR:
48
- throw new RuntimeException('Unexpected control character found');
49
- case JSON_ERROR_SYNTAX:
50
- throw new RuntimeException('Syntax error, malformed JSON');
51
- case JSON_ERROR_UTF8:
52
- if ($iteration == 1) {
53
- $clean = self::makeUTF8($value);
54
- return self::customEncode($clean, $iteration + 1);
55
- } else {
56
- throw new RuntimeException('UTF-8 error loop');
57
- }
58
- default:
59
- throw new RuntimeException('Unknown error');
60
- }
61
- } else {
62
- return self::oldCustomEncode($value);
63
- }
64
- }
65
-
66
- public static function safeEncode($data, $options = 0, $depth = 512)
67
- {
68
- try {
69
- $jsonString = DupLiteSnapJsonU::wp_json_encode($data, $options, $depth);
70
- } catch (Exception $e) {
71
- $jsonString = false;
72
- }
73
-
74
- if (($jsonString === false) || trim($jsonString) == '') {
75
- $jsonString = self::customEncode($value);
76
-
77
- if (($jsonString === false) || trim($jsonString) == '') {
78
- throw new Exception('Unable to generate JSON from object');
79
- }
80
- }
81
- return $jsonString;
82
- }
83
-
84
- /**
85
- * Attempts to only call the json_decode method directly
86
- *
87
- * Returns the value encoded in json in appropriate PHP type. Values true, false and null are returned as TRUE, FALSE and NULL respectively.
88
- * NULL is returned if the json cannot be decoded or if the encoded data is deeper than the recursion limit.
89
- *
90
- * @return object
91
- */
92
- public static function decode($json, $assoc = false)
93
- {
94
- $result = json_decode($json, $assoc);
95
-
96
- if ($result !== null) {
97
- return $result;
98
- }
99
-
100
- if (function_exists('json_last_error')) {
101
- throw new RuntimeException(self::$_messages[json_last_error()]);
102
- } else {
103
- throw new RuntimeException("DUP_JSON decode error");
104
- }
105
-
106
- }
107
-
108
- private static function makeUTF8($mixed)
109
- {
110
- if (is_array($mixed)) {
111
- foreach ($mixed as $key => $value) {
112
- $mixed[$key] = self::makeUTF8($value);
113
- }
114
- } else if (is_string($mixed)) {
115
- return utf8_encode($mixed);
116
- }
117
- return $mixed;
118
- }
119
-
120
- private static function escapeString($str)
121
- {
122
- return addcslashes($str, "\v\t\n\r\f\"\\/");
123
- }
124
-
125
- private static function oldCustomEncode($in)
126
- {
127
- $out = "";
128
-
129
- if (is_object($in)) {
130
- $arr[$key] = "\"".self::escapeString($key)."\":\"{$val}\"";
131
- $in = get_object_vars($in);
132
- }
133
-
134
- if (is_array($in)) {
135
- $obj = false;
136
- $arr = array();
137
-
138
- foreach ($in AS $key => $val) {
139
- if (!is_numeric($key)) {
140
- $obj = true;
141
- }
142
- $arr[$key] = self::oldCustomEncode($val);
143
- }
144
-
145
- if ($obj) {
146
- foreach ($arr AS $key => $val) {
147
- $arr[$key] = "\"".self::escapeString($key)."\":{$val}";
148
- }
149
- $val = implode(',', $arr);
150
- $out .= "{{$val}}";
151
- } else {
152
- $val = implode(',', $arr);
153
- $out .= "[{$val}]";
154
- }
155
- } elseif (is_bool($in)) {
156
- $out .= $in ? 'true' : 'false';
157
- } elseif (is_null($in)) {
158
- $out .= 'null';
159
- } elseif (is_string($in)) {
160
- $out .= "\"".self::escapeString($in)."\"";
161
- } else {
162
- $out .= $in;
163
- }
164
-
165
- return "{$out}";
166
- }
167
- }
1
+ <?php
2
+
3
+ use Duplicator\Libs\Snap\SnapJson;
4
+
5
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
6
+ /**
7
+ * Utility class for working with JSON data
8
+ *
9
+ * Standard: PSR-2
10
+ * @link http://www.php-fig.org/psr/psr-2
11
+ *
12
+ * @subpackage classes/utilities
13
+ * @copyright (c) 2017, Snapcreek LLC
14
+ * @license https://opensource.org/licenses/GPL-3.0 GNU Public License
15
+ */
16
+
17
+ // Exit if accessed directly
18
+ if (! defined('DUPLICATOR_VERSION')) {
19
+ exit;
20
+ }
21
+
22
+ class DUP_JSON
23
+ {
24
+ // PHP 5.3 doesn't allow concating of strings within an array initializer so accepting long string
25
+ protected static $messages = array(
26
+ JSON_ERROR_NONE => 'No error has occurred',
27
+ JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded',
28
+ JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
29
+ JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded',
30
+ JSON_ERROR_SYNTAX => 'Syntax error',
31
+ JSON_ERROR_UTF8 => 'Malformed UTF-8 characters. To resolve see https://snapcreek.com/duplicator/docs/faqs-tech/?utm_source=duplicator_free&utm_medium=wordpress_plugin&utm_campaign=problem_resolution&utm_content=malformed_utf8#faq-package-170-q'
32
+ );
33
+ /**
34
+ * Used on PHP 5.3+ to better handle calling the json_encode method
35
+ *
36
+ * Returns a string containing the JSON representation of the supplied value
37
+ *
38
+ * @return string
39
+ */
40
+ public static function customEncode($value, $iteration = 1)
41
+ {
42
+ if (DUP_Util::$on_php_53_plus) {
43
+ $encoded = SnapJson::jsonEncodePPrint($value);
44
+ switch (json_last_error()) {
45
+ case JSON_ERROR_NONE:
46
+ return $encoded;
47
+ case JSON_ERROR_DEPTH:
48
+ throw new RuntimeException('Maximum stack depth exceeded');
49
+ case JSON_ERROR_STATE_MISMATCH:
50
+ throw new RuntimeException('Underflow or the modes mismatch');
51
+ case JSON_ERROR_CTRL_CHAR:
52
+ throw new RuntimeException('Unexpected control character found');
53
+ case JSON_ERROR_SYNTAX:
54
+ throw new RuntimeException('Syntax error, malformed JSON');
55
+ case JSON_ERROR_UTF8:
56
+ if ($iteration == 1) {
57
+ $clean = self::makeUTF8($value);
58
+ return self::customEncode($clean, $iteration + 1);
59
+ } else {
60
+ throw new RuntimeException('UTF-8 error loop');
61
+ }
62
+ default:
63
+ throw new RuntimeException('Unknown error');
64
+ }
65
+ } else {
66
+ return self::oldCustomEncode($value);
67
+ }
68
+ }
69
+
70
+ public static function safeEncode($data, $options = 0, $depth = 512)
71
+ {
72
+ try {
73
+ $jsonString = SnapJson::jsonEncode($data, $options, $depth);
74
+ } catch (Exception $e) {
75
+ $jsonString = false;
76
+ }
77
+
78
+ if (($jsonString === false) || trim($jsonString) == '') {
79
+ $jsonString = self::customEncode($value);
80
+ if (($jsonString === false) || trim($jsonString) == '') {
81
+ throw new Exception('Unable to generate JSON from object');
82
+ }
83
+ }
84
+ return $jsonString;
85
+ }
86
+
87
+ /**
88
+ * Attempts to only call the json_decode method directly
89
+ *
90
+ * Returns the value encoded in json in appropriate PHP type. Values true, false and null are returned as TRUE, FALSE and NULL respectively.
91
+ * NULL is returned if the json cannot be decoded or if the encoded data is deeper than the recursion limit.
92
+ *
93
+ * @return object
94
+ */
95
+ public static function decode($json, $assoc = false)
96
+ {
97
+ $result = json_decode($json, $assoc);
98
+ if ($result !== null) {
99
+ return $result;
100
+ }
101
+
102
+ if (function_exists('json_last_error')) {
103
+ throw new RuntimeException(self::$messages[json_last_error()]);
104
+ } else {
105
+ throw new RuntimeException("DUP_JSON decode error");
106
+ }
107
+ }
108
+
109
+ private static function makeUTF8($mixed)
110
+ {
111
+ if (is_array($mixed)) {
112
+ foreach ($mixed as $key => $value) {
113
+ $mixed[$key] = self::makeUTF8($value);
114
+ }
115
+ } elseif (is_string($mixed)) {
116
+ return utf8_encode($mixed);
117
+ }
118
+ return $mixed;
119
+ }
120
+
121
+ private static function escapeString($str)
122
+ {
123
+ return addcslashes($str, "\v\t\n\r\f\"\\/");
124
+ }
125
+
126
+ private static function oldCustomEncode($in)
127
+ {
128
+ $out = "";
129
+ if (is_object($in)) {
130
+ $arr[$key] = "\"" . self::escapeString($key) . "\":\"{$val}\"";
131
+ $in = get_object_vars($in);
132
+ }
133
+
134
+ if (is_array($in)) {
135
+ $obj = false;
136
+ $arr = array();
137
+ foreach ($in as $key => $val) {
138
+ if (!is_numeric($key)) {
139
+ $obj = true;
140
+ }
141
+ $arr[$key] = self::oldCustomEncode($val);
142
+ }
143
+
144
+ if ($obj) {
145
+ foreach ($arr as $key => $val) {
146
+ $arr[$key] = "\"" . self::escapeString($key) . "\":{$val}";
147
+ }
148
+ $val = implode(',', $arr);
149
+ $out .= "{{$val}}";
150
+ } else {
151
+ $val = implode(',', $arr);
152
+ $out .= "[{$val}]";
153
+ }
154
+ } elseif (is_bool($in)) {
155
+ $out .= $in ? 'true' : 'false';
156
+ } elseif (is_null($in)) {
157
+ $out .= 'null';
158
+ } elseif (is_string($in)) {
159
+ $out .= "\"" . self::escapeString($in) . "\"";
160
+ } else {
161
+ $out .= $in;
162
+ }
163
+
164
+ return "{$out}";
165
+ }
166
+ }
 
classes/utilities/class.u.migration.php DELETED
@@ -1,137 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * Utility class managing th emigration data
5
- *
6
- * Standard: PSR_2
7
- * @link http://www.php_fig.org/psr/psr_2
8
- * @copyright (c) 2017, Snapcreek LLC
9
- * @license https://opensource.org/licenses/GPL_3.0 GNU Public License
10
- *
11
- */
12
-
13
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
14
-
15
- class DUP_Migration
16
- {
17
- const CLEAN_INSTALL_REPORT_OPTION = 'duplicator_clean_install_report';
18
- const ARCHIVE_REGEX_PATTERN = '/^(.+_[a-z0-9]{7,}_[0-9]{14})_archive\.(?:zip|daf)$/';
19
-
20
- /**
21
- * messages to be displayed in the successful migration box
22
- *
23
- * @var array
24
- */
25
- protected static $migrationCleanupReport = array(
26
- 'removed' => array(),
27
- 'stored' => array(),
28
- 'instFile' => array()
29
- );
30
-
31
- /**
32
- * Check the root path and in case there are installer files without hashes rename them.
33
- *
34
- * @return void
35
- */
36
- public static function renameInstallersPhpFiles()
37
- {
38
- $pathsTocheck = array(
39
- DupLiteSnapLibIOU::safePathTrailingslashit(ABSPATH),
40
- DupLiteSnapLibIOU::safePathTrailingslashit(DupLiteSnapLibUtilWp::getHomePath()),
41
- DupLiteSnapLibIOU::safePathTrailingslashit(WP_CONTENT_DIR)
42
- );
43
-
44
- $pathsTocheck = array_unique($pathsTocheck);
45
-
46
- $filesToCheck = array();
47
- foreach ($pathsTocheck as $cFolder) {
48
- if (
49
- !is_dir($cFolder) ||
50
- !is_writable($cFolder) // rename permissions
51
- ) {
52
- continue;
53
- }
54
- $cFile = $cFolder . 'installer.php';
55
- if (
56
- !is_file($cFile) ||
57
- !DupLiteSnapLibIOU::chmod($cFile, 'u+rw') ||
58
- !is_readable($cFile)
59
- ) {
60
- continue;
61
- }
62
- $filesToCheck[] = $cFile;
63
- }
64
-
65
- $installerTplCheck = '/class DUPX_Bootstrap.+const\s+ARCHIVE_FILENAME\s*=\s*[\'"](.+?)[\'"]\s*;.*const\s+PACKAGE_HASH\s*=\s*[\'"](.+?)[\'"];/s';
66
-
67
- foreach ($filesToCheck as $file) {
68
- $fileName = basename($file);
69
- if (($content = @file_get_contents($file, false, null, 0, 5000)) === false) {
70
- continue;
71
- }
72
- $matches = null;
73
- if (preg_match($installerTplCheck, $content, $matches) !== 1) {
74
- continue;
75
- }
76
-
77
- $archiveName = $matches[1];
78
- $hash = $matches[2];
79
- $matches = null;
80
-
81
- if (preg_match(self::ARCHIVE_REGEX_PATTERN, $archiveName, $matches) !== 1) {
82
- if (DupLiteSnapLibIOU::unlink($file)) {
83
- self::$migrationCleanupReport['instFile'][] = "<div class='failure'>"
84
- . "<i class='fa fa-check green'></i> "
85
- . sprintf(__('Installer file <b>%s</b> removed for secority reasons', 'duplicator'), esc_html($fileName))
86
- . "</div>";
87
- } else {
88
- self::$migrationCleanupReport['instFile'][] = "<div class='success'>"
89
- . '<i class="fa fa-exclamation-triangle red"></i> '
90
- . sprintf(__('Can\'t remove installer file <b>%s</b>, please remove it for security reasons', 'duplicator'), esc_html($fileName))
91
- . '</div>';
92
- }
93
- continue;
94
- }
95
-
96
- $archiveHash = $matches[1];
97
- if (strpos($file, $archiveHash) === false) {
98
- if (DupLiteSnapLibIOU::rename($file, dirname($file) . '/' . $archiveHash . '_installer.php', true)) {
99
- self::$migrationCleanupReport['instFile'][] = "<div class='failure'>"
100
- . "<i class='fa fa-check green'></i> "
101
- . sprintf(__('Installer file <b>%s</b> renamed with HASH', 'duplicator'), esc_html($fileName))
102
- . "</div>";
103
- } else {
104
- self::$migrationCleanupReport['instFile'][] = "<div class='success'>"
105
- . '<i class="fa fa-exclamation-triangle red"></i> '
106
- . sprintf(__('Can\'t rename installer file <b>%s</b> with HASH, please remove it for security reasons', 'duplicator'), esc_html($fileName))
107
- . '</div>';
108
- }
109
- }
110
- }
111
- }
112
-
113
- /**
114
- * return cleanup report
115
- *
116
- * @return array
117
- */
118
- public static function getCleanupReport()
119
- {
120
- $option = get_option(self::CLEAN_INSTALL_REPORT_OPTION);
121
- if (is_array($option)) {
122
- self::$migrationCleanupReport = array_merge(self::$migrationCleanupReport, $option);
123
- }
124
-
125
- return self::$migrationCleanupReport;
126
- }
127
-
128
- /**
129
- * save clean up report in wordpress options
130
- *
131
- * @return boolean
132
- */
133
- public static function saveCleanupReport()
134
- {
135
- return add_option(self::CLEAN_INSTALL_REPORT_OPTION, self::$migrationCleanupReport, '', 'no');
136
- }
137
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classes/utilities/class.u.multisite.php CHANGED
@@ -1,39 +1,264 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- /**
4
- * Methods used to work with WordPress MU sites
5
- *
6
- * Standard: PSR-2
7
- * @link http://www.php-fig.org/psr/psr-2
8
- *
9
- * @package Duplicator
10
- * @subpackage classes/utilities
11
- * @copyright (c) 2017, Snapcreek LLC
12
- *
13
- * @todo Refactor out IO methods into class.io.php file
14
- */
15
-
16
- // Exit if accessed directly
17
- if (! defined('DUPLICATOR_VERSION')) exit;
18
-
19
- class DUP_MU
20
- {
21
- public static function isMultisite()
22
- {
23
- return self::getMode() > 0;
24
- }
25
-
26
- // 0 = single site; 1 = multisite subdomain; 2 = multisite subdirectory
27
- public static function getMode()
28
- {
29
- if(is_multisite()) {
30
- if (defined('SUBDOMAIN_INSTALL') && SUBDOMAIN_INSTALL) {
31
- return 1;
32
- } else {
33
- return 2;
34
- }
35
- } else {
36
- return 0;
37
- }
38
- }
39
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ use Duplicator\Libs\Snap\SnapDB;
4
+ use Duplicator\Libs\Snap\SnapIO;
5
+ use Duplicator\Libs\Snap\SnapURL;
6
+ use Duplicator\Libs\Snap\SnapWP;
7
+
8
+ class DUP_MU
9
+ {
10
+ public static function networkMenuPageUrl($menu_slug, $echo = true)
11
+ {
12
+ global $_parent_pages;
13
+
14
+ if (isset($_parent_pages[$menu_slug])) {
15
+ $parent_slug = $_parent_pages[$menu_slug];
16
+ if ($parent_slug && !isset($_parent_pages[$parent_slug])) {
17
+ $url = network_admin_url(add_query_arg('page', $menu_slug, $parent_slug));
18
+ } else {
19
+ $url = network_admin_url('admin.php?page=' . $menu_slug);
20
+ }
21
+ } else {
22
+ $url = '';
23
+ }
24
+
25
+ $url = esc_url($url);
26
+
27
+ if ($echo) {
28
+ echo esc_url($url);
29
+ }
30
+
31
+ return $url;
32
+ }
33
+
34
+ /**
35
+ * return multisite mode
36
+ * 0 = single site
37
+ * 1 = multisite subdomain
38
+ * 2 = multisite subdirectory
39
+ *
40
+ * @return int
41
+ */
42
+ public static function getMode()
43
+ {
44
+ if (is_multisite()) {
45
+ if (defined('SUBDOMAIN_INSTALL') && SUBDOMAIN_INSTALL) {
46
+ return 1;
47
+ } else {
48
+ return 2;
49
+ }
50
+ } else {
51
+ return 0;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * This function is wrong because it assumes that if the folder sites exist, blogs.dir cannot exist.
57
+ * This is not true because if the network is old but a new site is created after the WordPress update both blogs.dir and sites folders exist.
58
+ *
59
+ * @deprecated since version 3.8.4
60
+ *
61
+ * @return int
62
+ */
63
+ public static function getGeneration()
64
+ {
65
+ if (self::getMode() == 0) {
66
+ return 0;
67
+ } else {
68
+ $sitesDir = WP_CONTENT_DIR . '/uploads/sites';
69
+
70
+ if (file_exists($sitesDir)) {
71
+ return 2;
72
+ } else {
73
+ return 1;
74
+ }
75
+ }
76
+ }
77
+
78
+ /**
79
+ *
80
+ * @param array $filteredSites
81
+ * @param array $filteredTables
82
+ * @param array $filteredPaths
83
+ *
84
+ * @return array
85
+ */
86
+ public static function getSubsites($filteredSites = array(), $filteredTables = array(), $filteredPaths = array())
87
+ {
88
+ if (!is_multisite()) {
89
+ return array(
90
+ self::getSubsiteInfo(1, $filteredTables, $filteredPaths)
91
+ );
92
+ }
93
+
94
+ $site_array = array();
95
+ $filteredSites = is_array($filteredSites) ? $filteredSites : array();
96
+
97
+ DUP_Log::trace("NETWORK SITES");
98
+
99
+ foreach (SnapWP::getSitesIds() as $siteId) {
100
+ if (in_array($siteId, $filteredSites)) {
101
+ continue;
102
+ }
103
+ if (($siteInfo = self::getSubsiteInfo($siteId, $filteredTables, $filteredPaths)) == false) {
104
+ continue;
105
+ }
106
+ array_push($site_array, $siteInfo);
107
+ DUP_Log::trace("Multisite subsite detected. ID={$siteInfo->id} Domain={$siteInfo->domain} Path={$siteInfo->path} Blogname={$siteInfo->blogname}");
108
+ }
109
+
110
+ return $site_array;
111
+ }
112
+
113
+ /**
114
+ *
115
+ * @param int $subsiteId
116
+ *
117
+ * @return stdClass|bool false on failure
118
+ */
119
+ public static function getSubsiteInfoById($subsiteId)
120
+ {
121
+ if (!is_multisite()) {
122
+ $subsiteId = 1;
123
+ }
124
+ return self::getSubsiteInfo($subsiteId);
125
+ }
126
+
127
+ /**
128
+ * Get subsite info
129
+ *
130
+ * @param int $siteId
131
+ * @param array $filteredTables
132
+ * @param array|false $filteredPaths return
133
+ *
134
+ * @return stdClass|bool false on failure
135
+ */
136
+ public static function getSubsiteInfo($siteId = 1, $filteredTables = array(), $filteredPaths = array())
137
+ {
138
+ if (is_multisite()) {
139
+ if (($siteDetails = get_blog_details($siteId)) == false) {
140
+ return false;
141
+ }
142
+ } else {
143
+ $siteId = 1;
144
+ $siteDetails = new stdClass();
145
+ $home = DUP_Archive::getOriginalUrls('home');
146
+ $parsedHome = SnapURL::parseUrl($home);
147
+ $siteDetails->domain = $parsedHome['host'];
148
+ $siteDetails->path = trailingslashit($parsedHome['path']);
149
+ $siteDetails->blogname = sanitize_text_field(get_option('blogname'));
150
+ }
151
+
152
+ $subsiteID = $siteId;
153
+ $siteInfo = new stdClass();
154
+ $siteInfo->id = $subsiteID;
155
+ $siteInfo->domain = $siteDetails->domain;
156
+ $siteInfo->path = $siteDetails->path;
157
+ $siteInfo->blogname = $siteDetails->blogname;
158
+ $siteInfo->blog_prefix = $GLOBALS['wpdb']->get_blog_prefix($subsiteID);
159
+ if (count($filteredTables) > 0) {
160
+ $siteInfo->filteredTables = array_values(array_intersect(self::getSubsiteTables($subsiteID), $filteredTables));
161
+ } else {
162
+ $siteInfo->filteredTables = array();
163
+ }
164
+ $siteInfo->adminUsers = SnapWP::getAdminUserLists($siteInfo->id);
165
+ $siteInfo->fullHomeUrl = get_home_url($siteId);
166
+ $siteInfo->fullSiteUrl = get_site_url($siteId);
167
+
168
+ if ($siteId > 1) {
169
+ switch_to_blog($siteId);
170
+ }
171
+
172
+ $uploadData = wp_upload_dir();
173
+ $uploadPath = $uploadData['basedir'];
174
+ $siteInfo->uploadPath = SnapIO::getRelativePath($uploadPath, DUP_Archive::getTargetRootPath(), true);
175
+ $siteInfo->fullUploadPath = untrailingslashit($uploadPath);
176
+ $siteInfo->fullUploadSafePath = SnapIO::safePathUntrailingslashit($uploadPath);
177
+ $siteInfo->fullUploadUrl = $uploadData['baseurl'];
178
+ if (count($filteredPaths)) {
179
+ $globalDirFilters = apply_filters('duplicator_global_file_filters', $GLOBALS['DUPLICATOR_GLOBAL_FILE_FILTERS']);
180
+ $siteInfo->filteredPaths = array_values(array_filter($filteredPaths, function ($path) use ($uploadPath, $subsiteID, $globalDirFilters) {
181
+ if (
182
+ ($relativeUpload = SnapIO::getRelativePath($path, $uploadPath)) === false ||
183
+ in_array($path, $globalDirFilters)
184
+ ) {
185
+ return false;
186
+ }
187
+
188
+ if ($subsiteID > 1) {
189
+ return true;
190
+ } else {
191
+ // no check on blogs.dir because in wp-content/blogs.dir not in upload folder
192
+ return !(strpos($relativeUpload, 'sites') === 0);
193
+ }
194
+ }));
195
+ } else {
196
+ $siteInfo->filteredPaths = array();
197
+ }
198
+
199
+ if ($siteId > 1) {
200
+ restore_current_blog();
201
+ }
202
+ return $siteInfo;
203
+ }
204
+
205
+ /**
206
+ * @param int $subsiteID
207
+ *
208
+ * @return array List of tables belonging to subsite
209
+ */
210
+ public static function getSubsiteTables($subsiteID)
211
+ {
212
+ /** @var \wpdb $wpdb */
213
+ global $wpdb;
214
+
215
+ $basePrefix = $wpdb->base_prefix;
216
+ $subsitePrefix = $wpdb->get_blog_prefix($subsiteID);
217
+
218
+ $sharedTables = array(
219
+ $basePrefix . 'users',
220
+ $basePrefix . 'usermeta'
221
+ );
222
+ $multisiteOnlyTables = array(
223
+ $basePrefix . 'blogmeta',
224
+ $basePrefix . 'blogs',
225
+ $basePrefix . 'blog_versions',
226
+ $basePrefix . 'registration_log',
227
+ $basePrefix . 'signups',
228
+ $basePrefix . 'site',
229
+ $basePrefix . 'sitemeta'
230
+ );
231
+
232
+ $subsiteTables = array();
233
+ $sql = "";
234
+ $dbnameSafe = esc_sql(DB_NAME);
235
+
236
+ if ($subsiteID != 1) {
237
+ $regex = '^' . SnapDB::quoteRegex($subsitePrefix);
238
+ $regexpSafe = esc_sql($regex);
239
+
240
+ $sharedTablesSafe = "'" . implode(
241
+ "', '",
242
+ esc_sql($sharedTables)
243
+ ) . "'";
244
+ $sql = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '$dbnameSafe' AND ";
245
+ $sql .= "(TABLE_NAME REGEXP '$regexpSafe' OR TABLE_NAME IN ($sharedTablesSafe))";
246
+ } else {
247
+ $regexMain = '^' . SnapDB::quoteRegex($basePrefix);
248
+ $regexpMainSafe = esc_sql($regexMain);
249
+ $regexNotSub = '^' . SnapDB::quoteRegex($basePrefix) . '[0-9]+_';
250
+ $regexpNotSubSafe = esc_sql($regexNotSub);
251
+
252
+ $multisiteOnlyTablesSafe = "'" . implode(
253
+ "', '",
254
+ esc_sql($multisiteOnlyTables)
255
+ ) . "'";
256
+ $sql = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '$dbnameSafe' AND ";
257
+ $sql .= "TABLE_NAME REGEXP '$regexpMainSafe' AND ";
258
+ $sql .= "TABLE_NAME NOT REGEXP '$regexpNotSubSafe' AND ";
259
+ $sql .= "TABLE_NAME NOT IN ($multisiteOnlyTablesSafe)";
260
+ }
261
+ $subsiteTables = $wpdb->get_col($sql);
262
+ return $subsiteTables;
263
+ }
264
+ }
classes/utilities/class.u.patch.php CHANGED
@@ -34,15 +34,46 @@ class DUP_Patch
34
  {
35
  $backupDir = $this->DupLiteBackupDir;
36
 
37
- foreach (glob("{$backupDir}/*_installer" . DUP_Installer::INSTALLER_SERVER_EXTENSION) as $file) {
38
- if (strstr($file, '_installer' . DUP_Installer::INSTALLER_SERVER_EXTENSION)) {
39
  $content = "<?php \n";
40
  $content .= " /** PATCH_MARKER_START:V.0001 **/ \n";
41
  $content .= " //TODO ADD PHP CODE HERE";
42
  $content .= " /** PATCH_MARKER_END **/ \n";
43
  $content .= "?>\n";
44
- DUP_IO::fwritePrepend($file, $content);
45
  }
46
  }
47
  }
48
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  {
35
  $backupDir = $this->DupLiteBackupDir;
36
 
37
+ foreach (glob("{$backupDir}/*_installer.php") as $file) {
38
+ if (strstr($file, '_installer.php')) {
39
  $content = "<?php \n";
40
  $content .= " /** PATCH_MARKER_START:V.0001 **/ \n";
41
  $content .= " //TODO ADD PHP CODE HERE";
42
  $content .= " /** PATCH_MARKER_END **/ \n";
43
  $content .= "?>\n";
44
+ $this->fwritePrepend($file, $content);
45
  }
46
  }
47
  }
48
+
49
+
50
+ /**
51
+ * Prepends data to an existing file
52
+ *
53
+ * @param string $file The full file path to the file
54
+ * @param string $content The content to prepend to the file
55
+ *
56
+ * @return TRUE on success or if file does not exist. FALSE on failure
57
+ */
58
+ private function fwritePrepend($file, $prepend)
59
+ {
60
+ if (!file_exists($file) || !is_writable($file)) {
61
+ return false;
62
+ }
63
+
64
+ $handle = fopen($file, "r+");
65
+ $len = strlen($prepend);
66
+ $final_len = filesize($file) + $len;
67
+ $cache_old = fread($handle, $len);
68
+ rewind($handle);
69
+ $i = 1;
70
+ while (ftell($handle) < $final_len) {
71
+ fwrite($handle, $prepend);
72
+ $prepend = $cache_old;
73
+ $cache_old = fread($handle, $len);
74
+ fseek($handle, $i * $len);
75
+ $i++;
76
+ }
77
+ }
78
+
79
+ }
classes/utilities/class.u.php CHANGED
@@ -1,10 +1,9 @@
1
  <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
 
4
- require_once(DUPLICATOR_PLUGIN_PATH . '/classes/class.io.php');
5
 
6
  /**
7
- * Utility class used for helper type functions
8
  *
9
  * Standard: PSR-2
10
  * @link http://www.php-fig.org/psr/psr-2
@@ -13,14 +12,11 @@ require_once(DUPLICATOR_PLUGIN_PATH . '/classes/class.io.php');
13
  * @subpackage classes/utilities
14
  * @copyright (c) 2017, Snapcreek LLC
15
  *
 
16
  */
 
17
  class DUP_Util
18
  {
19
-
20
- const SECURE_ISSUE_DIE = 'die';
21
- const SECURE_ISSUE_THROW = 'throw';
22
- const SECURE_ISSUE_RETURN = 'return';
23
-
24
  /**
25
  * Is PHP 5.2.9 or better running
26
  */
@@ -53,17 +49,16 @@ class DUP_Util
53
  */
54
  public static function init()
55
  {
56
- self::$on_php_529_plus = version_compare(PHP_VERSION, '5.2.9') >= 0;
57
- self::$on_php_53_plus = version_compare(PHP_VERSION, '5.3.0') >= 0;
58
- self::$on_php_54_plus = version_compare(PHP_VERSION, '5.4.0') >= 0;
59
- self::$PHP7_plus = version_compare(PHP_VERSION, '7.0.0', '>=');
 
60
  }
61
 
62
  public static function getArchitectureString()
63
  {
64
- $php_int_size = PHP_INT_SIZE;
65
-
66
- switch ($php_int_size) {
67
  case 4:
68
  return esc_html__('32-bit', 'duplicator');
69
  break;
@@ -87,18 +82,18 @@ class DUP_Util
87
 
88
  public static function getWPCoreDirs()
89
  {
90
- $wp_core_dirs = array(get_home_path().'wp-admin', get_home_path().'wp-includes');
91
 
92
  //if wp_content is overrided
93
- $wp_path = get_home_path()."wp-content";
94
- if (get_home_path().'wp-content' != WP_CONTENT_DIR) {
95
  $wp_path = WP_CONTENT_DIR;
96
  }
97
  $wp_path = str_replace("\\", "/", $wp_path);
98
 
99
  $wp_core_dirs[] = $wp_path;
100
- $wp_core_dirs[] = $wp_path.'/plugins';
101
- $wp_core_dirs[] = $wp_path.'/themes';
102
 
103
  return $wp_core_dirs;
104
  }
@@ -109,7 +104,7 @@ class DUP_Util
109
  */
110
  public static function getWPCoreFiles()
111
  {
112
- $wp_cored_dirs = array(get_home_path().'wp-config.php');
113
  return $wp_cored_dirs;
114
  }
115
 
@@ -133,8 +128,8 @@ class DUP_Util
133
  trigger_error('array_group_by(): The key should be a string, an integer, or a callback', E_USER_ERROR);
134
  return null;
135
  }
136
- $func = (!is_string($key) && is_callable($key) ? $key : null);
137
- $_key = $key;
138
  // Load the new array, splitting by the target key
139
  $grouped = array();
140
  foreach ($array as $value) {
@@ -193,8 +188,9 @@ class DUP_Util
193
  {
194
  // Open file
195
  $f = @fopen($filepath, "rb");
196
- if ($f === false)
197
  return false;
 
198
 
199
  // Sets buffer size
200
  $buffer = 256;
@@ -204,8 +200,9 @@ class DUP_Util
204
 
205
  // Read it and adjust line number if necessary
206
  // (Otherwise the result would be wrong if file doesn't end with a blank line)
207
- if (fread($f, 1) != "\n")
208
  $lines -= 1;
 
209
 
210
  // Start reading
211
  $output = '';
@@ -214,15 +211,15 @@ class DUP_Util
214
  // While we would like more
215
  while (ftell($f) > 0 && $lines >= 0) {
216
  // Figure out how far back we should jump
217
- $seek = min(ftell($f), $buffer);
218
  // Do the jump (backwards, relative to where we are)
219
  fseek($f, -$seek, SEEK_CUR);
220
  // Read a chunk and prepend it to our output
221
- $output = ($chunk = fread($f, $seek)).$output;
222
  // Jump back to where we started reading
223
  fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR);
224
  // Decrease our line counter
225
- $lines -= substr_count($chunk, "\n");
226
  }
227
 
228
  // While we have too many lines
@@ -249,9 +246,8 @@ class DUP_Util
249
  for ($i = 0; $size >= 1024 && $i < 4; $i++) {
250
  $size /= 1024;
251
  }
252
- return round($size, $roundBy).$units[$i];
253
- }
254
- catch (Exception $e) {
255
  return "n/a";
256
  }
257
  }
@@ -262,7 +258,7 @@ class DUP_Util
262
  * uni: /home/path/file.txt
263
  * win: D:/home/path/file.txt
264
  *
265
- * @param string $path The path to make safe
266
  *
267
  * @return string A path with all slashes facing "/"
268
  */
@@ -293,7 +289,7 @@ class DUP_Util
293
  */
294
  public static function appendOnce($string, $value)
295
  {
296
- return $string.(substr($string, -1) == $value ? '' : $value);
297
  }
298
 
299
  /**
@@ -319,8 +315,8 @@ class DUP_Util
319
  * @return array of all files in that path
320
  *
321
  * Notes:
322
- * - Avoid using glob() as GLOB_BRACE is not an option on some operating systems
323
- * - Pre PHP 5.3 DirectoryIterator will crash on unreadable files
324
  * - Scandir will not crash on unreadable items, but will not return results
325
  */
326
  public static function listFiles($path = '.')
@@ -329,21 +325,21 @@ class DUP_Util
329
  $files = array();
330
  if ($dh = opendir($path)) {
331
  while (($file = readdir($dh)) !== false) {
332
- if ($file == '.' || $file == '..')
333
  continue;
334
- $full_file_path = trailingslashit($path).$file;
 
335
  $files[] = str_replace("\\", '/', $full_file_path);
336
  }
337
  @closedir($dh);
338
  }
339
  return $files;
340
- }
341
- catch (Exception $exc) {
342
  $result = array();
343
  $files = @scandir($path);
344
  if (is_array($files)) {
345
  foreach ($files as $file) {
346
- $result[] = str_replace("\\", '/', $path).$file;
347
  }
348
  }
349
  return $result;
@@ -378,8 +374,9 @@ class DUP_Util
378
  */
379
  public static function isDirectoryEmpty($path)
380
  {
381
- if (!is_readable($path))
382
- return NULL;
 
383
  return (count(scandir($path)) == 2);
384
  }
385
 
@@ -393,16 +390,19 @@ class DUP_Util
393
  */
394
  public static function getDirectorySize($path)
395
  {
396
- if (!file_exists($path))
397
  return 0;
398
- if (is_file($path))
 
399
  return filesize($path);
 
400
 
401
  $size = 0;
402
- $list = glob($path."/*");
403
  if (!empty($list)) {
404
- foreach ($list as $file)
405
  $size += self::getDirectorySize($file);
 
406
  }
407
  return $size;
408
  }
@@ -418,24 +418,27 @@ class DUP_Util
418
  $cmds = array('shell_exec', 'escapeshellarg', 'escapeshellcmd', 'extension_loaded');
419
 
420
  //Function disabled at server level
421
- if (array_intersect($cmds, array_map('trim', explode(',', @ini_get('disable_functions')))))
422
  return apply_filters('duplicator_is_shellzip_available', false);
 
423
 
424
  //Suhosin: http://www.hardened-php.net/suhosin/
425
  //Will cause PHP to silently fail
426
  if (extension_loaded('suhosin')) {
427
  $suhosin_ini = @ini_get("suhosin.executor.func.blacklist");
428
- if (array_intersect($cmds, array_map('trim', explode(',', $suhosin_ini))))
429
  return apply_filters('duplicator_is_shellzip_available', false);
 
430
  }
431
 
432
  if (! function_exists('shell_exec')) {
433
- return apply_filters('duplicator_is_shellzip_available', false);
434
- }
435
 
436
  // Can we issue a simple echo command?
437
- if (!@shell_exec('echo duplicator'))
438
  return apply_filters('duplicator_is_shellzip_available', false);
 
439
 
440
  return apply_filters('duplicator_is_shellzip_available', true);
441
  }
@@ -479,6 +482,9 @@ class DUP_Util
479
  {
480
  return base64_encode($string);
481
  }
 
 
 
482
 
483
  /**
484
  * Does the current user have the capability
@@ -499,7 +505,7 @@ class DUP_Util
499
 
500
  if (!current_user_can($capability)) {
501
  $exitMsg = __('You do not have sufficient permissions to access this page.', 'duplicator');
502
- DUP_LOG::Trace('You do not have sufficient permissions to access this page. PERMISSION: '.$permission);
503
 
504
  switch ($exit) {
505
  case self::SECURE_ISSUE_THROW:
@@ -549,8 +555,7 @@ class DUP_Util
549
  }
550
 
551
  return strlen($user) ? $user : $unreadable;
552
- }
553
- catch (Exception $ex) {
554
  return $unreadable;
555
  }
556
  }
@@ -562,44 +567,42 @@ class DUP_Util
562
  */
563
  public static function initSnapshotDirectory()
564
  {
565
- $error = false;
 
566
  $path_wproot = duplicator_get_abs_path();
567
- $backupsDir = DUP_Settings::getSsdirPath();
568
- $path_plugin = DUP_Util::safePath(DUPLICATOR_PLUGIN_PATH);
569
 
570
- //--------------------------------
571
- //DIRCTORY CREATION
572
- //Seupt the main directory and tmp build dir
573
- if (!file_exists($backupsDir)) {
574
  $old_root_perm = @fileperms($path_wproot);
575
 
 
576
  //CHMOD DIRECTORY ACCESS
577
  //wordpress root directory
578
- DupLiteSnapLibIOU::chmod($path_wproot, 'u+rwx');
579
 
580
  //snapshot directory
581
- if (DupLiteSnapLibIOU::dirWriteCheckOrMkdir($backupsDir, 'u+rwx,go+rx') == false) {
582
  $error = true;
583
  }
584
 
585
  // restore original root perms
586
- DupLiteSnapLibIOU::chmod($path_wproot, $old_root_perm);
587
 
588
  if ($error) {
589
  return false;
590
  }
591
  }
592
 
593
- DupLiteSnapLibIOU::chmod($backupsDir, 'u+rwx,go+rx');
594
- DupLiteSnapLibIOU::dirWriteCheckOrMkdir(DUP_Settings::getSsdirTmpPath(), 'u+rwx');
595
 
596
  //--------------------------------
597
  //FILE CREATION & HARDEN PROCESS
598
  //index.php, .htaccess, robots.txt
599
- self::setupBackupDirIndexFile($backupsDir);
600
- self::setupBackupDirRobotsFile($backupsDir);
601
- self::setupBackupDirHtaccess($backupsDir);
602
- self::performHardenProcesses($backupsDir);
603
 
604
  return true;
605
  }
@@ -607,50 +610,50 @@ class DUP_Util
607
  /**
608
  * Attempts to create a secure .htaccess file in the download directory
609
  *
610
- * @return null
611
  */
612
- public static function setupBackupDirHtaccess($backupsDir)
613
  {
614
  try {
615
  $storageHtaccessOff = DUP_Settings::Get('storage_htaccess_off');
616
- $fileName = "{$backupsDir}/.htaccess";
617
 
618
  if ($storageHtaccessOff) {
619
  @unlink($fileName);
620
- } else if (!file_exists($fileName) || @filesize($fileName) == 0) {
621
- $htfile = @fopen($fileName, 'w');
622
- $htoutput = "Options -Indexes \n";
623
- $htoutput .= "<Files *.php>\n deny from all\n</Files>";
624
- if ($htfile !== false) {
625
- @fwrite($htfile, $htoutput);
626
- @fclose($htfile);
627
  }
628
- }
629
  } catch (Exception $ex) {
630
- DUP_Log::Info("Duplicator Error: Unable to properly configure .htaccess for servers storage directory {$fileName}" . $ex);
631
  }
632
  }
633
 
634
  /**
635
  * Attempts to create an index.php file in the backups directory
636
  *
637
- * @return null
638
  */
639
- public static function setupBackupDirIndexFile($backupsDir)
640
  {
641
  try {
642
- $fileName = "{$backupsDir}/index.php";
643
  if (!file_exists($fileName)) {
644
- $ssfile = @fopen($fileName, 'w');
645
- if ($ssfile !== false) {
646
- @fwrite($ssfile,
647
- '<?php error_reporting(0); if (stristr(php_sapi_name(), "fcgi")) { $url = "http://" . $_SERVER["HTTP_HOST"]; '
648
- . 'header("Location: {$url}/404.html");} else { header("HTTP/1.1 404 Not Found", true, 404);} exit(); ?>');
649
- @fclose($ssfile);
650
  }
651
  }
652
  } catch (Exception $ex) {
653
- DUP_Log::Info("Duplicator Error: Unable to properly configure index.php for servers storage directory {$fileName}" . $ex);
654
  }
655
  }
656
 
@@ -658,48 +661,50 @@ class DUP_Util
658
  * Attempts to create a robots.txt file in the backups directory
659
  * to prevent search engines
660
  *
661
- * @return null
662
  */
663
- public static function setupBackupDirRobotsFile($backupsDir)
664
  {
665
  try {
666
- $fileName = "{$backupsDir}/robots.txt";
667
  if (!file_exists($fileName)) {
668
- $robotfile = @fopen($fileName, 'w');
669
- if ($robotfile !== false) {
670
- @fwrite($robotfile,
671
- "User-agent: * \n"
672
- ."Disallow: /".DUP_Settings::SSDIR_NAME_LEGACY."/\n"
673
- ."Disallow: /".DUP_Settings::SSDIR_NAME_NEW."/");
674
- @fclose($robotfile);
675
  }
676
  }
677
  } catch (Exception $ex) {
678
- DUP_Log::Info("Duplicator Error: Unable to properly configure tobots.txt for servers storage directory {$fileName}" . $ex);
679
  }
680
  }
681
 
 
682
  /**
683
  * Run various secure processes to harden the backups dir
684
  *
685
- * @return null
686
  */
687
- public static function performHardenProcesses($backupsDir)
688
  {
689
- //Edge Case: Remove any dup-installer/main.installer.php
 
 
690
  $dupInstallFile = "{$backupsDir}/dup-installer/main.installer.php";
691
  if (file_exists($dupInstallFile) ) {
692
- DupLiteSnapLibIOU::chmod($dupInstallFile, "a+w");
693
- DUP_IO::deleteFile("{$dupInstallFile}");
694
  }
695
 
696
  //Rename installer php files to .bak
697
- DupLiteSnapLibIOU::regexGlobCallback(
698
  $backupsDir,
699
  function ($path) {
700
- $parts = pathinfo($path);
701
  $newPath = $parts['dirname'] . '/' . $parts['filename'] . DUP_Installer::INSTALLER_SERVER_EXTENSION;
702
- DupLiteSnapLibIOU::rename($path, $newPath);
703
  },
704
  array(
705
  'regexFile' => '/^.+_installer.*\.php$/',
@@ -719,7 +724,7 @@ class DUP_Util
719
  $filepath = null;
720
 
721
  if (self::hasShellExec()) {
722
- if (shell_exec('hash zip 2>&1') == NULL) {
723
  $filepath = 'zip';
724
  } else {
725
  $possible_paths = array(
@@ -760,7 +765,7 @@ class DUP_Util
760
  global $wpdb;
761
  $result = array();
762
  foreach (self::getWPCoreTablesEnd() as $tend) {
763
- $result[] = $wpdb->prefix.$tend;
764
  }
765
  return $result;
766
  }
@@ -803,7 +808,7 @@ class DUP_Util
803
 
804
  if (in_array($subTName, $coreEnds)) {
805
  return true;
806
- } else if (is_multisite()) {
807
  $exTable = explode('_', $subTName);
808
  if (count($exTable) >= 2 && is_numeric($exTable[0])) {
809
  $tChekc = implode('_', array_slice($exTable, 1));
@@ -835,7 +840,7 @@ class DUP_Util
835
 
836
  /**
837
  * Check given table is exist in real
838
- *
839
  * @param $table string Table name
840
  * @return booleam
841
  */
@@ -843,10 +848,11 @@ class DUP_Util
843
  {
844
  // It will clear the $GLOBALS['wpdb']->last_error var
845
  $GLOBALS['wpdb']->flush();
846
- $sql = "SELECT 1 FROM `".esc_sql($table)."` LIMIT 1;";
847
  $ret = $GLOBALS['wpdb']->get_var($sql);
848
- if (empty($GLOBALS['wpdb']->last_error))
849
  return true;
 
850
  return false;
851
  }
852
 
@@ -859,8 +865,9 @@ class DUP_Util
859
  */
860
  public static function isExecutable($cmd)
861
  {
862
- if (strlen($cmd) < 1)
863
  return false;
 
864
 
865
  if (@is_executable($cmd)) {
866
  return true;
@@ -871,7 +878,7 @@ class DUP_Util
871
  return true;
872
  }
873
 
874
- $output = shell_exec($cmd.' -?');
875
  if (!is_null($output)) {
876
  return true;
877
  }
@@ -882,7 +889,7 @@ class DUP_Util
882
  /**
883
  * Display human readable byte sizes
884
  *
885
- * @param string $size The size in bytes
886
  *
887
  * @return string Human readable bytes such as 50MB, 1GB
888
  */
@@ -890,11 +897,11 @@ class DUP_Util
890
  {
891
  try {
892
  $units = array('B', 'KB', 'MB', 'GB', 'TB');
893
- for ($i = 0; $size >= 1024 && $i < 4; $i++)
894
- $size /= 1024;
895
- return round($size, 2).$units[$i];
896
- }
897
- catch (Exception $e) {
898
  return "n/a";
899
  }
900
  }
@@ -935,22 +942,4 @@ class DUP_Util
935
  {
936
  return function_exists($function_name) && !in_array($function_name, self::getIniDisableFuncs());
937
  }
938
-
939
- /**
940
- * Is the web server IIS
941
- *
942
- * @return bool Returns true if web server is IIS
943
- */
944
- public static function isIISRunning()
945
- {
946
- if (isset($_SERVER["SERVER_SOFTWARE"])) {
947
- $sSoftware = strtolower($_SERVER["SERVER_SOFTWARE"]);
948
- if ( strpos($sSoftware, "microsoft-iis") !== false ) {
949
- return true;
950
- } else {
951
- return false;
952
- }
953
- }
954
- return false;
955
- }
956
- }
1
  <?php
 
2
 
3
+ use Duplicator\Libs\Snap\SnapIO;
4
 
5
  /**
6
+ * Recursivly scans a directory and finds all sym-links and unreadable files
7
  *
8
  * Standard: PSR-2
9
  * @link http://www.php-fig.org/psr/psr-2
12
  * @subpackage classes/utilities
13
  * @copyright (c) 2017, Snapcreek LLC
14
  *
15
+ * @todo Refactor out IO methods into class.io.php file
16
  */
17
+
18
  class DUP_Util
19
  {
 
 
 
 
 
20
  /**
21
  * Is PHP 5.2.9 or better running
22
  */
49
  */
50
  public static function init()
51
  {
52
+ /** @todo Remove the static init method in favor of always consistent values */
53
+ self::$on_php_529_plus = version_compare(PHP_VERSION, '5.2.9') >= 0;
54
+ self::$on_php_53_plus = version_compare(PHP_VERSION, '5.3.0') >= 0;
55
+ self::$on_php_54_plus = version_compare(PHP_VERSION, '5.4.0') >= 0;
56
+ self::$PHP7_plus = version_compare(PHP_VERSION, '7.0.0', '>=');
57
  }
58
 
59
  public static function getArchitectureString()
60
  {
61
+ switch (PHP_INT_SIZE) {
 
 
62
  case 4:
63
  return esc_html__('32-bit', 'duplicator');
64
  break;
82
 
83
  public static function getWPCoreDirs()
84
  {
85
+ $wp_core_dirs = array(get_home_path() . 'wp-admin', get_home_path() . 'wp-includes');
86
 
87
  //if wp_content is overrided
88
+ $wp_path = get_home_path() . "wp-content";
89
+ if (get_home_path() . 'wp-content' != WP_CONTENT_DIR) {
90
  $wp_path = WP_CONTENT_DIR;
91
  }
92
  $wp_path = str_replace("\\", "/", $wp_path);
93
 
94
  $wp_core_dirs[] = $wp_path;
95
+ $wp_core_dirs[] = $wp_path . '/plugins';
96
+ $wp_core_dirs[] = $wp_path . '/themes';
97
 
98
  return $wp_core_dirs;
99
  }
104
  */
105
  public static function getWPCoreFiles()
106
  {
107
+ $wp_cored_dirs = array(get_home_path() . 'wp-config.php');
108
  return $wp_cored_dirs;
109
  }
110
 
128
  trigger_error('array_group_by(): The key should be a string, an integer, or a callback', E_USER_ERROR);
129
  return null;
130
  }
131
+ $func = (!is_string($key) && is_callable($key) ? $key : null);
132
+ $_key = $key;
133
  // Load the new array, splitting by the target key
134
  $grouped = array();
135
  foreach ($array as $value) {
188
  {
189
  // Open file
190
  $f = @fopen($filepath, "rb");
191
+ if ($f === false) {
192
  return false;
193
+ }
194
 
195
  // Sets buffer size
196
  $buffer = 256;
200
 
201
  // Read it and adjust line number if necessary
202
  // (Otherwise the result would be wrong if file doesn't end with a blank line)
203
+ if (fread($f, 1) != "\n") {
204
  $lines -= 1;
205
+ }
206
 
207
  // Start reading
208
  $output = '';
211
  // While we would like more
212
  while (ftell($f) > 0 && $lines >= 0) {
213
  // Figure out how far back we should jump
214
+ $seek = min(ftell($f), $buffer);
215
  // Do the jump (backwards, relative to where we are)
216
  fseek($f, -$seek, SEEK_CUR);
217
  // Read a chunk and prepend it to our output
218
+ $output = ($chunk = fread($f, $seek)) . $output;
219
  // Jump back to where we started reading
220
  fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR);
221
  // Decrease our line counter
222
+ $lines -= substr_count($chunk, "\n");
223
  }
224
 
225
  // While we have too many lines
246
  for ($i = 0; $size >= 1024 && $i < 4; $i++) {
247
  $size /= 1024;
248
  }
249
+ return round($size, $roundBy) . $units[$i];
250
+ } catch (Exception $e) {
 
251
  return "n/a";
252
  }
253
  }
258
  * uni: /home/path/file.txt
259
  * win: D:/home/path/file.txt
260
  *
261
+ * @param string $path The path to make safe
262
  *
263
  * @return string A path with all slashes facing "/"
264
  */
289
  */
290
  public static function appendOnce($string, $value)
291
  {
292
+ return $string . (substr($string, -1) == $value ? '' : $value);
293
  }
294
 
295
  /**
315
  * @return array of all files in that path
316
  *
317
  * Notes:
318
+ * - Avoid using glob() as GLOB_BRACE is not an option on some operating systems
319
+ * - Pre PHP 5.3 DirectoryIterator will crash on unreadable files
320
  * - Scandir will not crash on unreadable items, but will not return results
321
  */
322
  public static function listFiles($path = '.')
325
  $files = array();
326
  if ($dh = opendir($path)) {
327
  while (($file = readdir($dh)) !== false) {
328
+ if ($file == '.' || $file == '..') {
329
  continue;
330
+ }
331
+ $full_file_path = trailingslashit($path) . $file;
332
  $files[] = str_replace("\\", '/', $full_file_path);
333
  }
334
  @closedir($dh);
335
  }
336
  return $files;
337
+ } catch (Exception $exc) {
 
338
  $result = array();
339
  $files = @scandir($path);
340
  if (is_array($files)) {
341
  foreach ($files as $file) {
342
+ $result[] = str_replace("\\", '/', $path) . $file;
343
  }
344
  }
345
  return $result;
374
  */
375
  public static function isDirectoryEmpty($path)
376
  {
377
+ if (!is_readable($path)) {
378
+ return null;
379
+ }
380
  return (count(scandir($path)) == 2);
381
  }
382
 
390
  */
391
  public static function getDirectorySize($path)
392
  {
393
+ if (!file_exists($path)) {
394
  return 0;
395
+ }
396
+ if (is_file($path)) {
397
  return filesize($path);
398
+ }
399
 
400
  $size = 0;
401
+ $list = glob($path . "/*");
402
  if (!empty($list)) {
403
+ foreach ($list as $file) {
404
  $size += self::getDirectorySize($file);
405
+ }
406
  }
407
  return $size;
408
  }
418
  $cmds = array('shell_exec', 'escapeshellarg', 'escapeshellcmd', 'extension_loaded');
419
 
420
  //Function disabled at server level
421
+ if (array_intersect($cmds, array_map('trim', explode(',', @ini_get('disable_functions'))))) {
422
  return apply_filters('duplicator_is_shellzip_available', false);
423
+ }
424
 
425
  //Suhosin: http://www.hardened-php.net/suhosin/
426
  //Will cause PHP to silently fail
427
  if (extension_loaded('suhosin')) {
428
  $suhosin_ini = @ini_get("suhosin.executor.func.blacklist");
429
+ if (array_intersect($cmds, array_map('trim', explode(',', $suhosin_ini)))) {
430
  return apply_filters('duplicator_is_shellzip_available', false);
431
+ }
432
  }
433
 
434
  if (! function_exists('shell_exec')) {
435
+ return apply_filters('duplicator_is_shellzip_available', false);
436
+ }
437
 
438
  // Can we issue a simple echo command?
439
+ if (!@shell_exec('echo duplicator')) {
440
  return apply_filters('duplicator_is_shellzip_available', false);
441
+ }
442
 
443
  return apply_filters('duplicator_is_shellzip_available', true);
444
  }
482
  {
483
  return base64_encode($string);
484
  }
485
+ const SECURE_ISSUE_DIE = 'die';
486
+ const SECURE_ISSUE_THROW = 'throw';
487
+ const SECURE_ISSUE_RETURN = 'return';
488
 
489
  /**
490
  * Does the current user have the capability
505
 
506
  if (!current_user_can($capability)) {
507
  $exitMsg = __('You do not have sufficient permissions to access this page.', 'duplicator');
508
+ DUP_LOG::Trace('You do not have sufficient permissions to access this page. PERMISSION: ' . $permission);
509
 
510
  switch ($exit) {
511
  case self::SECURE_ISSUE_THROW:
555
  }
556
 
557
  return strlen($user) ? $user : $unreadable;
558
+ } catch (Exception $ex) {
 
559
  return $unreadable;
560
  }
561
  }
567
  */
568
  public static function initSnapshotDirectory()
569
  {
570
+ $error = false;
571
+
572
  $path_wproot = duplicator_get_abs_path();
573
+ $path_ssdir = DUP_Settings::getSsdirPath();
 
574
 
575
+ if (!file_exists($path_ssdir)) {
 
 
 
576
  $old_root_perm = @fileperms($path_wproot);
577
 
578
+ //--------------------------------
579
  //CHMOD DIRECTORY ACCESS
580
  //wordpress root directory
581
+ SnapIO::chmod($path_wproot, 'u+rwx');
582
 
583
  //snapshot directory
584
+ if (SnapIO::dirWriteCheckOrMkdir($path_ssdir, 'u+rwx,go+rx') == false) {
585
  $error = true;
586
  }
587
 
588
  // restore original root perms
589
+ SnapIO::chmod($path_wproot, $old_root_perm);
590
 
591
  if ($error) {
592
  return false;
593
  }
594
  }
595
 
596
+ SnapIO::chmod($path_ssdir, 'u+rwx,go+rx');
597
+ SnapIO::dirWriteCheckOrMkdir(DUP_Settings::getSsdirTmpPath(), 'u+rwx');
598
 
599
  //--------------------------------
600
  //FILE CREATION & HARDEN PROCESS
601
  //index.php, .htaccess, robots.txt
602
+ self::setupBackupDirIndexFile();
603
+ self::setupBackupDirRobotsFile();
604
+ self::setupBackupDirHtaccess();
605
+ self::performHardenProcesses();
606
 
607
  return true;
608
  }
610
  /**
611
  * Attempts to create a secure .htaccess file in the download directory
612
  *
613
+ * @return void
614
  */
615
+ protected static function setupBackupDirHtaccess()
616
  {
617
  try {
618
  $storageHtaccessOff = DUP_Settings::Get('storage_htaccess_off');
619
+ $fileName = DUP_Settings::getSsdirPath() . '/.htaccess';
620
 
621
  if ($storageHtaccessOff) {
622
  @unlink($fileName);
623
+ } elseif (!file_exists($fileName)) {
624
+ $fileContent = <<<HTACCESS
625
+ Options -Indexes
626
+ <Files *.php>\n deny from all\n</Files>
627
+ HTACCESS;
628
+ if (file_put_contents($fileName, $fileContent) === false) {
629
+ throw new Exception('Can\'t create .haccess');
630
  }
631
+ }
632
  } catch (Exception $ex) {
633
+ DUP_Log::Trace("Unable create file htaccess {$fileName} msg:" . $ex->getMessage());
634
  }
635
  }
636
 
637
  /**
638
  * Attempts to create an index.php file in the backups directory
639
  *
640
+ * @return void
641
  */
642
+ protected static function setupBackupDirIndexFile()
643
  {
644
  try {
645
+ $fileName = DUP_Settings::getSsdirPath() . '/index.php';
646
  if (!file_exists($fileName)) {
647
+ $fileContent = <<<HTACCESS
648
+ <?php
649
+ // silence;
650
+ HTACCESS;
651
+ if (file_put_contents($fileName, $fileContent) === false) {
652
+ throw new Exception('Can\'t create .haccess');
653
  }
654
  }
655
  } catch (Exception $ex) {
656
+ DUP_Log::Trace("Unable create index.php {$fileName} msg:" . $ex->getMessage());
657
  }
658
  }
659
 
661
  * Attempts to create a robots.txt file in the backups directory
662
  * to prevent search engines
663
  *
664
+ * @return void
665
  */
666
+ protected static function setupBackupDirRobotsFile()
667
  {
668
  try {
669
+ $fileName = DUP_Settings::getSsdirPath() . '/robots.txt';
670
  if (!file_exists($fileName)) {
671
+ $fileContent = <<<HTACCESS
672
+ User-agent: *
673
+ Disallow: /
674
+ HTACCESS;
675
+ if (file_put_contents($fileName, $fileContent) === false) {
676
+ throw new Exception('Can\'t create .haccess');
 
677
  }
678
  }
679
  } catch (Exception $ex) {
680
+ DUP_Log::Trace("Unable create robots.txt {$fileName} msg:" . $ex->getMessage());
681
  }
682
  }
683
 
684
+
685
  /**
686
  * Run various secure processes to harden the backups dir
687
  *
688
+ * @return void
689
  */
690
+ public static function performHardenProcesses()
691
  {
692
+ $backupsDir = DUP_Settings::getSsdirPath();
693
+
694
+ //Edge Case: Remove any main.installer.php files
695
  $dupInstallFile = "{$backupsDir}/dup-installer/main.installer.php";
696
  if (file_exists($dupInstallFile) ) {
697
+ SnapIO::chmod($dupInstallFile, "a+w");
698
+ SnapIO::unlink("{$dupInstallFile}");
699
  }
700
 
701
  //Rename installer php files to .bak
702
+ SnapIO::regexGlobCallback(
703
  $backupsDir,
704
  function ($path) {
705
+ $parts = pathinfo($path);
706
  $newPath = $parts['dirname'] . '/' . $parts['filename'] . DUP_Installer::INSTALLER_SERVER_EXTENSION;
707
+ SnapIO::rename($path, $newPath);
708
  },
709
  array(
710
  'regexFile' => '/^.+_installer.*\.php$/',
724
  $filepath = null;
725
 
726
  if (self::hasShellExec()) {
727
+ if (shell_exec('hash zip 2>&1') == null) {
728
  $filepath = 'zip';
729
  } else {
730
  $possible_paths = array(
765
  global $wpdb;
766
  $result = array();
767
  foreach (self::getWPCoreTablesEnd() as $tend) {
768
+ $result[] = $wpdb->prefix . $tend;
769
  }
770
  return $result;
771
  }
808
 
809
  if (in_array($subTName, $coreEnds)) {
810
  return true;
811
+ } elseif (is_multisite()) {
812
  $exTable = explode('_', $subTName);
813
  if (count($exTable) >= 2 && is_numeric($exTable[0])) {
814
  $tChekc = implode('_', array_slice($exTable, 1));
840
 
841
  /**
842
  * Check given table is exist in real
843
+ *
844
  * @param $table string Table name
845
  * @return booleam
846
  */
848
  {
849
  // It will clear the $GLOBALS['wpdb']->last_error var
850
  $GLOBALS['wpdb']->flush();
851
+ $sql = "SELECT 1 FROM `" . esc_sql($table) . "` LIMIT 1;";
852
  $ret = $GLOBALS['wpdb']->get_var($sql);
853
+ if (empty($GLOBALS['wpdb']->last_error)) {
854
  return true;
855
+ }
856
  return false;
857
  }
858
 
865
  */
866
  public static function isExecutable($cmd)
867
  {
868
+ if (strlen($cmd) < 1) {
869
  return false;
870
+ }
871
 
872
  if (@is_executable($cmd)) {
873
  return true;
878
  return true;
879
  }
880
 
881
+ $output = shell_exec($cmd . ' -?');
882
  if (!is_null($output)) {
883
  return true;
884
  }
889
  /**
890
  * Display human readable byte sizes
891
  *
892
+ * @param string $size The size in bytes
893
  *
894
  * @return string Human readable bytes such as 50MB, 1GB
895
  */
897
  {
898
  try {
899
  $units = array('B', 'KB', 'MB', 'GB', 'TB');
900
+ for ($i = 0; $size >= 1024 && $i < 4; $i++) {
901
+ $size /= 1024;
902
+ }
903
+ return round($size, 2) . $units[$i];
904
+ } catch (Exception $e) {
905
  return "n/a";
906
  }
907
  }
942
  {
943
  return function_exists($function_name) && !in_array($function_name, self::getIniDisableFuncs());
944
  }
945
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classes/utilities/class.u.scancheck.php CHANGED
@@ -1,181 +1,169 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- /**
4
- * Recursivly scans a directory and finds all sym-links and unreadable files
5
- *
6
- * Standard: PSR-2
7
- * @link http://www.php-fig.org/psr/psr-2
8
- *
9
- * @package Duplicator
10
- * @subpackage classes/utilities
11
- * @copyright (c) 2017, Snapcreek LLC
12
- *
13
- */
14
-
15
- // Exit if accessed directly
16
- if (! defined('DUPLICATOR_VERSION')) exit;
17
-
18
- class DUP_ScanCheck
19
- {
20
- /**
21
- * The number of files scanned
22
- */
23
- public $fileCount = 0;
24
-
25
- /**
26
- * The number of directories scanned
27
- */
28
- public $dirCount = 0;
29
-
30
- /**
31
- * The maximum count of files before the recursive function stops
32
- */
33
- public $maxFiles = 1000000;
34
-
35
- /**
36
- * The maximum count of directories before the recursive function stops
37
- */
38
- public $maxDirs = 75000;
39
-
40
- /**
41
- * Recursivly scan the root directory provided
42
- */
43
- public $recursion = true;
44
-
45
- /**
46
- * Stores a list of symbolic link files
47
- */
48
- public $symLinks = array();
49
-
50
- /**
51
- * Stores a list of files unreadable by PHP
52
- */
53
- public $unreadable = array();
54
-
55
- /**
56
- * Stores a list of dirs with utf8 settings
57
- */
58
- public $nameTestDirs = array();
59
-
60
- /**
61
- * Stores a list of files with utf8 settings
62
- */
63
- public $nameTestFiles = array();
64
-
65
- /**
66
- * If the maxFiles or maxDirs limit is reached then true
67
- */
68
- protected $limitReached = false;
69
-
70
- /**
71
- * Is the server running on Windows
72
- */
73
- private $isWindows = false;
74
-
75
- /**
76
- * Init this instance of the object
77
- */
78
- function __construct()
79
- {
80
- $this->isWindows = defined('PHP_WINDOWS_VERSION_BUILD');
81
- }
82
-
83
- /**
84
- * Start the scan process
85
- *
86
- * @param string $dir A valid directory path where the scan will run
87
- * @param array $results Used for recursion, do not pass in value with calling
88
- *
89
- * @return obj The scan check object with the results of the scan
90
- */
91
- public function run($dir, &$results = array())
92
- {
93
- //Stop Recursion if Max search is reached
94
- if ($this->fileCount > $this->maxFiles || $this->dirCount > $this->maxDirs) {
95
- $this->limitReached = true;
96
- return $results;
97
- }
98
-
99
- $files = @scandir($dir);
100
- if (is_array($files)) {
101
- foreach ($files as $key => $value) {
102
- $path = realpath($dir.DIRECTORY_SEPARATOR.$value);
103
- if ($path) {
104
- //Files
105
- if (!is_dir($path)) {
106
- if (!is_readable($path)) {
107
- $results[] = $path;
108
- $this->unreadable[] = $path;
109
- } else if ($this->isLink($path)) {
110
- $results[] = $path;
111
- $this->symLinks[] = $path;
112
- } else {
113
- $name = basename($path);
114
- $invalid_test = preg_match('/(\/|\*|\?|\>|\<|\:|\\|\|)/', $name)
115
- || trim($name) == ''
116
- || (strrpos($name, '.') == strlen($name) - 1 && substr($name, -1) == '.')
117
- || preg_match('/[^\x20-\x7f]/', $name);
118
-
119
- if ($invalid_test) {
120
- if (! DUP_Util::$PHP7_plus && DUP_Util::isWindows()) {
121
- $this->nameTestFiles[] = utf8_decode($path);
122
- } else {
123
- $this->nameTestFiles[] = $path;
124
- }
125
- }
126
- }
127
- $this->fileCount++;
128
- }
129
- //Dirs
130
- else if ($value != "." && $value != "..") {
131
- if (!$this->isLink($path) && $this->recursion) {
132
- $this->Run($path, $results);
133
- }
134
-
135
- if (!is_readable($path)) {
136
- $results[] = $path;
137
- $this->unreadable[] = $path;
138
- } else if ($this->isLink($path)) {
139
- $results[] = $path;
140
- $this->symLinks[] = $path;
141
- } else {
142
-
143
- $invalid_test = strlen($path) > 244
144
- || trim($path) == ''
145
- || preg_match('/[^\x20-\x7f]/', $path);
146
-
147
- if ($invalid_test) {
148
- if (! DUP_Util::$PHP7_plus && DUP_Util::isWindows()) {
149
- $this->nameTestDirs[] = utf8_decode($path);
150
- } else {
151
- $this->nameTestDirs[] = $path;
152
- }
153
- }
154
- }
155
-
156
- $this->dirCount++;
157
- }
158
- }
159
- }
160
- }
161
- return $this;
162
- }
163
-
164
- /**
165
- * Separation logic for supporting how different operating systems work
166
- *
167
- * @param string $target A valid file path
168
- *
169
- * @return bool Is the target a sym link
170
- */
171
- private function isLink($target)
172
- {
173
- //Currently Windows does not support sym-link detection
174
- if ($this->isWindows) {
175
- return false;
176
- } elseif (is_link($target)) {
177
- return true;
178
- }
179
- return false;
180
- }
181
- }
1
+ <?php
2
+
3
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
4
+ /**
5
+ * Recursivly scans a directory and finds all sym-links and unreadable files
6
+ *
7
+ * Standard: PSR-2
8
+ * @link http://www.php-fig.org/psr/psr-2
9
+ *
10
+ * @package Duplicator
11
+ * @subpackage classes/utilities
12
+ * @copyright (c) 2017, Snapcreek LLC
13
+ *
14
+ */
15
+
16
+ // Exit if accessed directly
17
+ if (! defined('DUPLICATOR_VERSION')) {
18
+ exit;
19
+ }
20
+
21
+ class DUP_ScanCheck
22
+ {
23
+ /**
24
+ * The number of files scanned
25
+ */
26
+ public $fileCount = 0;
27
+ /**
28
+ * The number of directories scanned
29
+ */
30
+ public $dirCount = 0;
31
+ /**
32
+ * The maximum count of files before the recursive function stops
33
+ */
34
+ public $maxFiles = 1000000;
35
+ /**
36
+ * The maximum count of directories before the recursive function stops
37
+ */
38
+ public $maxDirs = 75000;
39
+ /**
40
+ * Recursivly scan the root directory provided
41
+ */
42
+ public $recursion = true;
43
+ /**
44
+ * Stores a list of symbolic link files
45
+ */
46
+ public $symLinks = array();
47
+ /**
48
+ * Stores a list of files unreadable by PHP
49
+ */
50
+ public $unreadable = array();
51
+ /**
52
+ * Stores a list of dirs with utf8 settings
53
+ */
54
+ public $nameTestDirs = array();
55
+ /**
56
+ * Stores a list of files with utf8 settings
57
+ */
58
+ public $nameTestFiles = array();
59
+ /**
60
+ * If the maxFiles or maxDirs limit is reached then true
61
+ */
62
+ protected $limitReached = false;
63
+ /**
64
+ * Is the server running on Windows
65
+ */
66
+ private $isWindows = false;
67
+ /**
68
+ * Init this instance of the object
69
+ */
70
+ public function __construct()
71
+ {
72
+ $this->isWindows = defined('PHP_WINDOWS_VERSION_BUILD');
73
+ }
74
+
75
+ /**
76
+ * Start the scan process
77
+ *
78
+ * @param string $dir A valid directory path where the scan will run
79
+ * @param array $results Used for recursion, do not pass in value with calling
80
+ *
81
+ * @return obj The scan check object with the results of the scan
82
+ */
83
+ public function run($dir, &$results = array())
84
+ {
85
+ //Stop Recursion if Max search is reached
86
+ if ($this->fileCount > $this->maxFiles || $this->dirCount > $this->maxDirs) {
87
+ $this->limitReached = true;
88
+ return $results;
89
+ }
90
+
91
+ $files = @scandir($dir);
92
+ if (is_array($files)) {
93
+ foreach ($files as $key => $value) {
94
+ $path = realpath($dir . DIRECTORY_SEPARATOR . $value);
95
+ if ($path) {
96
+ //Files
97
+ if (!is_dir($path)) {
98
+ if (!is_readable($path)) {
99
+ $results[] = $path;
100
+ $this->unreadable[] = $path;
101
+ } elseif ($this->isLink($path)) {
102
+ $results[] = $path;
103
+ $this->symLinks[] = $path;
104
+ } else {
105
+ $name = basename($path);
106
+ $invalid_test = preg_match('/(\/|\*|\?|\>|\<|\:|\\|\|)/', $name)
107
+ || trim($name) == ''
108
+ || (strrpos($name, '.') == strlen($name) - 1 && substr($name, -1) == '.')
109
+ || preg_match('/[^\x20-\x7f]/', $name);
110
+ if ($invalid_test) {
111
+ if (! DUP_Util::$PHP7_plus && DUP_Util::isWindows()) {
112
+ $this->nameTestFiles[] = utf8_decode($path);
113
+ } else {
114
+ $this->nameTestFiles[] = $path;
115
+ }
116
+ }
117
+ }
118
+ $this->fileCount++;
119
+ } elseif ($value != "." && $value != "..") {
120
+ //Dirs
121
+ if (!$this->isLink($path) && $this->recursion) {
122
+ $this->Run($path, $results);
123
+ }
124
+
125
+ if (!is_readable($path)) {
126
+ $results[] = $path;
127
+ $this->unreadable[] = $path;
128
+ } elseif ($this->isLink($path)) {
129
+ $results[] = $path;
130
+ $this->symLinks[] = $path;
131
+ } else {
132
+ $invalid_test = strlen($path) > 244
133
+ || trim($path) == ''
134
+ || preg_match('/[^\x20-\x7f]/', $path);
135
+ if ($invalid_test) {
136
+ if (! DUP_Util::$PHP7_plus && DUP_Util::isWindows()) {
137
+ $this->nameTestDirs[] = utf8_decode($path);
138
+ } else {
139
+ $this->nameTestDirs[] = $path;
140
+ }
141
+ }
142
+ }
143
+
144
+ $this->dirCount++;
145
+ }
146
+ }
147
+ }
148
+ }
149
+ return $this;
150
+ }
151
+
152
+ /**
153
+ * Separation logic for supporting how different operating systems work
154
+ *
155
+ * @param string $target A valid file path
156
+ *
157
+ * @return bool Is the target a sym link
158
+ */
159
+ private function isLink($target)
160
+ {
161
+ //Currently Windows does not support sym-link detection
162
+ if ($this->isWindows) {
163
+ return false;
164
+ } elseif (is_link($target)) {
165
+ return true;
166
+ }
167
+ return false;
168
+ }
169
+ }
 
 
 
 
 
 
 
 
 
 
 
 
classes/utilities/class.u.shell.php CHANGED
@@ -1,45 +1,49 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- // Exit if accessed directly
4
- if (! defined('DUPLICATOR_VERSION')) exit;
5
-
6
- class DUP_Shell_U
7
- {
8
- /**
9
- * Escape a string to be used as a shell argument with bypass support for Windows
10
- *
11
- * NOTES:
12
- * Provides a way to support shell args on Windows OS and allows %,! on Windows command line
13
- * Safe if input is know such as a defined constant and not from user input escape shellarg
14
- * on Windows with turn %,! into spaces
15
- *
16
- * @return string
17
- */
18
- public static function escapeshellargWindowsSupport($string)
19
- {
20
- if (strncasecmp(PHP_OS, 'WIN', 3) == 0) {
21
- if (strstr($string, '%') || strstr($string, '!')) {
22
- $result = '"'.str_replace('"', '', $string).'"';
23
- return $result;
24
- }
25
- }
26
- return escapeshellarg($string);
27
- }
28
-
29
- /**
30
- *
31
- * @return boolean
32
- *
33
- */
34
- public static function isPopenEnabled() {
35
-
36
- if (!DUP_Util::isIniFunctionEnalbe('popen') || !DUP_Util::isIniFunctionEnalbe('proc_open')) {
37
- $ret = false;
38
- } else {
39
- $ret = true;
40
- }
41
-
42
- $ret = apply_filters('duplicator_is_popen_enabled', $ret);
43
- return $ret;
44
- }
45
- }
 
 
 
 
1
+ <?php
2
+
3
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
4
+ // Exit if accessed directly
5
+ if (! defined('DUPLICATOR_VERSION')) {
6
+ exit;
7
+ }
8
+
9
+ class DUP_Shell_U
10
+ {
11
+ /**
12
+ * Escape a string to be used as a shell argument with bypass support for Windows
13
+ *
14
+ * NOTES:
15
+ * Provides a way to support shell args on Windows OS and allows %,! on Windows command line
16
+ * Safe if input is know such as a defined constant and not from user input escape shellarg
17
+ * on Windows with turn %,! into spaces
18
+ *
19
+ * @return string
20
+ */
21
+ public static function escapeshellargWindowsSupport($string)
22
+ {
23
+ if (strncasecmp(PHP_OS, 'WIN', 3) == 0) {
24
+ if (strstr($string, '%') || strstr($string, '!')) {
25
+ $result = '"' . str_replace('"', '', $string) . '"';
26
+ return $result;
27
+ }
28
+ }
29
+ return escapeshellarg($string);
30
+ }
31
+
32
+ /**
33
+ *
34
+ * @return boolean
35
+ *
36
+ */
37
+ public static function isPopenEnabled()
38
+ {
39
+
40
+ if (!DUP_Util::isIniFunctionEnalbe('popen') || !DUP_Util::isIniFunctionEnalbe('proc_open')) {
41
+ $ret = false;
42
+ } else {
43
+ $ret = true;
44
+ }
45
+
46
+ $ret = apply_filters('duplicator_is_popen_enabled', $ret);
47
+ return $ret;
48
+ }
49
+ }
classes/utilities/class.u.string.php CHANGED
@@ -1,99 +1,96 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- /**
4
- * Utility class working with strings
5
- *
6
- * Standard: PSR-2
7
- * @link http://www.php-fig.org/psr/psr-2
8
- *
9
- * @package DUP
10
- * @subpackage classes/utilities
11
- * @copyright (c) 2017, Snapcreek LLC
12
- * @license https://opensource.org/licenses/GPL-3.0 GNU Public License
13
- *
14
- */
15
- class DUP_STR
16
- {
17
-
18
- /**
19
- * Append the value to the string if it doesn't already exist
20
- *
21
- * @param string $string The string to append to
22
- * @param string $value The string to append to the $string
23
- *
24
- * @return string Returns the string with the $value appended once
25
- */
26
- public static function appendOnce($string, $value)
27
- {
28
- return $string.(substr($string, -1) == $value ? '' : $value);
29
- }
30
-
31
- /**
32
- * Returns true if the string contains UTF8 characters
33
- * @see http://php.net/manual/en/function.mb-detect-encoding.php
34
- *
35
- * @param string $string The class name where the $destArray exists
36
- *
37
- * @return null
38
- */
39
- public static function hasUTF8($string)
40
- {
41
- return preg_match('%(?:
42
- [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
43
- |\xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
44
- |[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
45
- |\xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
46
- |\xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
47
- |[\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
48
- |\xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
49
- )+%xs', $string);
50
- }
51
-
52
- /**
53
- * Returns true if the $needle is found in the $haystack
54
- *
55
- * @param string $haystack The full string to search in
56
- * @param string $needle The string to for
57
- *
58
- * @return bool
59
- */
60
- public static function contains($haystack, $needle)
61
- {
62
- $pos = strpos($haystack, $needle);
63
- return ($pos !== false);
64
- }
65
-
66
- /**
67
- * Returns true if the $haystack string starts with the $needle
68
- *
69
- * @param string $haystack The full string to search in
70
- * @param string $needle The string to for
71
- *
72
- * @return bool Returns true if the $haystack string starts with the $needle
73
- */
74
- public static function startsWith($haystack, $needle)
75
- {
76
- $length = strlen($needle);
77
- return (substr($haystack, 0, $length) === $needle);
78
- }
79
-
80
- /**
81
- * Returns true if the $haystack string ends with the $needle
82
- *
83
- * @param string $haystack The full string to search in
84
- * @param string $needle The string to for
85
- *
86
- * @return bool Returns true if the $haystack string ends with the $needle
87
- */
88
- public static function endsWith($haystack, $needle)
89
- {
90
- $length = strlen($needle);
91
- if ($length == 0) {
92
- return true;
93
- }
94
- return (substr($haystack, -$length) === $needle);
95
- }
96
-
97
-
98
- }
99
-
1
+ <?php
2
+
3
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
4
+ /**
5
+ * Utility class working with strings
6
+ *
7
+ * Standard: PSR-2
8
+ * @link http://www.php-fig.org/psr/psr-2
9
+ *
10
+ * @package DUP
11
+ * @subpackage classes/utilities
12
+ * @copyright (c) 2017, Snapcreek LLC
13
+ * @license https://opensource.org/licenses/GPL-3.0 GNU Public License
14
+ *
15
+ */
16
+ class DUP_STR
17
+ {
18
+ /**
19
+ * Append the value to the string if it doesn't already exist
20
+ *
21
+ * @param string $string The string to append to
22
+ * @param string $value The string to append to the $string
23
+ *
24
+ * @return string Returns the string with the $value appended once
25
+ */
26
+ public static function appendOnce($string, $value)
27
+ {
28
+ return $string . (substr($string, -1) == $value ? '' : $value);
29
+ }
30
+
31
+ /**
32
+ * Returns true if the string contains UTF8 characters
33
+ * @see http://php.net/manual/en/function.mb-detect-encoding.php
34
+ *
35
+ * @param string $string The class name where the $destArray exists
36
+ *
37
+ * @return null
38
+ */
39
+ public static function hasUTF8($string)
40
+ {
41
+ return preg_match('%(?:
42
+ [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
43
+ |\xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
44
+ |[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
45
+ |\xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
46
+ |\xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
47
+ |[\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
48
+ |\xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
49
+ )+%xs', $string);
50
+ }
51
+
52
+ /**
53
+ * Returns true if the $needle is found in the $haystack
54
+ *
55
+ * @param string $haystack The full string to search in
56
+ * @param string $needle The string to for
57
+ *
58
+ * @return bool
59
+ */
60
+ public static function contains($haystack, $needle)
61
+ {
62
+ $pos = strpos($haystack, $needle);
63
+ return ($pos !== false);
64
+ }
65
+
66
+ /**
67
+ * Returns true if the $haystack string starts with the $needle
68
+ *
69
+ * @param string $haystack The full string to search in
70
+ * @param string $needle The string to for
71
+ *
72
+ * @return bool Returns true if the $haystack string starts with the $needle
73
+ */
74
+ public static function startsWith($haystack, $needle)
75
+ {
76
+ $length = strlen($needle);
77
+ return (substr($haystack, 0, $length) === $needle);
78
+ }
79
+
80
+ /**
81
+ * Returns true if the $haystack string ends with the $needle
82
+ *
83
+ * @param string $haystack The full string to search in
84
+ * @param string $needle The string to for
85
+ *
86
+ * @return bool Returns true if the $haystack string ends with the $needle
87
+ */
88
+ public static function endsWith($haystack, $needle)
89
+ {
90
+ $length = strlen($needle);
91
+ if ($length == 0) {
92
+ return true;
93
+ }
94
+ return (substr($haystack, -$length) === $needle);
95
+ }
96
+ }
 
 
 
classes/utilities/class.u.validator.php CHANGED
@@ -1,231 +1,220 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- /**
4
- * Validate variables
5
- *
6
- * Standard: PSR-2
7
- * @link http://www.php-fig.org/psr/psr-2
8
- *
9
- * @package Duplicator
10
- * @subpackage classes/utilities
11
- * @copyright (c) 2017, Snapcreek LLC
12
- *
13
- */
14
- // Exit if accessed directly
15
- if (!defined('DUPLICATOR_VERSION')) {
16
- exit;
17
- }
18
-
19
- class DUP_Validator
20
- {
21
- /**
22
- * @var array $patterns
23
- */
24
- private static $patterns = array(
25
- 'fdir' => '/^([a-zA-Z]:[\\\\\/]|\/|\\\\\\\\|\/\/)[^<>\0]+$/',
26
- 'ffile' => '/^([a-zA-Z]:[\\\\\/]|\/|\\\\\\\\|\/\/)[^<>\0]+$/',
27
- 'fext' => '/^\.?[^\\\\\/*:<>\0?"|\s\.]+$/',
28
- 'email' => '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_\`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/',
29
- 'empty' => '/^$/',
30
- 'nempty' => '/^.+$/',
31
- );
32
-
33
- const FILTER_VALIDATE_IS_EMPTY = 'empty';
34
- const FILTER_VALIDATE_NOT_EMPTY = 'nempty';
35
- const FILTER_VALIDATE_FILE = 'ffile';
36
- const FILTER_VALIDATE_FOLDER = 'fdir';
37
- const FILTER_VALIDATE_FILE_EXT = 'fext';
38
- const FILTER_VALIDATE_EMAIL = 'email';
39
-
40
- /**
41
- * @var array $errors [ ['key' => string field key,
42
- * 'msg' => error message ] , [] ]
43
- */
44
- private $errors = array();
45
-
46
- /**
47
- *
48
- */
49
- public function __construct()
50
- {
51
- $this->errors = array();
52
- }
53
-
54
- /**
55
- *
56
- */
57
- public function reset()
58
- {
59
- $this->errors = array();
60
- }
61
-
62
- /**
63
- *
64
- * @return bool
65
- */
66
- public function isSuccess()
67
- {
68
- return empty($this->errors);
69
- }
70
-
71
- /**
72
- *
73
- * @return array
74
- */
75
- public function getErrors()
76
- {
77
- return $this->errors;
78
- }
79
-
80
- /**
81
- *
82
- * @return array return errors messages
83
- */
84
- public function getErrorsMsg()
85
- {
86
- $result = array();
87
- foreach ($this->errors as $err) {
88
- $result[] = $err['msg'];
89
- }
90
- return $result;
91
- }
92
-
93
- /**
94
- *
95
- * @param string $format printf format message where %s is the variable content default "%s\n"
96
- * @param bool $echo if false return string
97
- * @return void|string
98
- */
99
- public function getErrorsFormat($format = "%s\n", $echo = true)
100
- {
101
- $msgs = $this->getErrorsMsg();
102
-
103
- ob_start();
104
- foreach ($msgs as $msg) {
105
- printf($format, $msg);
106
- }
107
-
108
- if ($echo) {
109
- ob_end_flush();
110
- } else {
111
- return ob_get_clean();
112
- }
113
- }
114
-
115
- /**
116
- *
117
- * @param string $key
118
- * @param string $msg
119
- */
120
- protected function addError($key, $msg)
121
- {
122
- $this->errors[] = array(
123
- 'key' => $key,
124
- 'msg' => $msg
125
- );
126
- }
127
-
128
- /**
129
- * filter_var function wrapper see http://php.net/manual/en/function.filter-var.php
130
- *
131
- * additional options
132
- * valkey => key of field
133
- * errmsg => error message; % s will be replaced with the contents of the variable es. "<b>%s</b> isn't a valid field"
134
- * acc_vals => array of accepted values that skip validation
135
- *
136
- * @param mixed $variable
137
- * @param int $filter
138
- * @param array $options
139
- * @return mixed
140
- */
141
- public function filter_var($variable, $filter = FILTER_DEFAULT, $options = array())
142
- {
143
- $success = true;
144
- $result = null;
145
-
146
- if (isset($options['acc_vals']) && in_array($variable, $options['acc_vals'])) {
147
- return $variable;
148
- }
149
-
150
- if ($filter === FILTER_VALIDATE_BOOLEAN) {
151
- $options['flags'] = FILTER_NULL_ON_FAILURE;
152
-
153
- $result = filter_var($variable, $filter, $options);
154
-
155
- if (is_null($result)) {
156
- $success = false;
157
- }
158
- } else {
159
- $result = filter_var($variable, $filter, $options);
160
-
161
- if ($result === false) {
162
- $success = false;
163
- }
164
- }
165
-
166
- if (!$success) {
167
- $key = isset($options['valkey']) ? $options['valkey'] : '';
168
-
169
- if (isset($options['errmsg'])) {
170
- $msg = sprintf($options['errmsg'], $variable);
171
- } else {
172
- $msg = sprintf('%1$s isn\'t a valid value', $variable);
173
- }
174
-
175
- $this->addError($key, $msg);
176
- }
177
-
178
- return $result;
179
- }
180
-
181
- /**
182
- * validation of predefined regular expressions
183
- *
184
- * @param mixed $variable
185
- * @param string $filter
186
- * @param array $options
187
- * @return type
188
- * @throws Exception
189
- */
190
- public function filter_custom($variable, $filter, $options = array())
191
- {
192
-
193
- if (!isset(self::$patterns[$filter])) {
194
- throw new Exception('Filter not valid');
195
- }
196
-
197
- $options = array_merge($options, array(
198
- 'options' => array(
199
- 'regexp' => self::$patterns[$filter])
200
- )
201
- );
202
-
203
- //$options['regexp'] = self::$patterns[$filter];
204
-
205
- return $this->filter_var($variable, FILTER_VALIDATE_REGEXP, $options);
206
- }
207
-
208
- /**
209
- * it explodes a string with a delimiter and validates every element of the array
210
- *
211
- * @param string $variable
212
- * @param string $delimiter
213
- * @param string $filter
214
- * @param array $options
215
- */
216
- public function explode_filter_custom($variable, $delimiter, $filter, $options = array())
217
- {
218
- if (empty($variable)) {
219
- return array();
220
- }
221
-
222
- $vals = explode($delimiter, trim($variable, $delimiter));
223
- $res = array();
224
-
225
- foreach ($vals as $val) {
226
- $res[] = $this->filter_custom($val, $filter, $options);
227
- }
228
-
229
- return $res;
230
- }
231
- }
1
+ <?php
2
+
3
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
4
+ /**
5
+ * Validate variables
6
+ *
7
+ * Standard: PSR-2
8
+ * @link http://www.php-fig.org/psr/psr-2
9
+ *
10
+ * @package Duplicator
11
+ * @subpackage classes/utilities
12
+ * @copyright (c) 2017, Snapcreek LLC
13
+ *
14
+ */
15
+ // Exit if accessed directly
16
+ if (!defined('DUPLICATOR_VERSION')) {
17
+ exit;
18
+ }
19
+
20
+ class DUP_Validator
21
+ {
22
+ /**
23
+ * @var array $patterns
24
+ */
25
+ private static $patterns = array(
26
+ 'fdir' => '/^([a-zA-Z]:[\\\\\/]|\/|\\\\\\\\|\/\/)[^<>\0]+$/',
27
+ 'ffile' => '/^([a-zA-Z]:[\\\\\/]|\/|\\\\\\\\|\/\/)[^<>\0]+$/',
28
+ 'fext' => '/^\.?[^\\\\\/*:<>\0?"|\s\.]+$/',
29
+ 'email' => '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_\`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/',
30
+ 'empty' => '/^$/',
31
+ 'nempty' => '/^.+$/',
32
+ );
33
+ const FILTER_VALIDATE_IS_EMPTY = 'empty';
34
+ const FILTER_VALIDATE_NOT_EMPTY = 'nempty';
35
+ const FILTER_VALIDATE_FILE = 'ffile';
36
+ const FILTER_VALIDATE_FOLDER = 'fdir';
37
+ const FILTER_VALIDATE_FILE_EXT = 'fext';
38
+ const FILTER_VALIDATE_EMAIL = 'email';
39
+ /**
40
+ * @var array $errors [ ['key' => string field key,
41
+ * 'msg' => error message ] , [] ]
42
+ */
43
+ private $errors = array();
44
+ /**
45
+ *
46
+ */
47
+ public function __construct()
48
+ {
49
+ $this->errors = array();
50
+ }
51
+
52
+ /**
53
+ *
54
+ */
55
+ public function reset()
56
+ {
57
+ $this->errors = array();
58
+ }
59
+
60
+ /**
61
+ *
62
+ * @return bool
63
+ */
64
+ public function isSuccess()
65
+ {
66
+ return empty($this->errors);
67
+ }
68
+
69
+ /**
70
+ *
71
+ * @return array
72
+ */
73
+ public function getErrors()
74
+ {
75
+ return $this->errors;
76
+ }
77
+
78
+ /**
79
+ *
80
+ * @return array return errors messages
81
+ */
82
+ public function getErrorsMsg()
83
+ {
84
+ $result = array();
85
+ foreach ($this->errors as $err) {
86
+ $result[] = $err['msg'];
87
+ }
88
+ return $result;
89
+ }
90
+
91
+ /**
92
+ *
93
+ * @param string $format printf format message where %s is the variable content default "%s\n"
94
+ * @param bool $echo if false return string
95
+ * @return void|string
96
+ */
97
+ public function getErrorsFormat($format = "%s\n", $echo = true)
98
+ {
99
+ $msgs = $this->getErrorsMsg();
100
+ ob_start();
101
+ foreach ($msgs as $msg) {
102
+ printf($format, $msg);
103
+ }
104
+
105
+ if ($echo) {
106
+ ob_end_flush();
107
+ } else {
108
+ return ob_get_clean();
109
+ }
110
+ }
111
+
112
+ /**
113
+ *
114
+ * @param string $key
115
+ * @param string $msg
116
+ */
117
+ protected function addError($key, $msg)
118
+ {
119
+ $this->errors[] = array(
120
+ 'key' => $key,
121
+ 'msg' => $msg
122
+ );
123
+ }
124
+
125
+ /**
126
+ * filter_var function wrapper see http://php.net/manual/en/function.filter-var.php
127
+ *
128
+ * additional options
129
+ * valkey => key of field
130
+ * errmsg => error message; % s will be replaced with the contents of the variable es. "<b>%s</b> isn't a valid field"
131
+ * acc_vals => array of accepted values that skip validation
132
+ *
133
+ * @param mixed $variable
134
+ * @param int $filter
135
+ * @param array $options
136
+ * @return mixed
137
+ */
138
+ public function filter_var($variable, $filter = FILTER_DEFAULT, $options = array())
139
+ {
140
+ $success = true;
141
+ $result = null;
142
+ if (isset($options['acc_vals']) && in_array($variable, $options['acc_vals'])) {
143
+ return $variable;
144
+ }
145
+
146
+ if ($filter === FILTER_VALIDATE_BOOLEAN) {
147
+ $options['flags'] = FILTER_NULL_ON_FAILURE;
148
+ $result = filter_var($variable, $filter, $options);
149
+ if (is_null($result)) {
150
+ $success = false;
151
+ }
152
+ } else {
153
+ $result = filter_var($variable, $filter, $options);
154
+ if ($result === false) {
155
+ $success = false;
156
+ }
157
+ }
158
+
159
+ if (!$success) {
160
+ $key = isset($options['valkey']) ? $options['valkey'] : '';
161
+ if (isset($options['errmsg'])) {
162
+ $msg = sprintf($options['errmsg'], $variable);
163
+ } else {
164
+ $msg = sprintf('%1$s isn\'t a valid value', $variable);
165
+ }
166
+
167
+ $this->addError($key, $msg);
168
+ }
169
+
170
+ return $result;
171
+ }
172
+
173
+ /**
174
+ * validation of predefined regular expressions
175
+ *
176
+ * @param mixed $variable
177
+ * @param string $filter
178
+ * @param array $options
179
+ * @return type
180
+ * @throws Exception
181
+ */
182
+ public function filter_custom($variable, $filter, $options = array())
183
+ {
184
+
185
+ if (!isset(self::$patterns[$filter])) {
186
+ throw new Exception('Filter not valid');
187
+ }
188
+
189
+ $options = array_merge($options, array(
190
+ 'options' => array(
191
+ 'regexp' => self::$patterns[$filter])
192
+ ));
193
+ //$options['regexp'] = self::$patterns[$filter];
194
+
195
+ return $this->filter_var($variable, FILTER_VALIDATE_REGEXP, $options);
196
+ }
197
+
198
+ /**
199
+ * it explodes a string with a delimiter and validates every element of the array
200
+ *
201
+ * @param string $variable
202
+ * @param string $delimiter
203
+ * @param string $filter
204
+ * @param array $options
205
+ */
206
+ public function explode_filter_custom($variable, $delimiter, $filter, $options = array())
207
+ {
208
+ if (empty($variable)) {
209
+ return array();
210
+ }
211
+
212
+ $vals = explode($delimiter, trim($variable, $delimiter));
213
+ $res = array();
214
+ foreach ($vals as $val) {
215
+ $res[] = $this->filter_custom($val, $filter, $options);
216
+ }
217
+
218
+ return $res;
219
+ }
220
+ }
 
 
 
 
 
 
 
 
 
 
 
classes/utilities/class.u.zip.php CHANGED
@@ -1,154 +1,160 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- /**
4
- * Utility class for zipping up content
5
- *
6
- * Standard: PSR-2
7
- * @link http://www.php-fig.org/psr/psr-2
8
- *
9
- * @subpackage classes/utilities
10
- * @copyright (c) 2017, Snapcreek LLC
11
- * @license https://opensource.org/licenses/GPL-3.0 GNU Public License
12
- */
13
-
14
- // Exit if accessed directly
15
- if (! defined('DUPLICATOR_VERSION')) exit;
16
-
17
- class DUP_Zip_U
18
- {
19
- /**
20
- * Add a directory to an existing ZipArchive object
21
- *
22
- * @param ZipArchive $zipArchive An existing ZipArchive object
23
- * @param string $directoryPath The full directory path to add to the ZipArchive
24
- * @param bool $retainDirectory Should the full directory path be retained in the archive
25
- *
26
- * @return bool Returns true if the directory was added to the object
27
- */
28
- public static function addDirWithZipArchive(&$zipArchive, $directoryPath, $retainDirectory, $localPrefix, $isCompressed)
29
- {
30
- $success = TRUE;
31
- $directoryPath = rtrim($directoryPath, '/\\').'/';
32
- if (!$fp = @opendir($directoryPath)) {
33
- return FALSE;
34
- }
35
- while (FALSE !== ($file = readdir($fp))) {
36
- if ($file === '.' || $file === '..') continue;
37
- $objectPath = $directoryPath . $file;
38
- // Not used DUP_U::safePath(), because I would like to decrease max_nest_level
39
- // Otherwise we will get the error:
40
- // PHP Fatal error: Uncaught Error: Maximum function nesting level of '512' reached, aborting! in ...
41
- // $objectPath = DUP_U::safePath($objectPath);
42
- $objectPath = str_replace("\\", '/', $objectPath);
43
- $localName = ltrim(str_replace($directoryPath, '', $objectPath), '/');
44
- if ($retainDirectory) {
45
- $localName = basename($directoryPath)."/$localName";
46
- }
47
- $localName = $localPrefix . $localName;
48
-
49
- if (is_dir($objectPath)) {
50
- $localPrefixArg = substr($localName, 0, strrpos($localName, '/')).'/';
51
- $added = self::addDirWithZipArchive($zipArchive, $objectPath, $retainDirectory, $localPrefixArg, $isCompressed);
52
- } else if (is_readable($objectPath)) {
53
- $added = DUP_Zip_U::addFileToZipArchive($zipArchive, $objectPath, $localName, $isCompressed);
54
- } else {
55
- $added = FALSE;
56
- }
57
-
58
- if (!$added) {
59
- DUP_Log::error("Couldn't add file $objectPath to archive", '', false);
60
- $success = FALSE;
61
- break;
62
- }
63
- }
64
- @closedir($fp);
65
- return $success;
66
- }
67
-
68
-
69
- public static function extractFiles($archiveFilepath, $relativeFilesToExtract, $destinationDirectory, $useShellUnZip)
70
- {
71
- // TODO: Unzip using either shell unzip or ziparchive
72
- if($useShellUnZip) {
73
- $shellExecPath = DUPX_Server::get_unzip_filepath();
74
- $filenameString = implode(' ', $relativeFilesToExtract);
75
- $command = "{$shellExecPath} -o -qq \"{$archiveFilepath}\" {$filenameString} -d {$destinationDirectory} 2>&1";
76
- $stderr = shell_exec($command);
77
-
78
- if ($stderr != '') {
79
- $errorMessage = DUP_U::__("Error extracting {$archiveFilepath}): {$stderr}");
80
-
81
- throw new Exception($errorMessage);
82
- }
83
- } else {
84
- $zipArchive = new ZipArchive();
85
- $result = $zipArchive->open($archiveFilepath);
86
-
87
- if($result !== true) {
88
- throw new Exception("Error opening {$archiveFilepath} when extracting. Error code: {$retVal}");
89
- }
90
-
91
- $result = $zipArchive->extractTo($destinationDirectory, $relativeFilesToExtract);
92
-
93
- if($result === false) {
94
- throw new Exception("Error extracting {$archiveFilepath}.");
95
- }
96
- }
97
- }
98
-
99
- /**
100
- * Add a directory to an existing ZipArchive object
101
- *
102
- * @param string $sourceFilePath The file to add to the zip file
103
- * @param string $zipFilePath The zip file to be added to
104
- * @param bool $deleteOld Delete the zip file before adding a file
105
- * @param string $newName Rename the $sourceFile if needed
106
- *
107
- * @return bool Returns true if the file was added to the zip file
108
- */
109
- public static function zipFile($sourceFilePath, $zipFilePath, $deleteOld, $newName, $isCompressed)
110
- {
111
- if ($deleteOld && file_exists($zipFilePath)) {
112
- DUP_IO::deleteFile($zipFilePath);
113
- }
114
-
115
- if (file_exists($sourceFilePath)) {
116
- $zip_archive = new ZipArchive();
117
-
118
- $is_zip_open = ($zip_archive->open($zipFilePath, ZIPARCHIVE::CREATE) === TRUE);
119
-
120
- if ($is_zip_open === false) {
121
- DUP_Log::error("Cannot create zip archive {$zipFilePath}");
122
- } else {
123
- //ADD SQL
124
- if ($newName == null) {
125
- $source_filename = basename($sourceFilePath);
126
- DUP_Log::Info("adding {$source_filename}");
127
- } else {
128
- $source_filename = $newName;
129
- DUP_Log::Info("new name added {$newName}");
130
- }
131
-
132
- $in_zip = DUP_Zip_U::addFileToZipArchive($zip_archive, $sourceFilePath, $source_filename, $isCompressed);
133
-
134
- if ($in_zip === false) {
135
- DUP_Log::error("Unable to add {$sourceFilePath} to $zipFilePath");
136
- }
137
-
138
- $zip_archive->close();
139
-
140
- return true;
141
- }
142
- } else {
143
- DUP_Log::error("Trying to add {$sourceFilePath} to a zip but it doesn't exist!");
144
- }
145
-
146
- return false;
147
- }
148
-
149
- public static function addFileToZipArchive(&$zipArchive, $filepath, $localName, $isCompressed)
150
- {
151
- $added = $zipArchive->addFile($filepath, $localName);
152
- return $added;
153
- }
154
- }
 
 
 
 
 
 
1
+ <?php
2
+
3
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
4
+ /**
5
+ * Utility class for zipping up content
6
+ *
7
+ * Standard: PSR-2
8
+ * @link http://www.php-fig.org/psr/psr-2
9
+ *
10
+ * @subpackage classes/utilities
11
+ * @copyright (c) 2017, Snapcreek LLC
12
+ * @license https://opensource.org/licenses/GPL-3.0 GNU Public License
13
+ */
14
+
15
+ // Exit if accessed directly
16
+ if (! defined('DUPLICATOR_VERSION')) {
17
+ exit;
18
+ }
19
+
20
+ class DUP_Zip_U
21
+ {
22
+ /**
23
+ * Add a directory to an existing ZipArchive object
24
+ *
25
+ * @param ZipArchive $zipArchive An existing ZipArchive object
26
+ * @param string $directoryPath The full directory path to add to the ZipArchive
27
+ * @param bool $retainDirectory Should the full directory path be retained in the archive
28
+ *
29
+ * @return bool Returns true if the directory was added to the object
30
+ */
31
+ public static function addDirWithZipArchive($zipArchive, $directoryPath, $retainDirectory, $localPrefix, $isCompressed)
32
+ {
33
+ $directoryPath = rtrim(str_replace("\\", '/', $directoryPath), '/') . '/';
34
+ if (!is_dir($directoryPath) || !is_readable($directoryPath)) {
35
+ $success = false;
36
+ } elseif (!$fp = @opendir($directoryPath)) {
37
+ $success = false;
38
+ } else {
39
+ $success = true;
40
+ while (false !== ($file = readdir($fp))) {
41
+ if ($file === '.' || $file === '..') {
42
+ continue;
43
+ }
44
+ $objectPath = $directoryPath . $file;
45
+ // Not used SnapIO::safePath(), because I would like to decrease max_nest_level
46
+ // Otherwise we will get the error:
47
+ // PHP Fatal error: Uncaught Error: Maximum function nesting level of '512' reached, aborting! in ...
48
+ // $objectPath = SnapIO::safePath($objectPath);
49
+ $localName = ltrim(str_replace($directoryPath, '', $objectPath), '/');
50
+ if ($retainDirectory) {
51
+ $localName = basename($directoryPath) . "/$localName";
52
+ }
53
+ $localName = ltrim($localPrefix . $localName, '/');
54
+ if (is_readable($objectPath)) {
55
+ if (is_dir($objectPath)) {
56
+ $localPrefixArg = substr($localName, 0, strrpos($localName, '/')) . '/';
57
+ $added = self::addDirWithZipArchive($zipArchive, $objectPath, $retainDirectory, $localPrefixArg, $isCompressed);
58
+ } else {
59
+ $added = self::addFileToZipArchive($zipArchive, $objectPath, $localName, $isCompressed);
60
+ }
61
+ } else {
62
+ $added = false;
63
+ }
64
+
65
+ if (!$added) {
66
+ DUP_Log::error("Couldn't add file $objectPath to archive", '', false);
67
+ $success = false;
68
+ break;
69
+ }
70
+ }
71
+ @closedir($fp);
72
+ }
73
+
74
+ if ($success) {
75
+ return true;
76
+ } else {
77
+ DUP_Log::error("Couldn't add folder $directoryPath to archive", '', false);
78
+ return false;
79
+ }
80
+ }
81
+
82
+
83
+ public static function extractFiles($archiveFilepath, $relativeFilesToExtract, $destinationDirectory, $useShellUnZip)
84
+ {
85
+ // TODO: Unzip using either shell unzip or ziparchive
86
+ if ($useShellUnZip) {
87
+ $shellExecPath = DUPX_Server::get_unzip_filepath();
88
+ $filenameString = implode(' ', $relativeFilesToExtract);
89
+ $command = "{$shellExecPath} -o -qq \"{$archiveFilepath}\" {$filenameString} -d {$destinationDirectory} 2>&1";
90
+ $stderr = shell_exec($command);
91
+ if ($stderr != '') {
92
+ $errorMessage = __("Error extracting {$archiveFilepath}): {$stderr}", 'duplicator');
93
+ throw new Exception($errorMessage);
94
+ }
95
+ } else {
96
+ $zipArchive = new ZipArchive();
97
+ $result = $zipArchive->open($archiveFilepath);
98
+ if ($result !== true) {
99
+ throw new Exception("Error opening {$archiveFilepath} when extracting.");
100
+ }
101
+
102
+ $result = $zipArchive->extractTo($destinationDirectory, $relativeFilesToExtract);
103
+ if ($result === false) {
104
+ throw new Exception("Error extracting {$archiveFilepath}.");
105
+ }
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Add a directory to an existing ZipArchive object
111
+ *
112
+ * @param string $sourceFilePath The file to add to the zip file
113
+ * @param string $zipFilePath The zip file to be added to
114
+ * @param bool $deleteOld Delete the zip file before adding a file
115
+ * @param string $newName Rename the $sourceFile if needed
116
+ *
117
+ * @return bool Returns true if the file was added to the zip file
118
+ */
119
+ public static function zipFile($sourceFilePath, $zipFilePath, $deleteOld, $newName, $isCompressed)
120
+ {
121
+ if ($deleteOld && file_exists($zipFilePath)) {
122
+ SnapIO::unlink($zipFilePath);
123
+ }
124
+
125
+ if (file_exists($sourceFilePath)) {
126
+ $zip_archive = new ZipArchive();
127
+ $is_zip_open = ($zip_archive->open($zipFilePath, ZIPARCHIVE::CREATE) === true);
128
+ if ($is_zip_open === false) {
129
+ DUP_Log::error("Cannot create zip archive {$zipFilePath}");
130
+ } else {
131
+ //ADD SQL
132
+ if ($newName == null) {
133
+ $source_filename = basename($sourceFilePath);
134
+ DUP_Log::Info("adding {$source_filename}");
135
+ } else {
136
+ $source_filename = $newName;
137
+ DUP_Log::Info("new name added {$newName}");
138
+ }
139
+
140
+ $in_zip = DUP_Zip_U::addFileToZipArchive($zip_archive, $sourceFilePath, $source_filename, $isCompressed);
141
+ if ($in_zip === false) {
142
+ DUP_Log::error("Unable to add {$sourceFilePath} to $zipFilePath");
143
+ }
144
+
145
+ $zip_archive->close();
146
+ return true;
147
+ }
148
+ } else {
149
+ DUP_Log::error("Trying to add {$sourceFilePath} to a zip but it doesn't exist!");
150
+ }
151
+
152
+ return false;
153
+ }
154
+
155
+ public static function addFileToZipArchive($zipArchive, $filepath, $localName, $isCompressed)
156
+ {
157
+ $added = $zipArchive->addFile($filepath, $localName);
158
+ return $added;
159
+ }
160
+ }
ctrls/class.web.services.php CHANGED
@@ -1,9 +1,11 @@
1
  <?php
 
 
 
2
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
 
4
  class DUP_Web_Services
5
  {
6
-
7
  /**
8
  * init ajax actions
9
  */
@@ -61,8 +63,7 @@ class DUP_Web_Services
61
  DUP_Package::not_active_files_tmp_cleanup();
62
 
63
  //throw new Exception('force error test');
64
- }
65
- catch (Exception $e) {
66
  $error = true;
67
  $result['message'] = $e->getMessage();
68
  }
@@ -125,9 +126,9 @@ class DUP_Web_Services
125
  $fileName = $package->getInstDownloadName();
126
  $realFileName = $package->Installer->File;
127
  $backupDir = DUP_Settings::getSsdirPath();
128
-
129
- if(DUP_STR::endsWith($realFileName, '.php')) {
130
- $realFileName = basename($realFileName, '.php').DUP_Installer::INSTALLER_SERVER_EXTENSION;
131
  }
132
  $filepath = "{$backupDir}/{$realFileName}";
133
 
@@ -137,35 +138,33 @@ class DUP_Web_Services
137
  }
138
 
139
  // Clean output buffer
140
- if (ob_get_level() !== 0 && @ob_end_clean() === FALSE) {
141
  @ob_clean();
142
  }
143
 
144
  header('Content-Description: File Transfer');
145
  header('Content-Type: application/octet-stream');
146
- header('Content-Disposition: attachment; filename="'.$fileName.'"');
147
  header('Expires: 0');
148
  header('Cache-Control: must-revalidate');
149
  header('Pragma: public');
150
- header('Content-Length: '.filesize($filepath));
151
  flush(); // Flush system output buffer
152
 
153
  try {
154
  $fp = @fopen($filepath, 'r');
155
  if (false === $fp) {
156
- throw new Exception('Fail to open the file '.$filepath);
157
  }
158
- while (!feof($fp) && ($data = fread($fp, DUPLICATOR_BUFFER_READ_WRITE_SIZE)) !== FALSE) {
159
  echo $data;
160
  }
161
  @fclose($fp);
162
- }
163
- catch (Exception $e) {
164
  readfile($filepath);
165
  }
166
  exit;
167
- }
168
- catch (Exception $ex) {
169
  //Prevent brute force
170
  sleep(2);
171
  wp_die($ex->getMessage());
@@ -176,7 +175,7 @@ class DUP_Web_Services
176
  {
177
  DUP_Handler::init_error_handler();
178
 
179
- try{
180
  DUP_Util::hasCapability('export', DUP_Util::SECURE_ISSUE_THROW);
181
 
182
  if (!wp_verify_nonce($_REQUEST['nonce'], 'duplicator_set_admin_notice_viewed')) {
@@ -184,7 +183,7 @@ class DUP_Web_Services
184
  throw new Exception('Security issue');
185
  }
186
 
187
- $notice_id = DupLiteSnapLibUtil::filterInputRequest('notice_id', FILTER_UNSAFE_RAW);
188
 
189
  if (empty($notice_id)) {
190
  throw new Exception(__('Invalid Request', 'duplicator'));
@@ -201,8 +200,7 @@ class DUP_Web_Services
201
 
202
  $notices[$notice_id] = 'true';
203
  update_user_meta(get_current_user_id(), DUPLICATOR_ADMIN_NOTICES_USER_META_KEY, $notices);
204
- }
205
- catch (Exception $ex) {
206
  wp_die($ex->getMessage());
207
  }
208
  }
@@ -224,15 +222,14 @@ class DUP_Web_Services
224
  case DUP_UI_Notice::OPTION_KEY_NEW_NOTICE_TEMPLATE:
225
  delete_option($noticeToDismiss);
226
  break;
227
- case DUP_UI_Notice::OPTION_KEY_IS_PRO_ENABLE_NOTICE_DISMISSED:
228
  case DUP_UI_Notice::OPTION_KEY_IS_MU_NOTICE_DISMISSED:
229
  update_option($noticeToDismiss, true);
230
  break;
231
  default:
232
  throw new Exception('Notice invalid');
233
  }
234
- }
235
- catch (Exception $e) {
236
  wp_send_json_error($e->getMessage());
237
  }
238
 
1
  <?php
2
+
3
+ use Duplicator\Libs\Snap\SnapUtil;
4
+
5
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
6
 
7
  class DUP_Web_Services
8
  {
 
9
  /**
10
  * init ajax actions
11
  */
63
  DUP_Package::not_active_files_tmp_cleanup();
64
 
65
  //throw new Exception('force error test');
66
+ } catch (Exception $e) {
 
67
  $error = true;
68
  $result['message'] = $e->getMessage();
69
  }
126
  $fileName = $package->getInstDownloadName();
127
  $realFileName = $package->Installer->File;
128
  $backupDir = DUP_Settings::getSsdirPath();
129
+
130
+ if (DUP_STR::endsWith($realFileName, '.php')) {
131
+ $realFileName = basename($realFileName, '.php') . DUP_Installer::INSTALLER_SERVER_EXTENSION;
132
  }
133
  $filepath = "{$backupDir}/{$realFileName}";
134
 
138
  }
139
 
140
  // Clean output buffer
141
+ if (ob_get_level() !== 0 && @ob_end_clean() === false) {
142
  @ob_clean();
143
  }
144
 
145
  header('Content-Description: File Transfer');
146
  header('Content-Type: application/octet-stream');
147
+ header('Content-Disposition: attachment; filename="' . $fileName . '"');
148
  header('Expires: 0');
149
  header('Cache-Control: must-revalidate');
150
  header('Pragma: public');
151
+ header('Content-Length: ' . filesize($filepath));
152
  flush(); // Flush system output buffer
153
 
154
  try {
155
  $fp = @fopen($filepath, 'r');
156
  if (false === $fp) {
157
+ throw new Exception('Fail to open the file ' . $filepath);
158
  }
159
+ while (!feof($fp) && ($data = fread($fp, DUPLICATOR_BUFFER_READ_WRITE_SIZE)) !== false) {
160
  echo $data;
161
  }
162
  @fclose($fp);
163
+ } catch (Exception $e) {
 
164
  readfile($filepath);
165
  }
166
  exit;
167
+ } catch (Exception $ex) {
 
168
  //Prevent brute force
169
  sleep(2);
170
  wp_die($ex->getMessage());
175
  {
176
  DUP_Handler::init_error_handler();
177
 
178
+ try {
179
  DUP_Util::hasCapability('export', DUP_Util::SECURE_ISSUE_THROW);
180
 
181
  if (!wp_verify_nonce($_REQUEST['nonce'], 'duplicator_set_admin_notice_viewed')) {
183
  throw new Exception('Security issue');
184
  }
185
 
186
+ $notice_id = SnapUtil::filterInputRequest('notice_id', FILTER_UNSAFE_RAW);
187
 
188
  if (empty($notice_id)) {
189
  throw new Exception(__('Invalid Request', 'duplicator'));
200
 
201
  $notices[$notice_id] = 'true';
202
  update_user_meta(get_current_user_id(), DUPLICATOR_ADMIN_NOTICES_USER_META_KEY, $notices);
203
+ } catch (Exception $ex) {
 
204
  wp_die($ex->getMessage());
205
  }
206
  }
222
  case DUP_UI_Notice::OPTION_KEY_NEW_NOTICE_TEMPLATE:
223
  delete_option($noticeToDismiss);
224
  break;
225
+ case DUP_UI_Notice::OPTION_KEY_IS_ENABLE_NOTICE_DISMISSED:
226
  case DUP_UI_Notice::OPTION_KEY_IS_MU_NOTICE_DISMISSED:
227
  update_option($noticeToDismiss, true);
228
  break;
229
  default:
230
  throw new Exception('Notice invalid');
231
  }
232
+ } catch (Exception $e) {
 
233
  wp_send_json_error($e->getMessage());
234
  }
235
 
ctrls/ctrl.base.php CHANGED
@@ -1,153 +1,152 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- // Exit if accessed directly
4
- if (! defined('DUPLICATOR_VERSION')) exit;
5
-
6
- require_once(DUPLICATOR_PLUGIN_PATH.'/classes/utilities/class.u.php');
7
-
8
- //Enum used to define the various test statues
9
- final class DUP_CTRL_Status
10
- {
11
- const ERROR = -2;
12
- const FAILED = -1;
13
- const UNDEFINED = 0;
14
- const SUCCESS = 1;
15
- }
16
-
17
- /**
18
- * Base class for all controllers
19
- *
20
- * @package Duplicator
21
- * @subpackage classes/ctrls
22
- */
23
- class DUP_CTRL_Base
24
- {
25
- //Represents the name of the Nonce Action
26
- public $Action;
27
- //The return type valiad options: PHP, JSON-AJAX, JSON
28
- public $returnType = 'JSON-AJAX';
29
-
30
- public function setResponseType($type)
31
- {
32
- $opts = array('PHP', 'JSON-AJAX', 'JSON');
33
- if (!in_array($type, $opts)) {
34
- throw new Exception('The $type param must be one of the following: '.implode(',', $opts).' for the following function ['.__FUNCTION__.']');
35
- }
36
- $this->returnType = $type;
37
- }
38
-
39
- }
40
-
41
- /**
42
- * A class structer used to report on controller methods
43
- *
44
- * @package Duplicator
45
- * @subpackage classes/ctrls
46
- */
47
- class DUP_CTRL_Report
48
- {
49
- //Properties
50
- public $runTime;
51
- public $returnType;
52
- public $results;
53
- public $status;
54
-
55
- }
56
-
57
- /**
58
- * A class used format all controller responses in a consistent format. Every controller response will
59
- * have a Report and Payload structer. The Payload is an array of the result response. The Report is used
60
- * report on the overall status of the controller method
61
- *
62
- * Standard: PSR-2
63
- * @link http://www.php-fig.org/psr/psr-2
64
- *
65
- * @package Duplicator
66
- * @subpackage classes/ctrls
67
- * @copyright (c) 2017, Snapcreek LLC
68
- *
69
- */
70
- class DUP_CTRL_Result
71
- {
72
- //Properties
73
- public $report;
74
- public $payload;
75
- private $timeStart;
76
- private $timeEnd;
77
- private $CTRL;
78
-
79
- function __construct(DUP_CTRL_Base $CTRL_OBJ)
80
- {
81
- DUP_Util::hasCapability('export');
82
- $this->timeStart = $this->microtimeFloat();
83
- $this->CTRL = $CTRL_OBJ;
84
-
85
- //Report Data
86
- $this->report = new DUP_CTRL_Report();
87
- $this->report->returnType = $CTRL_OBJ->returnType;
88
- }
89
-
90
- /**
91
- * Used to process a controller request
92
- *
93
- * @param object $payload The response object that will be returned
94
- * @param enum $test The status of a response
95
- *
96
- * @return object || JSON Returns a PHP object or json encoded object
97
- */
98
- public function process($payload, $test = DUP_CTRL_Status::UNDEFINED)
99
- {
100
- if (is_array($this->payload)) {
101
- $this->payload[] = $payload;
102
- $this->report->results = count($this->payload);
103
- } else {
104
- $this->payload = $payload;
105
- $this->report->results = (is_array($payload)) ? count($payload) : 1;
106
- }
107
-
108
- $this->report->status = $test;
109
- $this->getProcessTime();
110
-
111
- switch ($this->CTRL->returnType) {
112
- case 'JSON' :
113
- return DupLiteSnapJsonU::wp_json_encode($this);
114
- break;
115
- case 'PHP' :
116
- return $this;
117
- break;
118
- default:
119
- wp_send_json($this);
120
- break;
121
- }
122
- }
123
-
124
- /**
125
- * Used to process an error response
126
- *
127
- * @param object $exception The PHP exception object
128
- *
129
- * @return object || JSON Returns a PHP object or json encoded object
130
- */
131
- public function processError($exception)
132
- {
133
- $payload = array();
134
- $payload['Message'] = $exception->getMessage();
135
- $payload['File'] = $exception->getFile();
136
- $payload['Line'] = $exception->getLine();
137
- $payload['Trace'] = $exception->getTraceAsString();
138
- $this->process($payload, DUP_CTRL_Status::ERROR);
139
- die(DupLiteSnapJsonU::wp_json_encode($this));
140
- }
141
-
142
- private function getProcessTime()
143
- {
144
- $this->timeEnd = $this->microtimeFloat();
145
- $this->report->runTime = $this->timeEnd - $this->timeStart;
146
- }
147
-
148
- private function microtimeFloat()
149
- {
150
- list($usec, $sec) = explode(" ", microtime());
151
- return ((float) $usec + (float) $sec);
152
- }
153
- }
1
+ <?php
2
+
3
+ use Duplicator\Libs\Snap\SnapJson;
4
+
5
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
6
+ // Exit if accessed directly
7
+ if (! defined('DUPLICATOR_VERSION')) {
8
+ exit;
9
+ }
10
+
11
+ require_once(DUPLICATOR_PLUGIN_PATH . '/classes/utilities/class.u.php');
12
+ //Enum used to define the various test statues
13
+ final class DUP_CTRL_Status
14
+ {
15
+ const ERROR = -2;
16
+ const FAILED = -1;
17
+ const UNDEFINED = 0;
18
+ const SUCCESS = 1;
19
+ }
20
+
21
+ /**
22
+ * Base class for all controllers
23
+ *
24
+ * @package Duplicator
25
+ * @subpackage classes/ctrls
26
+ */
27
+ class DUP_CTRL_Base
28
+ {
29
+ //Represents the name of the Nonce Action
30
+ public $Action;
31
+ //The return type valiad options: PHP, JSON-AJAX, JSON
32
+ public $returnType = 'JSON-AJAX';
33
+ public function setResponseType($type)
34
+ {
35
+ $opts = array('PHP', 'JSON-AJAX', 'JSON');
36
+ if (!in_array($type, $opts)) {
37
+ throw new Exception('The $type param must be one of the following: ' . implode(',', $opts) . ' for the following function [' . __FUNCTION__ . ']');
38
+ }
39
+ $this->returnType = $type;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * A class structer used to report on controller methods
45
+ *
46
+ * @package Duplicator
47
+ * @subpackage classes/ctrls
48
+ */
49
+ class DUP_CTRL_Report
50
+ {
51
+ //Properties
52
+ public $runTime;
53
+ public $returnType;
54
+ public $results;
55
+ public $status;
56
+ }
57
+
58
+ /**
59
+ * A class used format all controller responses in a consistent format. Every controller response will
60
+ * have a Report and Payload structer. The Payload is an array of the result response. The Report is used
61
+ * report on the overall status of the controller method
62
+ *
63
+ * Standard: PSR-2
64
+ * @link http://www.php-fig.org/psr/psr-2
65
+ *
66
+ * @package Duplicator
67
+ * @subpackage classes/ctrls
68
+ * @copyright (c) 2017, Snapcreek LLC
69
+ *
70
+ */
71
+ class DUP_CTRL_Result
72
+ {
73
+ //Properties
74
+ public $report;
75
+ public $payload;
76
+ private $timeStart;
77
+ private $timeEnd;
78
+ private $CTRL;
79
+
80
+ public function __construct(DUP_CTRL_Base $CTRL_OBJ)
81
+ {
82
+ DUP_Util::hasCapability('export');
83
+ $this->timeStart = $this->microtimeFloat();
84
+ $this->CTRL = $CTRL_OBJ;
85
+ //Report Data
86
+ $this->report = new DUP_CTRL_Report();
87
+ $this->report->returnType = $CTRL_OBJ->returnType;
88
+ }
89
+
90
+ /**
91
+ * Used to process a controller request
92
+ *
93
+ * @param object $payload The response object that will be returned
94
+ * @param enum $test The status of a response
95
+ *
96
+ * @return object || JSON Returns a PHP object or json encoded object
97
+ */
98
+ public function process($payload, $test = DUP_CTRL_Status::UNDEFINED)
99
+ {
100
+ if (is_array($this->payload)) {
101
+ $this->payload[] = $payload;
102
+ $this->report->results = count($this->payload);
103
+ } else {
104
+ $this->payload = $payload;
105
+ $this->report->results = (is_array($payload)) ? count($payload) : 1;
106
+ }
107
+
108
+ $this->report->status = $test;
109
+ $this->getProcessTime();
110
+ switch ($this->CTRL->returnType) {
111
+ case 'JSON':
112
+ return SnapJson::jsonEncode($this);
113
+ break;
114
+ case 'PHP':
115
+ return $this;
116
+ break;
117
+ default:
118
+ wp_send_json($this);
119
+ break;
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Used to process an error response
125
+ *
126
+ * @param object $exception The PHP exception object
127
+ *
128
+ * @return object || JSON Returns a PHP object or json encoded object
129
+ */
130
+ public function processError($exception)
131
+ {
132
+ $payload = array();
133
+ $payload['Message'] = $exception->getMessage();
134
+ $payload['File'] = $exception->getFile();
135
+ $payload['Line'] = $exception->getLine();
136
+ $payload['Trace'] = $exception->getTraceAsString();
137
+ $this->process($payload, DUP_CTRL_Status::ERROR);
138
+ die(SnapJson::jsonEncode($this));
139
+ }
140
+
141
+ private function getProcessTime()
142
+ {
143
+ $this->timeEnd = $this->microtimeFloat();
144
+ $this->report->runTime = $this->timeEnd - $this->timeStart;
145
+ }
146
+
147
+ private function microtimeFloat()
148
+ {
149
+ list($usec, $sec) = explode(" ", microtime());
150
+ return ((float) $usec + (float) $sec);
151
+ }
152
+ }
 
ctrls/ctrl.package.php CHANGED
@@ -1,479 +1,430 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- // Exit if accessed directly
4
- if (!defined('DUPLICATOR_VERSION'))
5
- exit;
6
-
7
- require_once(DUPLICATOR_PLUGIN_PATH.'/ctrls/ctrl.base.php');
8
- require_once(DUPLICATOR_PLUGIN_PATH.'/classes/utilities/class.u.scancheck.php');
9
- require_once(DUPLICATOR_PLUGIN_PATH.'/classes/utilities/class.u.json.php');
10
- require_once(DUPLICATOR_PLUGIN_PATH.'/classes/package/class.pack.php');
11
-
12
- require_once(DUPLICATOR_PLUGIN_PATH.'/classes/package/duparchive/class.pack.archive.duparchive.state.create.php');
13
- require_once(DUPLICATOR_PLUGIN_PATH.'/classes/package/duparchive/class.pack.archive.duparchive.php');
14
- /* @var $package DUP_Package */
15
-
16
- /**
17
- * Display error if any fatal error occurs occurs while scan ajax call
18
- *
19
- * @return void
20
- */
21
- function duplicator_package_scan_shutdown()
22
- {
23
- $logMessage = DUP_Handler::getVarLog();
24
- if (!empty($logMessage)) {
25
- echo nl2br($logMessage);
26
- }
27
- }
28
-
29
- /**
30
- * DUPLICATOR_PACKAGE_SCAN
31
- * Returns a JSON scan report object which contains data about the system
32
- *
33
- * @return json JSON report object
34
- * @example to test: /wp-admin/admin-ajax.php?action=duplicator_package_scan
35
- */
36
- function duplicator_package_scan()
37
- {
38
- DUP_Handler::init_error_handler();
39
- DUP_Handler::setMode(DUP_Handler::MODE_VAR);
40
- register_shutdown_function('duplicator_package_scan_shutdown');
41
-
42
- check_ajax_referer('duplicator_package_scan', 'nonce');
43
- DUP_Util::hasCapability('export');
44
-
45
- header('Content-Type: application/json;');
46
- @ob_flush();
47
-
48
- @set_time_limit(0);
49
- $errLevel = error_reporting();
50
- error_reporting(E_ERROR);
51
- DUP_Util::initSnapshotDirectory();
52
-
53
- $package = DUP_Package::getActive();
54
- $report = $package->runScanner();
55
-
56
- $package->saveActiveItem('ScanFile', $package->ScanFile);
57
- $json_response = DUP_JSON::safeEncode($report);
58
-
59
- DUP_Package::tempFileCleanup();
60
- error_reporting($errLevel);
61
- die($json_response);
62
- }
63
-
64
- /**
65
- * duplicator_package_build
66
- * Returns the package result status
67
- *
68
- * @return json JSON object of package results
69
- */
70
- function duplicator_package_build()
71
- {
72
- DUP_Handler::init_error_handler();
73
-
74
- check_ajax_referer('duplicator_package_build', 'nonce');
75
-
76
- header('Content-Type: application/json');
77
-
78
- $Package = null;
79
-
80
- try {
81
- DUP_Util::hasCapability('export', DUP_Util::SECURE_ISSUE_THROW);
82
-
83
- @set_time_limit(0);
84
- $errLevel = error_reporting();
85
- error_reporting(E_ERROR);
86
- DUP_Util::initSnapshotDirectory();
87
-
88
- $Package = DUP_Package::getActive();
89
- $Package->save('zip');
90
-
91
- DUP_Settings::Set('active_package_id', $Package->ID);
92
- DUP_Settings::Save();
93
-
94
- if (!is_readable(DUP_Settings::getSsdirTmpPath()."/{$Package->ScanFile}")) {
95
- die("The scan result file was not found. Please run the scan step before building the package.");
96
- }
97
-
98
- $Package->runZipBuild();
99
-
100
- //JSON:Debug Response
101
- //Pass = 1, Warn = 2, Fail = 3
102
- $json = array();
103
- $json['status'] = 1;
104
- $json['error'] = '';
105
- $json['package'] = $Package;
106
- $json['instDownloadName'] = $Package->getInstDownloadName();
107
- $json['runtime'] = $Package->Runtime;
108
- $json['exeSize'] = $Package->ExeSize;
109
- $json['archiveSize'] = $Package->ZipSize;
110
-
111
- //Simulate a Host Build Interrupt
112
- //die(0);
113
- }
114
- catch (Exception $e) {
115
- $Package->setStatus(DUP_PackageStatus::ERROR);
116
-
117
- //JSON:Debug Response
118
- //Pass = 1, Warn = 2, Fail = 3
119
- $json = array();
120
- $json['status'] = 3;
121
- $json['error'] = $e->getMessage();
122
- $json['package'] = $Package;
123
- $json['instDownloadName'] = null;
124
- $json['runtime'] = null;
125
- $json['exeSize'] = null;
126
- $json['archiveSize'] = null;
127
- }
128
- $json_response = DupLiteSnapJsonU::wp_json_encode($json);
129
-
130
- error_reporting($errLevel);
131
- die($json_response);
132
- }
133
-
134
- /**
135
- * Returns the package result status
136
- *
137
- * @return json JSON object of package results
138
- */
139
- function duplicator_duparchive_package_build()
140
- {
141
- DUP_Handler::init_error_handler();
142
- DUP_Log::Info('[CTRL DUP ARCIVE] CALL TO '.__FUNCTION__);
143
-
144
- check_ajax_referer('duplicator_duparchive_package_build', 'nonce');
145
- DUP_Util::hasCapability('export');
146
- header('Content-Type: application/json');
147
-
148
- @set_time_limit(0);
149
- $errLevel = error_reporting();
150
- error_reporting(E_ERROR);
151
-
152
- // The DupArchive build process always works on a saved package so the first time through save the active package to the package table.
153
- // After that, just retrieve it.
154
- $active_package_id = DUP_Settings::Get('active_package_id');
155
- DUP_Log::Info('[CTRL DUP ARCIVE] CURRENT PACKAGE ACTIVE '.$active_package_id);
156
-
157
- if ($active_package_id == -1) {
158
- $package = DUP_Package::getActive();
159
- $package->save('daf');
160
- DUP_Log::Info('[CTRL DUP ARCIVE] PACKAGE AS NEW ID '.$package->ID.' SAVED | STATUS:'.$package->Status);
161
- //DUP_Log::TraceObject("[CTRL DUP ARCIVE] PACKAGE SAVED:", $package);
162
- DUP_Settings::Set('active_package_id', $package->ID);
163
- DUP_Settings::Save();
164
- } else {
165
- if (($package = DUP_Package::getByID($active_package_id)) == null) {
166
- DUP_Log::Info('[CTRL DUP ARCIVE] ERROR: Get package by id '.$active_package_id.' FAILED');
167
- die('Get package by id '.$active_package_id.' FAILED');
168
- }
169
- DUP_Log::Info('[CTRL DUP ARCIVE] PACKAGE GET BY ID '.$active_package_id.' | STATUS:'.$package->Status);
170
- // DUP_Log::TraceObject("getting active package by id {$active_package_id}", $package);
171
- }
172
-
173
- if (!is_readable(DUP_Settings::getSsdirTmpPath()."/{$package->ScanFile}")) {
174
- DUP_Log::Info('[CTRL DUP ARCIVE] ERROR: The scan result file was not found. Please run the scan step before building the package.');
175
- die("The scan result file was not found. Please run the scan step before building the package.");
176
- }
177
-
178
- if ($package === null) {
179
- DUP_Log::Info('[CTRL DUP ARCIVE] There is no active package.');
180
- die("There is no active package.");
181
- }
182
-
183
- if ($package->Status == DUP_PackageStatus::ERROR) {
184
- $package->setStatus(DUP_PackageStatus::ERROR);
185
- $hasCompleted = true;
186
- } else {
187
- try {
188
- $hasCompleted = $package->runDupArchiveBuild();
189
- }
190
- catch (Exception $ex) {
191
- DUP_Log::Info('[CTRL DUP ARCIVE] ERROR: caught exception');
192
- Dup_Log::error('[CTRL DUP ARCIVE] Caught exception', $ex->getMessage(), Dup_ErrorBehavior::LogOnly);
193
- DUP_Log::Info('[CTRL DUP ARCIVE] ERROR: after log');
194
- $package->setStatus(DUP_PackageStatus::ERROR);
195
- $hasCompleted = true;
196
- }
197
- }
198
-
199
- $json = array();
200
- $json['failures'] = array_merge($package->BuildProgress->build_failures, $package->BuildProgress->validation_failures);
201
- if (!empty($json['failures'])) {
202
- DUP_Log::Info('[CTRL DUP ARCIVE] FAILURES '. print_r($json['failures'], true));
203
- }
204
-
205
- //JSON:Debug Response
206
- //Pass = 1, Warn = 2, 3 = Failure, 4 = Not Done
207
- if ($hasCompleted) {
208
- DUP_Log::Info('[CTRL DUP ARCIVE] COMPLETED PACKAGE STATUS: '.$package->Status);
209
-
210
- if ($package->Status == DUP_PackageStatus::ERROR) {
211
- DUP_Log::Info('[CTRL DUP ARCIVE] ERROR');
212
- $error_message = __('Error building DupArchive package').'<br/>';
213
-
214
- foreach ($json['failures'] as $failure) {
215
- $error_message .= implode(',', $failure->description);
216
- }
217
-
218
- Dup_Log::error("Build failed so sending back error", esc_html($error_message), Dup_ErrorBehavior::LogOnly);
219
- DUP_Log::Info('[CTRL DUP ARCIVE] ERROR AFTER LOG 2');
220
-
221
- $json['status'] = 3;
222
- } else {
223
- Dup_Log::Info("sending back success status");
224
- $json['status'] = 1;
225
- }
226
-
227
- Dup_Log::Trace('#### json package');
228
- $json['package'] = $package;
229
- $json['instDownloadName'] = $package->getInstDownloadName();
230
- $json['runtime'] = $package->Runtime;
231
- $json['exeSize'] = $package->ExeSize;
232
- $json['archiveSize'] = $package->ZipSize;
233
- DUP_Log::Trace('[CTRL DUP ARCIVE] JSON PACKAGE');
234
- } else {
235
- DUP_Log::Info('[CTRL DUP ARCIVE] sending back continue status PACKAGE STATUS: '.$package->Status);
236
- $json['status'] = 4;
237
- }
238
-
239
- $json_response = DupLiteSnapJsonU::wp_json_encode($json);
240
-
241
- Dup_Log::TraceObject('json response', $json_response);
242
- error_reporting($errLevel);
243
- die($json_response);
244
- }
245
-
246
- /**
247
- * DUPLICATOR_PACKAGE_DELETE
248
- * Deletes the files and database record entries
249
- *
250
- * @return json A JSON message about the action.
251
- * Use console.log to debug from client
252
- */
253
- function duplicator_package_delete()
254
- {
255
- DUP_Handler::init_error_handler();
256
- check_ajax_referer('duplicator_package_delete', 'nonce');
257
-
258
- $json = array(
259
- 'success' => false,
260
- 'message' => ''
261
- );
262
- $package_ids = filter_input(INPUT_POST, 'package_ids', FILTER_VALIDATE_INT, array(
263
- 'flags' => FILTER_REQUIRE_ARRAY,
264
- 'options' => array(
265
- 'default' => false
266
- )
267
- ));
268
- $delCount = 0;
269
-
270
- try {
271
- DUP_Util::hasCapability('export', DUP_Util::SECURE_ISSUE_THROW);
272
-
273
- if ($package_ids === false || in_array(false, $package_ids)) {
274
- throw new Exception('Invalid Request.', 'duplicator');
275
- }
276
-
277
- foreach ($package_ids as $id) {
278
- $package = DUP_Package::getByID($id);
279
-
280
- if ($package === null) {
281
- throw new Exception('Invalid Request.', 'duplicator');
282
- }
283
-
284
- $package->delete();
285
- $delCount++;
286
- }
287
-
288
- $json['success'] = true;
289
- $json['ids'] = $package_ids;
290
- $json['removed'] = $delCount;
291
- }
292
- catch (Exception $ex) {
293
- $json['message'] = $ex->getMessage();
294
- }
295
-
296
- die(DupLiteSnapJsonU::wp_json_encode($json));
297
- }
298
-
299
- /**
300
- * Active package info
301
- * Returns a JSON scan report active package info or
302
- * active_package_present == false if no active package is present.
303
- *
304
- * @return json
305
- */
306
- function duplicator_active_package_info()
307
- {
308
- ob_start();
309
- try {
310
- DUP_Handler::init_error_handler();
311
- DUP_Util::hasCapability('export', DUP_Util::SECURE_ISSUE_THROW);
312
-
313
- if (!check_ajax_referer('duplicator_active_package_info', 'nonce', false)) {
314
- throw new Exception(__('An unauthorized security request was made to this page. Please try again!', 'duplicator'));
315
- }
316
-
317
- global $wpdb;
318
-
319
- $error = false;
320
- $result = array(
321
- 'active_package' => array(
322
- 'present' => false,
323
- 'status' => 0,
324
- 'size' => 0
325
- ),
326
- 'html' => '',
327
- 'message' => ''
328
- );
329
-
330
- $result['active_package']['present'] = DUP_Package::is_active_package_present();
331
-
332
- if ($result['active_package']['present']) {
333
- $id = DUP_Settings::Get('active_package_id');
334
- $package = DUP_Package::getByID($id);
335
- if (is_null($package)) {
336
- throw new Exception(__('Active package object error', 'duplicator'));
337
- }
338
- $result['active_package']['status'] = $package->Status;
339
- $result['active_package']['size'] = $package->getArchiveSize();
340
- $result['active_package']['size_format'] = DUP_Util::byteSize($package->getArchiveSize());
341
- }
342
- }
343
- catch (Exception $e) {
344
- $error = true;
345
- $result['message'] = $e->getMessage();
346
- }
347
-
348
- $result['html'] = ob_get_clean();
349
- if ($error) {
350
- wp_send_json_error($result);
351
- } else {
352
- wp_send_json_success($result);
353
- }
354
- }
355
-
356
- /**
357
- * Controller for Tools
358
- * @package Duplicator\ctrls
359
- */
360
- class DUP_CTRL_Package extends DUP_CTRL_Base
361
- {
362
-
363
- /**
364
- * Init this instance of the object
365
- */
366
- function __construct()
367
- {
368
- add_action('wp_ajax_DUP_CTRL_Package_addQuickFilters', array($this, 'addQuickFilters'));
369
- add_action('wp_ajax_DUP_CTRL_Package_getPackageFile', array($this, 'getPackageFile'));
370
- add_action('wp_ajax_DUP_CTRL_Package_getActivePackageStatus', array($this, 'getActivePackageStatus'));
371
- }
372
-
373
- /**
374
- * Removed all reserved installer files names
375
- *
376
- * @param string $_POST['dir_paths'] A semi-colon separated list of directory paths
377
- *
378
- * @return string Returns all of the active directory filters as a ";" separated string
379
- */
380
- public function addQuickFilters()
381
- {
382
- DUP_Handler::init_error_handler();
383
- check_ajax_referer('DUP_CTRL_Package_addQuickFilters', 'nonce');
384
-
385
- $result = new DUP_CTRL_Result($this);
386
-
387
- $inputData = filter_input_array(INPUT_POST, array(
388
- 'dir_paths' => array(
389
- 'filter' => FILTER_DEFAULT,
390
- 'flags' => FILTER_REQUIRE_SCALAR,
391
- 'options' => array(
392
- 'default' => ''
393
- )
394
- ),
395
- 'file_paths' => array(
396
- 'filter' => FILTER_DEFAULT,
397
- 'flags' => FILTER_REQUIRE_SCALAR,
398
- 'options' => array(
399
- 'default' => ''
400
- )
401
- ),
402
- )
403
- );
404
-
405
- try {
406
- DUP_Util::hasCapability('export', DUP_Util::SECURE_ISSUE_THROW);
407
-
408
- //CONTROLLER LOGIC
409
- $package = DUP_Package::getActive();
410
-
411
- //DIRS
412
- $dir_filters = ($package->Archive->FilterOn) ? $package->Archive->FilterDirs.';'.$inputData['dir_paths'] : $inputData['dir_paths'];
413
- $dir_filters = $package->Archive->parseDirectoryFilter($dir_filters);
414
- $changed = $package->Archive->saveActiveItem($package, 'FilterDirs', $dir_filters);
415
-
416
- //FILES
417
- $file_filters = ($package->Archive->FilterOn) ? $package->Archive->FilterFiles.';'.$inputData['file_paths'] : $inputData['file_paths'];
418
- $file_filters = $package->Archive->parseFileFilter($file_filters);
419
- $changed = $package->Archive->saveActiveItem($package, 'FilterFiles', $file_filters);
420
-
421
- if (!$package->Archive->FilterOn && !empty($package->Archive->FilterExts)) {
422
- $changed = $package->Archive->saveActiveItem($package, 'FilterExts', '');
423
- }
424
-
425
- $changed = $package->Archive->saveActiveItem($package, 'FilterOn', 1);
426
-
427
- //Result
428
- $package = DUP_Package::getActive();
429
- $payload['dirs-in'] = esc_html(sanitize_text_field($inputData['dir_paths']));
430
- $payload['dir-out'] = esc_html($package->Archive->FilterDirs);
431
- $payload['files-in'] = esc_html(sanitize_text_field($inputData['file_paths']));
432
- $payload['files-out'] = esc_html($package->Archive->FilterFiles);
433
-
434
- //RETURN RESULT
435
- $test = ($changed) ? DUP_CTRL_Status::SUCCESS : DUP_CTRL_Status::FAILED;
436
- $result->process($payload, $test);
437
- }
438
- catch (Exception $exc) {
439
- $result->processError($exc);
440
- }
441
- }
442
-
443
- /**
444
- * Get active package status
445
- *
446
- * <code>
447
- * //JavaScript Ajax Request
448
- * Duplicator.Package.getActivePackageStatus()
449
- * </code>
450
- */
451
- public function getActivePackageStatus()
452
- {
453
- DUP_Handler::init_error_handler();
454
- check_ajax_referer('DUP_CTRL_Package_getActivePackageStatus', 'nonce');
455
-
456
- $result = new DUP_CTRL_Result($this);
457
-
458
- try {
459
- DUP_Util::hasCapability('export', DUP_Util::SECURE_ISSUE_THROW);
460
- //CONTROLLER LOGIC
461
- $active_package_id = DUP_Settings::Get('active_package_id');
462
- $package = DUP_Package::getByID($active_package_id);
463
- $payload = array();
464
-
465
- if ($package != null) {
466
- $test = DUP_CTRL_Status::SUCCESS;
467
- $payload['status'] = $package->Status;
468
- } else {
469
- $test = DUP_CTRL_Status::FAILED;
470
- }
471
-
472
- //RETURN RESULT
473
- return $result->process($payload, $test);
474
- }
475
- catch (Exception $exc) {
476
- $result->processError($exc);
477
- }
478
- }
479
- }
1
+ <?php
2
+
3
+ use Duplicator\Libs\Snap\SnapJson;
4
+
5
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
6
+ // Exit if accessed directly
7
+ if (!defined('DUPLICATOR_VERSION')) {
8
+ exit;
9
+ }
10
+
11
+ require_once(DUPLICATOR_PLUGIN_PATH . '/ctrls/ctrl.base.php');
12
+ require_once(DUPLICATOR_PLUGIN_PATH . '/classes/utilities/class.u.scancheck.php');
13
+ require_once(DUPLICATOR_PLUGIN_PATH . '/classes/utilities/class.u.json.php');
14
+ require_once(DUPLICATOR_PLUGIN_PATH . '/classes/package/class.pack.php');
15
+ require_once(DUPLICATOR_PLUGIN_PATH . '/classes/package/duparchive/class.pack.archive.duparchive.state.create.php');
16
+ require_once(DUPLICATOR_PLUGIN_PATH . '/classes/package/duparchive/class.pack.archive.duparchive.php');
17
+ /* @var $package DUP_Package */
18
+
19
+ /**
20
+ * Display error if any fatal error occurs occurs while scan ajax call
21
+ *
22
+ * @return void
23
+ */
24
+ function duplicator_package_scan_shutdown()
25
+ {
26
+ $logMessage = DUP_Handler::getVarLog();
27
+ if (!empty($logMessage)) {
28
+ echo nl2br($logMessage);
29
+ }
30
+ }
31
+
32
+ /**
33
+ * DUPLICATOR_PACKAGE_SCAN
34
+ * Returns a JSON scan report object which contains data about the system
35
+ *
36
+ * @return json JSON report object
37
+ * @example to test: /wp-admin/admin-ajax.php?action=duplicator_package_scan
38
+ */
39
+ function duplicator_package_scan()
40
+ {
41
+ DUP_Handler::init_error_handler();
42
+ DUP_Handler::setMode(DUP_Handler::MODE_VAR);
43
+ register_shutdown_function('duplicator_package_scan_shutdown');
44
+ check_ajax_referer('duplicator_package_scan', 'nonce');
45
+ DUP_Util::hasCapability('export');
46
+ header('Content-Type: application/json;');
47
+ @ob_flush();
48
+ @set_time_limit(0);
49
+ $errLevel = error_reporting();
50
+ error_reporting(E_ERROR);
51
+ DUP_Util::initSnapshotDirectory();
52
+ $package = DUP_Package::getActive();
53
+ $report = $package->runScanner();
54
+ $package->saveActiveItem('ScanFile', $package->ScanFile);
55
+ $package->Archive->saveActiveItem($package, 'dirsCount', $package->Archive->dirsCount);
56
+ $package->Archive->saveActiveItem($package, 'filesCount', $package->Archive->filesCount);
57
+ $json_response = DUP_JSON::safeEncode($report);
58
+ DUP_Package::tempFileCleanup();
59
+ error_reporting($errLevel);
60
+ die($json_response);
61
+ }
62
+
63
+ /**
64
+ * duplicator_package_build
65
+ * Returns the package result status
66
+ *
67
+ * @return json JSON object of package results
68
+ */
69
+ function duplicator_package_build()
70
+ {
71
+ DUP_Handler::init_error_handler();
72
+ check_ajax_referer('duplicator_package_build', 'nonce');
73
+ header('Content-Type: application/json');
74
+ $Package = null;
75
+ try {
76
+ DUP_Util::hasCapability('export', DUP_Util::SECURE_ISSUE_THROW);
77
+ @set_time_limit(0);
78
+ $errLevel = error_reporting();
79
+ error_reporting(E_ERROR);
80
+ DUP_Util::initSnapshotDirectory();
81
+ $Package = DUP_Package::getActive();
82
+ $Package->save('zip');
83
+ DUP_Settings::Set('active_package_id', $Package->ID);
84
+ DUP_Settings::Save();
85
+ if (!is_readable(DUP_Settings::getSsdirTmpPath() . "/{$Package->ScanFile}")) {
86
+ die("The scan result file was not found. Please run the scan step before building the package.");
87
+ }
88
+
89
+ $Package->runZipBuild();
90
+ //JSON:Debug Response
91
+ //Pass = 1, Warn = 2, Fail = 3
92
+ $json = array();
93
+ $json['status'] = 1;
94
+ $json['error'] = '';
95
+ $json['package'] = $Package;
96
+ $json['instDownloadName'] = $Package->getInstDownloadName();
97
+ $json['runtime'] = $Package->Runtime;
98
+ $json['exeSize'] = $Package->ExeSize;
99
+ $json['archiveSize'] = $Package->ZipSize;
100
+ //Simulate a Host Build Interrupt
101
+ //die(0);
102
+ } catch (Exception $e) {
103
+ $Package->setStatus(DUP_PackageStatus::ERROR);
104
+ //JSON:Debug Response
105
+ //Pass = 1, Warn = 2, Fail = 3
106
+ $json = array();
107
+ $json['status'] = 3;
108
+ $json['error'] = $e->getMessage() . "\n" . "FILE: " . $e->getFile() . "[" . $e->getLine() . "]\n" . $e->getTraceAsString();
109
+ $json['package'] = $Package;
110
+ $json['instDownloadName'] = null;
111
+ $json['runtime'] = null;
112
+ $json['exeSize'] = null;
113
+ $json['archiveSize'] = null;
114
+ }
115
+ $json_response = SnapJson::jsonEncode($json);
116
+ error_reporting($errLevel);
117
+ die($json_response);
118
+ }
119
+
120
+ /**
121
+ * Returns the package result status
122
+ *
123
+ * @return json JSON object of package results
124
+ */
125
+ function duplicator_duparchive_package_build()
126
+ {
127
+ DUP_Handler::init_error_handler();
128
+ DUP_Log::Info('[CTRL DUP ARCIVE] CALL TO ' . __FUNCTION__);
129
+ check_ajax_referer('duplicator_duparchive_package_build', 'nonce');
130
+ DUP_Util::hasCapability('export');
131
+ header('Content-Type: application/json');
132
+ @set_time_limit(0);
133
+ $errLevel = error_reporting();
134
+ error_reporting(E_ERROR);
135
+ // The DupArchive build process always works on a saved package so the first time through save the active package to the package table.
136
+ // After that, just retrieve it.
137
+ $active_package_id = DUP_Settings::Get('active_package_id');
138
+ DUP_Log::Info('[CTRL DUP ARCIVE] CURRENT PACKAGE ACTIVE ' . $active_package_id);
139
+ if ($active_package_id == -1) {
140
+ $package = DUP_Package::getActive();
141
+ $package->save('daf');
142
+ DUP_Log::Info('[CTRL DUP ARCIVE] PACKAGE AS NEW ID ' . $package->ID . ' SAVED | STATUS:' . $package->Status);
143
+ //DUP_Log::TraceObject("[CTRL DUP ARCIVE] PACKAGE SAVED:", $package);
144
+ DUP_Settings::Set('active_package_id', $package->ID);
145
+ DUP_Settings::Save();
146
+ } else {
147
+ if (($package = DUP_Package::getByID($active_package_id)) == null) {
148
+ DUP_Log::Info('[CTRL DUP ARCIVE] ERROR: Get package by id ' . $active_package_id . ' FAILED');
149
+ die('Get package by id ' . $active_package_id . ' FAILED');
150
+ }
151
+ DUP_Log::Info('[CTRL DUP ARCIVE] PACKAGE GET BY ID ' . $active_package_id . ' | STATUS:' . $package->Status);
152
+ // DUP_Log::TraceObject("getting active package by id {$active_package_id}", $package);
153
+ }
154
+
155
+ if (!is_readable(DUP_Settings::getSsdirTmpPath() . "/{$package->ScanFile}")) {
156
+ DUP_Log::Info('[CTRL DUP ARCIVE] ERROR: The scan result file was not found. Please run the scan step before building the package.');
157
+ die("The scan result file was not found. Please run the scan step before building the package.");
158
+ }
159
+
160
+ if ($package === null) {
161
+ DUP_Log::Info('[CTRL DUP ARCIVE] There is no active package.');
162
+ die("There is no active package.");
163
+ }
164
+
165
+ if ($package->Status == DUP_PackageStatus::ERROR) {
166
+ $package->setStatus(DUP_PackageStatus::ERROR);
167
+ $hasCompleted = true;
168
+ } else {
169
+ try {
170
+ $hasCompleted = $package->runDupArchiveBuild();
171
+ } catch (Exception $ex) {
172
+ DUP_Log::Info('[CTRL DUP ARCIVE] ERROR: caught exception');
173
+ Dup_Log::error('[CTRL DUP ARCIVE] Caught exception', $ex->getMessage(), Dup_ErrorBehavior::LogOnly);
174
+ DUP_Log::Info('[CTRL DUP ARCIVE] ERROR: after log');
175
+ $package->setStatus(DUP_PackageStatus::ERROR);
176
+ $hasCompleted = true;
177
+ }
178
+ }
179
+
180
+ $json = array();
181
+ $json['failures'] = array_merge($package->BuildProgress->build_failures, $package->BuildProgress->validation_failures);
182
+ if (!empty($json['failures'])) {
183
+ DUP_Log::Info('[CTRL DUP ARCIVE] FAILURES ' . print_r($json['failures'], true));
184
+ }
185
+
186
+ //JSON:Debug Response
187
+ //Pass = 1, Warn = 2, 3 = Failure, 4 = Not Done
188
+ if ($hasCompleted) {
189
+ DUP_Log::Info('[CTRL DUP ARCIVE] COMPLETED PACKAGE STATUS: ' . $package->Status);
190
+ if ($package->Status == DUP_PackageStatus::ERROR) {
191
+ DUP_Log::Info('[CTRL DUP ARCIVE] ERROR');
192
+ $error_message = __('Error building DupArchive package') . '<br/>';
193
+ foreach ($json['failures'] as $failure) {
194
+ $error_message .= implode(',', $failure->description);
195
+ }
196
+
197
+ Dup_Log::error("Build failed so sending back error", esc_html($error_message), Dup_ErrorBehavior::LogOnly);
198
+ DUP_Log::Info('[CTRL DUP ARCIVE] ERROR AFTER LOG 2');
199
+ $json['status'] = 3;
200
+ } else {
201
+ Dup_Log::Info("sending back success status");
202
+ $json['status'] = 1;
203
+ }
204
+
205
+ Dup_Log::Trace('#### json package');
206
+ $json['package'] = $package;
207
+ $json['instDownloadName'] = $package->getInstDownloadName();
208
+ $json['runtime'] = $package->Runtime;
209
+ $json['exeSize'] = $package->ExeSize;
210
+ $json['archiveSize'] = $package->ZipSize;
211
+ DUP_Log::Trace('[CTRL DUP ARCIVE] JSON PACKAGE');
212
+ } else {
213
+ DUP_Log::Info('[CTRL DUP ARCIVE] sending back continue status PACKAGE STATUS: ' . $package->Status);
214
+ $json['status'] = 4;
215
+ }
216
+
217
+ $json_response = SnapJson::jsonEncode($json);
218
+ Dup_Log::TraceObject('json response', $json_response);
219
+ error_reporting($errLevel);
220
+ die($json_response);
221
+ }
222
+
223
+ /**
224
+ * DUPLICATOR_PACKAGE_DELETE
225
+ * Deletes the files and database record entries
226
+ *
227
+ * @return json A JSON message about the action.
228
+ * Use console.log to debug from client
229
+ */
230
+ function duplicator_package_delete()
231
+ {
232
+ DUP_Handler::init_error_handler();
233
+ check_ajax_referer('duplicator_package_delete', 'nonce');
234
+ $json = array(
235
+ 'success' => false,
236
+ 'message' => ''
237
+ );
238
+ $package_ids = filter_input(INPUT_POST, 'package_ids', FILTER_VALIDATE_INT, array(
239
+ 'flags' => FILTER_REQUIRE_ARRAY,
240
+ 'options' => array(
241
+ 'default' => false
242
+ )
243
+ ));
244
+ $delCount = 0;
245
+ try {
246
+ DUP_Util::hasCapability('export', DUP_Util::SECURE_ISSUE_THROW);
247
+ if ($package_ids === false || in_array(false, $package_ids)) {
248
+ throw new Exception('Invalid Request.', 'duplicator');
249
+ }
250
+
251
+ foreach ($package_ids as $id) {
252
+ $package = DUP_Package::getByID($id);
253
+ if ($package === null) {
254
+ throw new Exception('Invalid Request.', 'duplicator');
255
+ }
256
+
257
+ $package->delete();
258
+ $delCount++;
259
+ }
260
+
261
+ $json['success'] = true;
262
+ $json['ids'] = $package_ids;
263
+ $json['removed'] = $delCount;
264
+ } catch (Exception $ex) {
265
+ $json['message'] = $ex->getMessage();
266
+ }
267
+
268
+ die(SnapJson::jsonEncode($json));
269
+ }
270
+
271
+ /**
272
+ * Active package info
273
+ * Returns a JSON scan report active package info or
274
+ * active_package_present == false if no active package is present.
275
+ *
276
+ * @return json
277
+ */
278
+ function duplicator_active_package_info()
279
+ {
280
+ ob_start();
281
+ try {
282
+ DUP_Handler::init_error_handler();
283
+ DUP_Util::hasCapability('export', DUP_Util::SECURE_ISSUE_THROW);
284
+ if (!check_ajax_referer('duplicator_active_package_info', 'nonce', false)) {
285
+ throw new Exception(__('An unauthorized security request was made to this page. Please try again!', 'duplicator'));
286
+ }
287
+
288
+ global $wpdb;
289
+ $error = false;
290
+ $result = array(
291
+ 'active_package' => array(
292
+ 'present' => false,
293
+ 'status' => 0,
294
+ 'size' => 0
295
+ ),
296
+ 'html' => '',
297
+ 'message' => ''
298
+ );
299
+ $result['active_package']['present'] = DUP_Package::is_active_package_present();
300
+ if ($result['active_package']['present']) {
301
+ $id = DUP_Settings::Get('active_package_id');
302
+ $package = DUP_Package::getByID($id);
303
+ if (is_null($package)) {
304
+ throw new Exception(__('Active package object error', 'duplicator'));
305
+ }
306
+ $result['active_package']['status'] = $package->Status;
307
+ $result['active_package']['size'] = $package->getArchiveSize();
308
+ $result['active_package']['size_format'] = DUP_Util::byteSize($package->getArchiveSize());
309
+ }
310
+ } catch (Exception $e) {
311
+ $error = true;
312
+ $result['message'] = $e->getMessage();
313
+ }
314
+
315
+ $result['html'] = ob_get_clean();
316
+ if ($error) {
317
+ wp_send_json_error($result);
318
+ } else {
319
+ wp_send_json_success($result);
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Controller for Tools
325
+ * @package Duplicator\ctrls
326
+ */
327
+ class DUP_CTRL_Package extends DUP_CTRL_Base
328
+ {
329
+ /**
330
+ * Init this instance of the object
331
+ */
332
+ public function __construct()
333
+ {
334
+ add_action('wp_ajax_DUP_CTRL_Package_addQuickFilters', array($this, 'addQuickFilters'));
335
+ add_action('wp_ajax_DUP_CTRL_Package_getPackageFile', array($this, 'getPackageFile'));
336
+ add_action('wp_ajax_DUP_CTRL_Package_getActivePackageStatus', array($this, 'getActivePackageStatus'));
337
+ }
338
+
339
+ /**
340
+ * Removed all reserved installer files names
341
+ *
342
+ * @param string $_POST['dir_paths'] A semi-colon separated list of directory paths
343
+ *
344
+ * @return string Returns all of the active directory filters as a ";" separated string
345
+ */
346
+ public function addQuickFilters()
347
+ {
348
+ DUP_Handler::init_error_handler();
349
+ check_ajax_referer('DUP_CTRL_Package_addQuickFilters', 'nonce');
350
+ $result = new DUP_CTRL_Result($this);
351
+ $inputData = filter_input_array(INPUT_POST, array(
352
+ 'dir_paths' => array(
353
+ 'filter' => FILTER_DEFAULT,
354
+ 'flags' => FILTER_REQUIRE_SCALAR,
355
+ 'options' => array(
356
+ 'default' => ''
357
+ )
358
+ ),
359
+ 'file_paths' => array(
360
+ 'filter' => FILTER_DEFAULT,
361
+ 'flags' => FILTER_REQUIRE_SCALAR,
362
+ 'options' => array(
363
+ 'default' => ''
364
+ )
365
+ ),
366
+ ));
367
+ try {
368
+ DUP_Util::hasCapability('export', DUP_Util::SECURE_ISSUE_THROW);
369
+ //CONTROLLER LOGIC
370
+ $package = DUP_Package::getActive();
371
+ //DIRS
372
+ $dir_filters = ($package->Archive->FilterOn) ? $package->Archive->FilterDirs . ';' . $inputData['dir_paths'] : $inputData['dir_paths'];
373
+ $dir_filters = $package->Archive->parseDirectoryFilter($dir_filters);
374
+ $changed = $package->Archive->saveActiveItem($package, 'FilterDirs', $dir_filters);
375
+ //FILES
376
+ $file_filters = ($package->Archive->FilterOn) ? $package->Archive->FilterFiles . ';' . $inputData['file_paths'] : $inputData['file_paths'];
377
+ $file_filters = $package->Archive->parseFileFilter($file_filters);
378
+ $changed = $package->Archive->saveActiveItem($package, 'FilterFiles', $file_filters);
379
+ if (!$package->Archive->FilterOn && !empty($package->Archive->FilterExts)) {
380
+ $changed = $package->Archive->saveActiveItem($package, 'FilterExts', '');
381
+ }
382
+
383
+ $changed = $package->Archive->saveActiveItem($package, 'FilterOn', 1);
384
+ //Result
385
+ $package = DUP_Package::getActive();
386
+ $payload['dirs-in'] = esc_html(sanitize_text_field($inputData['dir_paths']));
387
+ $payload['dir-out'] = esc_html($package->Archive->FilterDirs);
388
+ $payload['files-in'] = esc_html(sanitize_text_field($inputData['file_paths']));
389
+ $payload['files-out'] = esc_html($package->Archive->FilterFiles);
390
+ //RETURN RESULT
391
+ $test = ($changed) ? DUP_CTRL_Status::SUCCESS : DUP_CTRL_Status::FAILED;
392
+ $result->process($payload, $test);
393
+ } catch (Exception $exc) {
394
+ $result->processError($exc);
395
+ }
396
+ }
397
+
398
+ /**
399
+ * Get active package status
400
+ *
401
+ * <code>
402
+ * //JavaScript Ajax Request
403
+ * Duplicator.Package.getActivePackageStatus()
404
+ * </code>
405
+ */
406
+ public function getActivePackageStatus()
407
+ {
408
+ DUP_Handler::init_error_handler();
409
+ check_ajax_referer('DUP_CTRL_Package_getActivePackageStatus', 'nonce');
410
+ $result = new DUP_CTRL_Result($this);
411
+ try {
412
+ DUP_Util::hasCapability('export', DUP_Util::SECURE_ISSUE_THROW);
413
+ //CONTROLLER LOGIC
414
+ $active_package_id = DUP_Settings::Get('active_package_id');
415
+ $package = DUP_Package::getByID($active_package_id);
416
+ $payload = array();
417
+ if ($package != null) {
418
+ $test = DUP_CTRL_Status::SUCCESS;
419
+ $payload['status'] = $package->Status;
420
+ } else {
421
+ $test = DUP_CTRL_Status::FAILED;
422
+ }
423
+
424
+ //RETURN RESULT
425
+ return $result->process($payload, $test);
426
+ } catch (Exception $exc) {
427
+ $result->processError($exc);
428
+ }
429
+ }
430
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ctrls/ctrl.tools.php CHANGED
@@ -1,132 +1,183 @@
1
- <?php
2
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
- // Exit if accessed directly
4
- if (! defined('DUPLICATOR_VERSION')) exit;
5
-
6
- require_once(DUPLICATOR_PLUGIN_PATH . '/ctrls/ctrl.base.php');
7
- require_once(DUPLICATOR_PLUGIN_PATH . '/classes/utilities/class.u.scancheck.php');
8
-
9
- /**
10
- * Controller for Tools
11
- * @package Duplicator\ctrls
12
- */
13
- class DUP_CTRL_Tools extends DUP_CTRL_Base
14
- {
15
- /**
16
- * Init this instance of the object
17
- */
18
- function __construct()
19
- {
20
- add_action('wp_ajax_DUP_CTRL_Tools_runScanValidator', array($this, 'runScanValidator'));
21
- add_action('wp_ajax_DUP_CTRL_Tools_getTraceLog', array($this, 'getTraceLog'));
22
- }
23
-
24
- /**
25
- * Calls the ScanValidator and returns a JSON result
26
- *
27
- * @notes: Testing = /wp-admin/admin-ajax.php?action=DUP_CTRL_Tools_runScanValidator
28
- */
29
- public function runScanValidator()
30
- {
31
- DUP_Handler::init_error_handler();
32
- check_ajax_referer('DUP_CTRL_Tools_runScanValidator', 'nonce');
33
-
34
- @set_time_limit(0);
35
-
36
- $isValid = true;
37
- $inputData = filter_input_array(INPUT_POST, array(
38
- 'recursive_scan' => array(
39
- 'filter' => FILTER_VALIDATE_BOOLEAN,
40
- 'flags' => FILTER_NULL_ON_FAILURE
41
- )
42
- ));
43
-
44
- if (is_null($inputData['recursive_scan'])) {
45
- $isValid = false;
46
- }
47
-
48
- $result = new DUP_CTRL_Result($this);
49
- try {
50
- DUP_Util::hasCapability('export', DUP_Util::SECURE_ISSUE_THROW);
51
-
52
- if (!$isValid) {
53
- throw new Exception(__('Invalid Request.', 'duplicator'));
54
- }
55
- //CONTROLLER LOGIC
56
- $path = duplicator_get_abs_path();
57
- if (!is_dir($path)) {
58
- throw new Exception("Invalid directory provided '{$path}'!");
59
- }
60
-
61
- $scanner = new DUP_ScanCheck();
62
- $scanner->recursion = $inputData['recursive_scan'];
63
- $payload = $scanner->run($path);
64
-
65
- //RETURN RESULT
66
- $test = ($payload->fileCount > 0)
67
- ? DUP_CTRL_Status::SUCCESS
68
- : DUP_CTRL_Status::FAILED;
69
- $result->process($payload, $test);
70
- } catch (Exception $exc) {
71
- $result->processError($exc);
72
- }
73
- }
74
-
75
- public function getTraceLog()
76
- {
77
- DUP_Log::Trace("enter");
78
-
79
- check_ajax_referer('DUP_CTRL_Tools_getTraceLog', 'nonce');
80
- Dup_Util::hasCapability('export');
81
-
82
- $file_path = DUP_Log::GetTraceFilepath();
83
- $backup_path = DUP_Log::GetBackupTraceFilepath();
84
- $zip_path = DUP_Settings::getSsdirPath()."/".DUPLICATOR_ZIPPED_LOG_FILENAME;
85
- $zipped = DUP_Zip_U::zipFile($file_path, $zip_path, true, null, true);
86
-
87
- if ($zipped && file_exists($backup_path)) {
88
- $zipped = DUP_Zip_U::zipFile($backup_path, $zip_path, false, null, true);
89
- }
90
-
91
- header("Pragma: public");
92
- header("Expires: 0");
93
- header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
94
- header("Cache-Control: private", false);
95
- header("Content-Transfer-Encoding: binary");
96
-
97
- $fp = fopen($zip_path, 'rb');
98
-
99
- if (($fp !== false) && $zipped) {
100
- $zip_filename = basename($zip_path);
101
-
102
- header("Content-Type: application/octet-stream");
103
- header("Content-Disposition: attachment; filename=\"$zip_filename\";");
104
-
105
- // required or large files wont work
106
- if (ob_get_length()) {
107
- ob_end_clean();
108
- }
109
-
110
- DUP_Log::trace("streaming $zip_path");
111
- if (fpassthru($fp) === false) {
112
- DUP_Log::trace("Error with fpassthru for $zip_path");
113
- }
114
-
115
- fclose($fp);
116
- @unlink($zip_path);
117
- } else {
118
- header("Content-Type: text/plain");
119
- header("Content-Disposition: attachment; filename=\"error.txt\";");
120
- if ($zipped === false) {
121
- $message = "Couldn't create zip file.";
122
- } else {
123
- $message = "Couldn't open $file_path.";
124
- }
125
- DUP_Log::trace($message);
126
- echo esc_html($message);
127
- }
128
-
129
- exit;
130
- }
131
-
132
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ use Duplicator\Core\Controllers\ControllersManager;
4
+ use Duplicator\Libs\Snap\SnapURL;
5
+ use Duplicator\Libs\Snap\SnapUtil;
6
+
7
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
8
+ // Exit if accessed directly
9
+ if (! defined('DUPLICATOR_VERSION')) {
10
+ exit;
11
+ }
12
+
13
+ require_once(DUPLICATOR_PLUGIN_PATH . '/ctrls/ctrl.base.php');
14
+ require_once(DUPLICATOR_PLUGIN_PATH . '/classes/utilities/class.u.scancheck.php');
15
+
16
+ /**
17
+ * Controller for Tools
18
+ * @package Duplicator\ctrls
19
+ */
20
+ class DUP_CTRL_Tools extends DUP_CTRL_Base
21
+ {
22
+ /**
23
+ * Init this instance of the object
24
+ */
25
+ public function __construct()
26
+ {
27
+ add_action('wp_ajax_DUP_CTRL_Tools_runScanValidator', array($this, 'runScanValidator'));
28
+ add_action('wp_ajax_DUP_CTRL_Tools_getTraceLog', array($this, 'getTraceLog'));
29
+ }
30
+
31
+ /**
32
+ *
33
+ * @return boolean
34
+ */
35
+ public static function isToolPage()
36
+ {
37
+ return ControllersManager::isCurrentPage('duplicator-tools');
38
+ }
39
+
40
+ /**
41
+ *
42
+ * @return boolean
43
+ */
44
+ public static function isDiagnosticPage()
45
+ {
46
+ return ControllersManager::isCurrentPage('duplicator-tools', 'diagnostics');
47
+ }
48
+
49
+ /**
50
+ * Return diagnostic URL
51
+ *
52
+ * @param bool $relative if true return relative URL else absolute
53
+ *
54
+ * @return string
55
+ */
56
+ public static function getDiagnosticURL($relative = true)
57
+ {
58
+ return ControllersManager::getMenuLink(
59
+ 'duplicator-tools',
60
+ 'diagnostics',
61
+ '',
62
+ array(),
63
+ $relative
64
+ );
65
+ }
66
+
67
+ /**
68
+ * Return clean installer files action URL
69
+ *
70
+ * @param bool $relative if true return relative URL else absolute
71
+ *
72
+ * @return string
73
+ */
74
+ public static function getCleanFilesAcrtionUrl($relative = true)
75
+ {
76
+ return ControllersManager::getMenuLink(
77
+ 'duplicator-tools',
78
+ 'diagnostics',
79
+ '',
80
+ array(
81
+ 'action' => 'installer',
82
+ '_wpnonce' => wp_create_nonce('duplicator_cleanup_page')
83
+ ),
84
+ $relative
85
+ );
86
+ }
87
+
88
+ /**
89
+ * Calls the ScanValidator and returns a JSON result
90
+ *
91
+ * @notes: Testing = /wp-admin/admin-ajax.php?action=DUP_CTRL_Tools_runScanValidator
92
+ */
93
+ public function runScanValidator()
94
+ {
95
+ DUP_Handler::init_error_handler();
96
+ check_ajax_referer('DUP_CTRL_Tools_runScanValidator', 'nonce');
97
+ @set_time_limit(0);
98
+ $isValid = true;
99
+ $inputData = filter_input_array(INPUT_POST, array(
100
+ 'recursive_scan' => array(
101
+ 'filter' => FILTER_VALIDATE_BOOLEAN,
102
+ 'flags' => FILTER_NULL_ON_FAILURE
103
+ )
104
+ ));
105
+ if (is_null($inputData['recursive_scan'])) {
106
+ $isValid = false;
107
+ }
108
+
109
+ $result = new DUP_CTRL_Result($this);
110
+ try {
111
+ DUP_Util::hasCapability('export', DUP_Util::SECURE_ISSUE_THROW);
112
+ if (!$isValid) {
113
+ throw new Exception(__('Invalid Request.', 'duplicator'));
114
+ }
115
+ //CONTROLLER LOGIC
116
+ $path = duplicator_get_abs_path();
117
+ if (!is_dir($path)) {
118
+ throw new Exception("Invalid directory provided '{$path}'!");
119
+ }
120
+
121
+ $scanner = new DUP_ScanCheck();
122
+ $scanner->recursion = $inputData['recursive_scan'];
123
+ $payload = $scanner->run($path);
124
+ //RETURN RESULT
125
+ $test = ($payload->fileCount > 0)
126
+ ? DUP_CTRL_Status::SUCCESS
127
+ : DUP_CTRL_Status::FAILED;
128
+ $result->process($payload, $test);
129
+ } catch (Exception $exc) {
130
+ $result->processError($exc);
131
+ }
132
+ }
133
+
134
+ public function getTraceLog()
135
+ {
136
+ DUP_Log::Trace("enter");
137
+ check_ajax_referer('DUP_CTRL_Tools_getTraceLog', 'nonce');
138
+ Dup_Util::hasCapability('export');
139
+ $file_path = DUP_Log::GetTraceFilepath();
140
+ $backup_path = DUP_Log::GetBackupTraceFilepath();
141
+ $zip_path = DUP_Settings::getSsdirPath() . "/" . DUPLICATOR_ZIPPED_LOG_FILENAME;
142
+ $zipped = DUP_Zip_U::zipFile($file_path, $zip_path, true, null, true);
143
+ if ($zipped && file_exists($backup_path)) {
144
+ $zipped = DUP_Zip_U::zipFile($backup_path, $zip_path, false, null, true);
145
+ }
146
+
147
+ header("Pragma: public");
148
+ header("Expires: 0");
149
+ header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
150
+ header("Cache-Control: private", false);
151
+ header("Content-Transfer-Encoding: binary");
152
+ $fp = fopen($zip_path, 'rb');
153
+ if (($fp !== false) && $zipped) {
154
+ $zip_filename = basename($zip_path);
155
+ header("Content-Type: application/octet-stream");
156
+ header("Content-Disposition: attachment; filename=\"$zip_filename\";");
157
+ // required or large files wont work
158
+ if (ob_get_length()) {
159
+ ob_end_clean();
160
+ }
161
+
162
+ DUP_Log::trace("streaming $zip_path");
163
+ if (fpassthru($fp) === false) {
164
+ DUP_Log::trace("Error with fpassthru for $zip_path");
165
+ }
166
+
167
+ fclose($fp);
168
+ @unlink($zip_path);
169
+ } else {
170
+ header("Content-Type: text/plain");
171
+ header("Content-Disposition: attachment; filename=\"error.txt\";");
172
+ if ($zipped === false) {
173
+ $message = "Couldn't create zip file.";
174
+ } else {
175
+ $message = "Couldn't open $file_path.";
176
+ }
177
+ DUP_Log::trace($message);
178
+ echo esc_html($message);
179
+ }
180
+
181
+ exit;
182
+ }
183
+ }
ctrls/ctrl.ui.php CHANGED
@@ -1,41 +1,43 @@
1
  <?php
 
2
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
3
  // Exit if accessed directly
4
- if (! defined('DUPLICATOR_VERSION')) exit;
 
 
5
 
6
- require_once(DUPLICATOR_PLUGIN_PATH . '/ctrls/ctrl.base.php');
7
  require_once(DUPLICATOR_PLUGIN_PATH . '/classes/ui/class.ui.viewstate.php');
8
 
9
  /**
10
- * Controller for Tools
11
  * @package Duplicator\ctrls
12
  */
13
  class DUP_CTRL_UI extends DUP_CTRL_Base
14
- {
15
-
16
- function __construct()
17
- {
18
- add_action('wp_ajax_DUP_CTRL_UI_SaveViewState', array($this, 'SaveViewState'));
19
- }
20
 
21
 
22
- /**
23
  * Calls the SaveViewState and returns a JSON result
24
- *
25
- * @param string $_POST['key'] A unique key that identifies the state of the UI element
26
- * @param bool $_POST['value'] The value to store for the state of the UI element
27
- *
28
- * @notes: Testing: See Testing Interface
29
- * URL = /wp-admin/admin-ajax.php?action=DUP_CTRL_UI_SaveViewState
30
- *
31
- * <code>
32
- * //JavaScript Ajax Request
33
- * Duplicator.UI.SaveViewState('dup-pack-archive-panel', 1);
34
- *
35
- * //Call PHP Code
36
- * $view_state = DUP_UI_ViewState::getValue('dup-pack-archive-panel');
37
- * $ui_css_archive = ($view_state == 1) ? 'display:block' : 'display:none';
38
- * </code>
39
  */
40
  public function SaveViewState()
41
  {
@@ -133,33 +135,30 @@ class DUP_CTRL_UI extends DUP_CTRL_Base
133
  $result->processError($exc);
134
  }
135
  }
136
-
137
- /**
138
  * Returns a JSON list of all saved view state items
139
- *
140
- *
141
- * <code>
142
- * See SaveViewState()
143
- * </code>
144
  */
145
- public function GetViewStateList()
146
- {
147
- $result = new DUP_CTRL_Result($this);
148
-
149
- try
150
- {
151
- //CONTROLLER LOGIC
152
- $payload = DUP_UI_ViewState::getArray();
153
-
154
- //RETURN RESULT
155
- $test = (is_array($payload) && count($payload))
156
- ? DUP_CTRL_Status::SUCCESS
157
- : DUP_CTRL_Status::FAILED;
158
- return $result->process($payload, $test);
159
- }
160
- catch (Exception $exc)
161
- {
162
- $result->processError($exc);
163
- }
164
- }
165
  }
1
  <?php
2
+
3
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
4
  // Exit if accessed directly
5
+ if (! defined('DUPLICATOR_VERSION')) {
6
+ exit;
7
+ }
8
 
9
+ require_once(DUPLICATOR_PLUGIN_PATH . '/ctrls/ctrl.base.php');
10
  require_once(DUPLICATOR_PLUGIN_PATH . '/classes/ui/class.ui.viewstate.php');
11
 
12
  /**
13
+ * Controller for Tools
14
  * @package Duplicator\ctrls
15
  */
16
  class DUP_CTRL_UI extends DUP_CTRL_Base
17
+ {
18
+ public function __construct()
19
+ {
20
+ add_action('wp_ajax_DUP_CTRL_UI_SaveViewState', array($this, 'SaveViewState'));
21
+ }
 
22
 
23
 
24
+ /**
25
  * Calls the SaveViewState and returns a JSON result
26
+ *
27
+ * @param string $_POST['key'] A unique key that identifies the state of the UI element
28
+ * @param bool $_POST['value'] The value to store for the state of the UI element
29
+ *
30
+ * @notes: Testing: See Testing Interface
31
+ * URL = /wp-admin/admin-ajax.php?action=DUP_CTRL_UI_SaveViewState
32
+ *
33
+ * <code>
34
+ * //JavaScript Ajax Request
35
+ * Duplicator.UI.SaveViewState('dup-pack-archive-panel', 1);
36
+ *
37
+ * //Call PHP Code
38
+ * $view_state = DUP_UI_ViewState::getValue('dup-pack-archive-panel');
39
+ * $ui_css_archive = ($view_state == 1) ? 'display:block' : 'display:none';
40
+ * </code>
41
  */
42
  public function SaveViewState()
43
  {
135
  $result->processError($exc);
136
  }
137
  }
138
+
139
+ /**
140
  * Returns a JSON list of all saved view state items
141
+ *
142
+ *
143
+ * <code>
144
+ * See SaveViewState()
145
+ * </code>
146
  */
147
+ public function GetViewStateList()
148
+ {
149
+ $result = new DUP_CTRL_Result($this);
150
+
151
+ try {
152
+ //CONTROLLER LOGIC
153
+ $payload = DUP_UI_ViewState::getArray();
154
+
155
+ //RETURN RESULT
156
+ $test = (is_array($payload) && count($payload))
157
+ ? DUP_CTRL_Status::SUCCESS
158
+ : DUP_CTRL_Status::FAILED;
159
+ return $result->process($payload, $test);
160
+ } catch (Exception $exc) {
161
+ $result->processError($exc);
162
+ }
163
+ }
 
 
 
164
  }
ctrls/index.php CHANGED
@@ -1,2 +1,3 @@
1
  <?php
2
- //silent
 
1
  <?php
2
+
3
+ //silent
deactivation.php CHANGED
@@ -1,440 +1,434 @@
1
- <?php
2
- /**
3
- * Standard: PSR-2
4
- * @link http://www.php-fig.org/psr/psr-2 Full Documentation
5
- *
6
- */
7
- defined('ABSPATH') || defined('DUPXABSPATH') || exit;
8
-
9
- function duplicator_deactivation_enqueue_scripts($hook)
10
- {
11
- if ('plugins.php' == $hook && !defined('DOING_AJAX')) {
12
- wp_enqueue_style('duplicator-deactivation-modal', DUPLICATOR_PLUGIN_URL.'assets/css/modal.css', array(), '1.0.0');
13
- }
14
- }
15
- add_action('admin_enqueue_scripts', 'duplicator_deactivation_enqueue_scripts');
16
-
17
- if (!function_exists('duplicator_plugins_admin_footer')) {
18
-
19
- function duplicator_plugins_admin_footer()
20
- {
21
- global $hook_suffix;
22
-
23
- if ('plugins.php' == $hook_suffix && !defined('DOING_AJAX')) {
24
- duplicator_add_deactivation_feedback_dialog_box();
25
- }
26
- }
27
- }
28
- add_action('admin_footer', 'duplicator_plugins_admin_footer');
29
-
30
- /**
31
- * Displays a confirmation and feedback dialog box when the user clicks on the "Deactivate" link on the plugins
32
- * page.
33
- *
34
- * @since 2.1.3
35
- */
36
- if (!function_exists('duplicator_add_deactivation_feedback_dialog_box')) {
37
-
38
- function duplicator_add_deactivation_feedback_dialog_box()
39
- {
40
- $basename = 'duplicator/duplicator.php';
41
- /*
42
- $slug = dirname( $basename );
43
- $plugin_id = sanitize_title( $plugin_data['Name'] );
44
- */
45
- $slug = 'duplicator';
46
- $plugin_id = 'duplicator';
47
-
48
- $contact_support_template = __('Need help? We are ready to answer your questions.', 'duplicator').' <a href="https://snapcreek.com/ticket/" target="_blank">'.__('Contact Support', 'duplicator').'</a>';
49
-
50
- $reasons = array(
51
- array(
52
- 'id' => 'NOT_WORKING',
53
- 'text' => __("It's not working on my server.", 'duplicator'),
54
- 'input_type' => 'textarea',
55
- 'input_placeholder' => __("Kindly share what didn't work so we can fix it in future updates...", 'duplicator'),
56
- 'internal_message' => $contact_support_template
57
- ),
58
- array(
59
- 'id' => 'CONFUSING_TO_UNDERSTAND',
60
- 'text' => __("It's too confusing to understand.", 'duplicator'),
61
- 'input_type' => 'textarea',
62
- 'input_placeholder' => __('Please tell us what is not clear so that we can improve it.', 'duplicator'),
63
- 'internal_message' => $contact_support_template
64
- ),
65
- array(
66
- 'id' => 'FOUND_A_DIFFERENT_PLUGIN',
67
- 'text' => __('I found a different plugin that I like better.', 'duplicator'),
68
- 'input_type' => 'textfield',
69
- 'input_placeholder' => __("What's the plugin name?", 'duplicator')
70
- ),
71
- array(
72
- 'id' => 'NOT_DO_WHAT_I_NEED',
73
- 'text' => __("It does not do what I need.", 'duplicator'),
74
- 'input_type' => 'textarea',
75
- 'input_placeholder' => __('What does it need to do?', 'duplicator')
76
- ),
77
- array(
78
- 'id' => 'TEMPORARY_DEACTIVATION',
79
- 'text' => __("It's a temporary deactivation, I use the plugin all the time.", 'duplicator'),
80
- 'input_type' => '',
81
- 'input_placeholder' => ''
82
- ),
83
- array(
84
- 'id' => 'SWITCHING_PRO_VERSION',
85
- 'text' => sprintf(__("I'm switching over to the %s", 'duplicator'), '<a href="https://snapcreek.com/duplicator/" target="_blank">'.__('Pro version', 'duplicator').'</a>'),
86
- 'input_type' => '',
87
- 'input_placeholder' => ''
88
- ),
89
- /*
90
- array(
91
- 'id' => 'OTHER',
92
- 'text' => __('Other', 'duplicator'),
93
- 'input_type' => 'textarea',
94
- 'input_placeholder' => __('Please tell us the reason so we can improve it.', 'duplicator')
95
- )
96
- */
97
- );
98
-
99
- $reasons_list_items_html = '';
100
-
101
- foreach ($reasons as $reason) {
102
- $list_item_classes = 'duplicator-modal-reason'.(!empty($reason['input_type']) ? ' has-input' : '' );
103
-
104
- if (!empty($reason['internal_message'])) {
105
- $list_item_classes .= ' has-internal-message';
106
- $reason_internal_message = $reason['internal_message'];
107
- } else {
108
- $reason_internal_message = '';
109
- }
110
-
111
- $reasons_list_items_html .= '<li class="'.$list_item_classes.'" data-input-type="'.$reason['input_type'].'" data-input-placeholder="'.$reason['input_placeholder'].'">
112
- <label>
113
- <span>
114
- <input type="radio" name="selected-reason" value="'.$reason['id'].'"/>
115
- </span>
116
- <span>'.$reason['text'].'</span>
117
- </label>
118
- <div class="duplicator-modal-internal-message">'.$reason_internal_message.'</div>
119
- </li>';
120
- }
121
- ?>
122
- <script type="text/javascript">
123
- (function ($) {
124
- var modalHtml =
125
- '<div class="duplicator-modal duplicator-modal-deactivation-feedback">'
126
- + ' <div class="duplicator-modal-dialog">'
127
- + ' <div class="duplicator-modal-body">'
128
- + ' <h2><?php _e('Quick Feedback', 'duplicator'); ?></h2>'
129
- + ' <div class="duplicator-modal-panel active"><p><?php _e('If you have a moment, please let us know why you are deactivating', 'duplicator'); ?>:</p>'
130
- + '<ul>' + <?php echo DupLiteSnapJsonU::wp_json_encode($reasons_list_items_html); ?> + '</ul>'
131
- + ' </div>'
132
- + ' </div>'
133
- + ' <div class="duplicator-modal-footer">'
134
- + ' <div>'
135
- + ' <a href="#" class="button button-secondary duplicator-modal-button-close"><?php _e('Cancel', 'duplicator'); ?></a>'
136
- + ' <a href="#" class="button button-secondary duplicator-modal-button-skip"><?php _e('Skip & Deactivate', 'duplicator'); ?></a>'
137
- + ' <a href="#" class="button button-primary duplicator-modal-button-deactivate" disabled="disabled" ><?php _e('Send & Deactivate', 'duplicator'); ?></a>'
138
- + ' </div>'
139
- + ' <div class="clear"></div>'
140
- + ' <div><small class="duplicator-modal-resp-msg" ><i><?php _e('Your response is sent anonymously.','duplicator'); ?></i></small></div>'
141
- + ' </div>'
142
- + ' </div>'
143
- + '</div>',
144
- $modal = $(modalHtml),
145
- $deactivateLink = $('#the-list .active[data-plugin="<?php echo $basename; ?>"] .deactivate a'),
146
- selectedReasonID = false;
147
-
148
- /* WP added data-plugin attr after 4.5 version/ In prev version was id attr */
149
- if (0 == $deactivateLink.length)
150
- $deactivateLink = $('#the-list .active#<?php echo $plugin_id; ?> .deactivate a');
151
-
152
- $modal.appendTo($('body'));
153
-
154
- DuplicatorModalRegisterEventHandlers();
155
-
156
- function DuplicatorModalRegisterEventHandlers() {
157
- $deactivateLink.click(function (evt) {
158
- evt.preventDefault();
159
-
160
- /* Display the dialog box.*/
161
- DuplicatorModalReset();
162
- $modal.addClass('active');
163
- $('body').addClass('has-duplicator-modal');
164
- });
165
-
166
- $modal.on('input propertychange', '.duplicator-modal-reason-input input', function () {
167
- if (!DuplicatorModalIsReasonSelected('OTHER')) {
168
- return;
169
- }
170
-
171
- var reason = $(this).val().trim();
172
-
173
- /* If reason is not empty, remove the error-message class of the message container to change the message color back to default. */
174
- if (reason.length > 0) {
175
- $modal.find('.message').removeClass('error-message');
176
- DuplicatorModalEnableDeactivateButton();
177
- }
178
- });
179
-
180
- $modal.on('blur', '.duplicator-modal-reason-input input', function () {
181
- var $userReason = $(this);
182
-
183
- setTimeout(function () {
184
- if (!DuplicatorModalIsReasonSelected('OTHER')) {
185
- return;
186
- }
187
-
188
- /* If reason is empty, add the error-message class to the message container to change the message color to red. */
189
- if (0 === $userReason.val().trim().length) {
190
- $modal.find('.message').addClass('error-message');
191
- DuplicatorModalDisableDeactivateButton();
192
- }
193
- }, 150);
194
- });
195
-
196
- $modal.on('click', '.duplicator-modal-footer .button', function (evt) {
197
- evt.preventDefault();
198
-
199
- if ($(this).hasClass('disabled')) {
200
- return;
201
- }
202
-
203
- var _parent = $(this).parents('.duplicator-modal:first'),
204
- _this = $(this);
205
-
206
- if (_this.hasClass('allow-deactivate')) {
207
- var $radio = $modal.find('input[type="radio"]:checked');
208
-
209
- if (0 === $radio.length) {
210
- /* If no selected reason, just deactivate the plugin. */
211
- window.location.href = $deactivateLink.attr('href');
212
- return;
213
- }
214
-
215
- var $selected_reason = $radio.parents('li:first'),
216
- $input = $selected_reason.find('textarea, input[type="text"]'),
217
- userReason = (0 !== $input.length) ? $input.val().trim() : '';
218
-
219
- if (DuplicatorModalIsReasonSelected('OTHER') && '' === userReason) {
220
- return;
221
- }
222
-
223
- $.ajax({
224
- url: ajaxurl,
225
- method: 'POST',
226
- data: {
227
- 'action': 'duplicator_submit_uninstall_reason_action',
228
- 'plugin': '<?php echo $basename; ?>',
229
- 'reason_id': $radio.val(),
230
- 'reason_info': userReason,
231
- 'duplicator_ajax_nonce': '<?php echo wp_create_nonce('duplicator_ajax_nonce'); ?>'
232
- },
233
- beforeSend: function () {
234
- _parent.find('.duplicator-modal-footer .button').addClass('disabled');
235
- // _parent.find( '.duplicator-modal-footer .button-secondary' ).text( '<?php _e('Processing', 'duplicator'); ?>' + '...' );
236
- _parent.find('.duplicator-modal-footer .duplicator-modal-button-deactivate').text('<?php _e('Processing', 'duplicator'); ?>' + '...');
237
- },
238
- complete: function (message) {
239
- /* Do not show the dialog box, deactivate the plugin. */
240
- window.location.href = $deactivateLink.attr('href');
241
- }
242
- });
243
- } else if (_this.hasClass('duplicator-modal-button-deactivate')) {
244
- /* Change the Deactivate button's text and show the reasons panel. */
245
- _parent.find('.duplicator-modal-button-deactivate').addClass('allow-deactivate');
246
- DuplicatorModalShowPanel();
247
- } else if (_this.hasClass('duplicator-modal-button-skip')) {
248
- window.location.href = $deactivateLink.attr('href');
249
- return;
250
- }
251
- });
252
-
253
- $modal.on('click', 'input[type="radio"]', function () {
254
- var $selectedReasonOption = $(this);
255
-
256
- /* If the selection has not changed, do not proceed. */
257
- if (selectedReasonID === $selectedReasonOption.val())
258
- return;
259
-
260
- selectedReasonID = $selectedReasonOption.val();
261
-
262
- var _parent = $(this).parents('li:first');
263
-
264
- $modal.find('.duplicator-modal-reason-input').remove();
265
- $modal.find('.duplicator-modal-internal-message').hide();
266
- $modal.find('.duplicator-modal-button-deactivate').removeAttr( 'disabled' );
267
- //$modal.find('.duplicator-modal-button-skip').css('display', 'inline-block');
268
- $modal.find('.duplicator-modal-resp-msg').show();
269
-
270
- DuplicatorModalEnableDeactivateButton();
271
-
272
- if (_parent.hasClass('has-internal-message')) {
273
- _parent.find('.duplicator-modal-internal-message').show();
274
- }
275
-
276
- if (_parent.hasClass('has-input')) {
277
- var reasonInputHtml = '<div class="duplicator-modal-reason-input"><span class="message"></span>' + (('textfield' === _parent.data('input-type')) ? '<input type="text" />' : '<textarea rows="5" maxlength="200"></textarea>') + '</div>';
278
-
279
- _parent.append($(reasonInputHtml));
280
- _parent.find('input, textarea').attr('placeholder', _parent.data('input-placeholder')).focus();
281
-
282
- /*if (DuplicatorModalIsReasonSelected('OTHER')) {
283
- $modal.find('.message').text('<?php _e('Please tell us the reason so we can improve it.', 'duplicator'); ?>').show();
284
- DuplicatorModalDisableDeactivateButton();
285
- }*/
286
- }
287
- });
288
-
289
- /* If the user has clicked outside the window, cancel it. */
290
- $modal.on('click', function (evt) {
291
- var $target = $(evt.target);
292
-
293
- /* If the user has clicked anywhere in the modal dialog, just return. */
294
- if ($target.hasClass('duplicator-modal-body') || $target.hasClass('duplicator-modal-footer')) {
295
- return;
296
- }
297
-
298
- /* If the user has not clicked the close button and the clicked element is inside the modal dialog, just return. */
299
- if (!$target.hasClass('duplicator-modal-button-close') && ($target.parents('.duplicator-modal-body').length > 0 || $target.parents('.duplicator-modal-footer').length > 0)) {
300
- return;
301
- }
302
-
303
- /* Close the modal dialog */
304
- $modal.removeClass('active');
305
- $('body').removeClass('has-duplicator-modal');
306
-
307
- return false;
308
- });
309
- }
310
-
311
- function DuplicatorModalIsReasonSelected(reasonID) {
312
- /* Get the selected radio input element.*/
313
- return (reasonID == $modal.find('input[type="radio"]:checked').val());
314
- }
315
-
316
- function DuplicatorModalReset() {
317
- selectedReasonID = false;
318
-
319
- DuplicatorModalEnableDeactivateButton();
320
-
321
- /* Uncheck all radio buttons.*/
322
- $modal.find('input[type="radio"]').prop('checked', false);
323
-
324
- /* Remove all input fields ( textfield, textarea ).*/
325
- $modal.find('.duplicator-modal-reason-input').remove();
326
-
327
- $modal.find('.message').hide();
328
- var $deactivateButton = $modal.find('.duplicator-modal-button-deactivate');
329
- $deactivateButton.addClass('allow-deactivate');
330
- DuplicatorModalShowPanel();
331
- }
332
-
333
- function DuplicatorModalEnableDeactivateButton() {
334
- $modal.find('.duplicator-modal-button-deactivate').removeClass('disabled');
335
- }
336
-
337
- function DuplicatorModalDisableDeactivateButton() {
338
- $modal.find('.duplicator-modal-button-deactivate').addClass('disabled');
339
- }
340
-
341
- function DuplicatorModalShowPanel() {
342
- $modal.find('.duplicator-modal-panel').addClass('active');
343
- /* Update the deactivate button's text */
344
- //$modal.find('.duplicator-modal-button-deactivate').text('<?php _e('Skip & Deactivate', 'duplicator'); ?>');
345
- //$modal.find('.duplicator-modal-button-skip, .duplicator-modal-resp-msg').css('display', 'none');
346
- }
347
- })(jQuery);
348
- </script>
349
- <?php
350
- }
351
- }
352
-
353
- /**
354
- * Called after the user has submitted his reason for deactivating the plugin.
355
- *
356
- */
357
- if (!function_exists('duplicator_submit_uninstall_reason_action')) {
358
-
359
- function duplicator_submit_uninstall_reason_action()
360
- {
361
- DUP_Handler::init_error_handler();
362
-
363
- $isValid = true;
364
- $inputData = filter_input_array(INPUT_POST, array(
365
- 'reason_id' => array(
366
- 'filter' => FILTER_UNSAFE_RAW,
367
- 'flags' => FILTER_REQUIRE_SCALAR,
368
- 'options' => array(
369
- 'default' => false
370
- )
371
- ),
372
- 'plugin' => array(
373
- 'filter' => FILTER_UNSAFE_RAW,
374
- 'flags' => FILTER_REQUIRE_SCALAR,
375
- 'options' => array(
376
- 'default' => false
377
- )
378
- ),
379
- 'reason_info' => array(
380
- 'filter' => FILTER_UNSAFE_RAW,
381
- 'flags' => FILTER_REQUIRE_SCALAR,
382
- 'options' => array(
383
- 'default' => ''
384
- )
385
- )
386
- ));
387
-
388
- $reason_id = $inputData['reason_id'];
389
- $basename = $inputData['plugin'];
390
-
391
- if (!$reason_id || !$basename) {
392
- $isValid = false;
393
- }
394
-
395
- try {
396
- if (!wp_verify_nonce($_POST['duplicator_ajax_nonce'], 'duplicator_ajax_nonce')) {
397
- throw new Exception(__('Security issue', 'duplicator'));
398
- }
399
-
400
- DUP_Util::hasCapability('export', DUP_Util::SECURE_ISSUE_THROW);
401
-
402
- if (!$isValid) {
403
- throw new Exception(__('Invalid Request.', 'duplicator'));
404
- }
405
-
406
- $reason_info = isset($_REQUEST['reason_info']) ? stripcslashes(esc_html($_REQUEST['reason_info'])) : '';
407
- if (!empty($reason_info)) {
408
- $reason_info = substr($reason_info, 0, 255);
409
- }
410
-
411
- $options = array(
412
- 'product' => $basename,
413
- 'reason_id' => $reason_id,
414
- 'reason_info' => $reason_info,
415
- );
416
-
417
- /* send data */
418
- $raw_response = wp_remote_post('https://snapcreekanalytics.com/wp-content/plugins/duplicator-statistics-plugin/deactivation-feedback/',
419
- array(
420
- 'method' => 'POST',
421
- 'body' => $options,
422
- 'timeout' => 15,
423
- // 'sslverify' => FALSE
424
- ));
425
-
426
- if (!is_wp_error($raw_response) && 200 == wp_remote_retrieve_response_code($raw_response)) {
427
- echo 'done';
428
- } else {
429
- $error_msg = $raw_response->get_error_code().': '.$raw_response->get_error_message();
430
- error_log($error_msg);
431
- throw new Exception($error_msg);
432
- }
433
- } catch (Exception $ex) {
434
- echo $ex->getMessage();
435
- }
436
- exit;
437
- }
438
- }
439
-
440
- add_action('wp_ajax_duplicator_submit_uninstall_reason_action', 'duplicator_submit_uninstall_reason_action');
1
+ <?php
2
+
3
+ /**
4
+ * Standard: PSR-2
5
+ * @link http://www.php-fig.org/psr/psr-2 Full Documentation
6
+ *
7
+ */
8
+
9
+ use Duplicator\Libs\Snap\SnapJson;
10
+
11
+ defined('ABSPATH') || defined('DUPXABSPATH') || exit;
12
+ function duplicator_deactivation_enqueue_scripts($hook)
13
+ {
14
+ if ('plugins.php' == $hook && !defined('DOING_AJAX')) {
15
+ wp_enqueue_style('duplicator-deactivation-modal', DUPLICATOR_PLUGIN_URL . 'assets/css/modal.css', array(), '1.0.0');
16
+ }
17
+ }
18
+ add_action('admin_enqueue_scripts', 'duplicator_deactivation_enqueue_scripts');
19
+ if (!function_exists('duplicator_plugins_admin_footer')) {
20
+ function duplicator_plugins_admin_footer()
21
+ {
22
+ global $hook_suffix;
23
+ if ('plugins.php' == $hook_suffix && !defined('DOING_AJAX')) {
24
+ duplicator_add_deactivation_feedback_dialog_box();
25
+ }
26
+ }
27
+
28
+ }
29
+ add_action('admin_footer', 'duplicator_plugins_admin_footer');
30
+ /**
31
+ * Displays a confirmation and feedback dialog box when the user clicks on the "Deactivate" link on the plugins
32
+ * page.
33
+ *
34
+ * @since 2.1.3
35
+ */
36
+ if (!function_exists('duplicator_add_deactivation_feedback_dialog_box')) {
37
+ function duplicator_add_deactivation_feedback_dialog_box()
38
+ {
39
+ $basename = 'duplicator/duplicator.php';
40
+ /*
41
+ $slug = dirname( $basename );
42
+ $plugin_id = sanitize_title( $plugin_data['Name'] );
43
+ */
44
+ $slug = 'duplicator';
45
+ $plugin_id = 'duplicator';
46
+ $contact_support_template = __('Need help? We are ready to answer your questions.', 'duplicator') .
47
+ ' <a href="https://snapcreek.com/ticket/" target="_blank">' . __('Contact Support', 'duplicator') . '</a>';
48
+ $reasons = array(
49
+ array(
50
+ 'id' => 'NOT_WORKING',
51
+ 'text' => __("It's not working on my server.", 'duplicator'),
52
+ 'input_type' => 'textarea',
53
+ 'input_placeholder' => __("Kindly share what didn't work so we can fix it in future updates...", 'duplicator'),
54
+ 'internal_message' => $contact_support_template
55
+ ),
56
+ array(
57
+ 'id' => 'CONFUSING_TO_UNDERSTAND',
58
+ 'text' => __("It's too confusing to understand.", 'duplicator'),
59
+ 'input_type' => 'textarea',
60
+ 'input_placeholder' => __('Please tell us what is not clear so that we can improve it.', 'duplicator'),
61
+ 'internal_message' => $contact_support_template
62
+ ),
63
+ array(
64
+ 'id' => 'FOUND_A_DIFFERENT_PLUGIN',
65
+ 'text' => __('I found a different plugin that I like better.', 'duplicator'),
66
+ 'input_type' => 'textfield',
67
+ 'input_placeholder' => __("What's the plugin name?", 'duplicator')
68
+ ),
69
+ array(
70
+ 'id' => 'NOT_DO_WHAT_I_NEED',
71
+ 'text' => __("It does not do what I need.", 'duplicator'),
72
+ 'input_type' => 'textarea',
73
+ 'input_placeholder' => __('What does it need to do?', 'duplicator')
74
+ ),
75
+ array(
76
+ 'id' => 'TEMPORARY_DEACTIVATION',
77
+ 'text' => __("It's a temporary deactivation, I use the plugin all the time.", 'duplicator'),
78
+ 'input_type' => '',
79
+ 'input_placeholder' => ''
80
+ ),
81
+ array(
82
+ 'id' => 'SWITCHING_PRO_VERSION',
83
+ 'text' => sprintf(__("I'm switching over to the %s", 'duplicator'), '<a href="https://snapcreek.com/duplicator/" target="_blank">' . __('Pro version', 'duplicator') . '</a>'),
84
+ 'input_type' => '',
85
+ 'input_placeholder' => ''
86
+ ),
87
+ /*
88
+ array(
89
+ 'id' => 'OTHER',
90
+ 'text' => __('Other', 'duplicator'),
91
+ 'input_type' => 'textarea',
92
+ 'input_placeholder' => __('Please tell us the reason so we can improve it.', 'duplicator')
93
+ )
94
+ */
95
+ );
96
+ $reasons_list_items_html = '';
97
+ foreach ($reasons as $reason) {
98
+ $list_item_classes = 'duplicator-modal-reason' . (!empty($reason['input_type']) ? ' has-input' : '' );
99
+ if (!empty($reason['internal_message'])) {
100
+ $list_item_classes .= ' has-internal-message';
101
+ $reason_internal_message = $reason['internal_message'];
102
+ } else {
103
+ $reason_internal_message = '';
104
+ }
105
+
106
+ $reasons_list_items_html .= '<li class="' . $list_item_classes . '" data-input-type="' . $reason['input_type'] . '" data-input-placeholder="' . $reason['input_placeholder'] . '">
107
+ <label>
108
+ <span>
109
+ <input type="radio" name="selected-reason" value="' . $reason['id'] . '"/>
110
+ </span>
111
+ <span>' . $reason['text'] . '</span>
112
+ </label>
113
+ <div class="duplicator-modal-internal-message">' . $reason_internal_message . '</div>
114
+ </li>';
115
+ }
116
+ ?>
117
+ <script type="text/javascript">
118
+ (function ($) {
119
+ var modalHtml =
120
+ '<div class="duplicator-modal duplicator-modal-deactivation-feedback">'
121
+ + ' <div class="duplicator-modal-dialog">'
122
+ + ' <div class="duplicator-modal-body">'
123
+ + ' <h2><?php _e('Quick Feedback', 'duplicator'); ?></h2>'
124
+ + ' <div class="duplicator-modal-panel active"><p><?php _e('If you have a moment, please let us know why you are deactivating', 'duplicator'); ?>:</p>'
125
+ + '<ul>' + <?php echo SnapJson::jsonEncode($reasons_list_items_html); ?> + '</ul>'
126
+ + ' </div>'
127
+ + ' </div>'
128
+ + ' <div class="duplicator-modal-footer">'
129
+ + ' <div>'
130
+ + ' <a href="#" class="button button-secondary duplicator-modal-button-close"><?php _e('Cancel', 'duplicator'); ?></a>'
131
+ + ' <a href="#" class="button button-secondary duplicator-modal-button-skip"><?php _e('Skip & Deactivate', 'duplicator'); ?></a>'
132
+ + ' <a href="#" class="button button-primary duplicator-modal-button-deactivate" disabled="disabled" ><?php _e('Send & Deactivate', 'duplicator'); ?></a>'
133
+ + ' </div>'
134
+ + ' <div class="clear"></div>'
135
+ + ' <div><small class="duplicator-modal-resp-msg" ><i><?php _e('Your response is sent anonymously.', 'duplicator'); ?></i></small></div>'
136
+ + ' </div>'
137
+ + ' </div>'
138
+ + '</div>',
139
+ $modal = $(modalHtml),
140
+ $deactivateLink = $('#the-list .active[data-plugin="<?php echo $basename; ?>"] .deactivate a'),
141
+ selectedReasonID = false;
142
+
143
+ /* WP added data-plugin attr after 4.5 version/ In prev version was id attr */
144
+ if (0 == $deactivateLink.length)
145
+ $deactivateLink = $('#the-list .active#<?php echo $plugin_id; ?> .deactivate a');
146
+
147
+ $modal.appendTo($('body'));
148
+
149
+ DuplicatorModalRegisterEventHandlers();
150
+
151
+ function DuplicatorModalRegisterEventHandlers() {
152
+ $deactivateLink.click(function (evt) {
153
+ evt.preventDefault();
154
+
155
+ /* Display the dialog box.*/
156
+ DuplicatorModalReset();
157
+ $modal.addClass('active');
158
+ $('body').addClass('has-duplicator-modal');
159
+ });
160
+
161
+ $modal.on('input propertychange', '.duplicator-modal-reason-input input', function () {
162
+ if (!DuplicatorModalIsReasonSelected('OTHER')) {
163
+ return;
164
+ }
165
+
166
+ var reason = $(this).val().trim();
167
+
168
+ /* If reason is not empty, remove the error-message class of the message container to change the message color back to default. */
169
+ if (reason.length > 0) {
170
+ $modal.find('.message').removeClass('error-message');
171
+ DuplicatorModalEnableDeactivateButton();
172
+ }
173
+ });
174
+
175
+ $modal.on('blur', '.duplicator-modal-reason-input input', function () {
176
+ var $userReason = $(this);
177
+
178
+ setTimeout(function () {
179
+ if (!DuplicatorModalIsReasonSelected('OTHER')) {
180
+ return;
181
+ }
182
+
183
+ /* If reason is empty, add the error-message class to the message container to change the message color to red. */
184
+ if (0 === $userReason.val().trim().length) {
185
+ $modal.find('.message').addClass('error-message');
186
+ DuplicatorModalDisableDeactivateButton();
187
+ }
188
+ }, 150);
189
+ });
190
+
191
+ $modal.on('click', '.duplicator-modal-footer .button', function (evt) {
192
+ evt.preventDefault();
193
+
194
+ if ($(this).hasClass('disabled')) {
195
+ return;
196
+ }
197
+
198
+ var _parent = $(this).parents('.duplicator-modal:first'),
199
+ _this = $(this);
200
+
201
+ if (_this.hasClass('allow-deactivate')) {
202
+ var $radio = $modal.find('input[type="radio"]:checked');
203
+
204
+ if (0 === $radio.length) {
205
+ /* If no selected reason, just deactivate the plugin. */
206
+ window.location.href = $deactivateLink.attr('href');
207
+ return;
208
+ }
209
+
210
+ var $selected_reason = $radio.parents('li:first'),
211
+ $input = $selected_reason.find('textarea, input[type="text"]'),
212
+ userReason = (0 !== $input.length) ? $input.val().trim() : '';
213
+
214
+ if (DuplicatorModalIsReasonSelected('OTHER') && '' === userReason) {
215
+ return;
216
+ }
217
+
218
+ $.ajax({
219
+ url: ajaxurl,
220
+ method: 'POST',
221
+ data: {
222
+ 'action': 'duplicator_submit_uninstall_reason_action',
223
+ 'plugin': '<?php echo $basename; ?>',
224
+ 'reason_id': $radio.val(),
225
+ 'reason_info': userReason,
226
+ 'duplicator_ajax_nonce': '<?php echo wp_create_nonce('duplicator_ajax_nonce'); ?>'
227
+ },
228
+ beforeSend: function () {
229
+ _parent.find('.duplicator-modal-footer .button').addClass('disabled');
230
+ // _parent.find( '.duplicator-modal-footer .button-secondary' ).text( '<?php _e('Processing', 'duplicator'); ?>' + '...' );
231
+ _parent.find('.duplicator-modal-footer .duplicator-modal-button-deactivate').text('<?php _e('Processing', 'duplicator'); ?>' + '...');
232
+ },
233
+ complete: function (message) {
234
+ /* Do not show the dialog box, deactivate the plugin. */
235
+ window.location.href = $deactivateLink.attr('href');
236
+ }
237
+ });
238
+ } else if (_this.hasClass('duplicator-modal-button-deactivate')) {
239
+ /* Change the Deactivate button's text and show the reasons panel. */
240
+ _parent.find('.duplicator-modal-button-deactivate').addClass('allow-deactivate');
241
+ DuplicatorModalShowPanel();
242
+ } else if (_this.hasClass('duplicator-modal-button-skip')) {
243
+ window.location.href = $deactivateLink.attr('href');
244
+ return;
245
+ }
246
+ });
247
+
248
+ $modal.on('click', 'input[type="radio"]', function () {
249
+ var $selectedReasonOption = $(this);
250
+
251
+ /* If the selection has not changed, do not proceed. */
252
+ if (selectedReasonID === $selectedReasonOption.val())
253
+ return;
254
+
255
+ selectedReasonID = $selectedReasonOption.val();
256
+
257
+ var _parent = $(this).parents('li:first');
258
+
259
+ $modal.find('.duplicator-modal-reason-input').remove();
260
+ $modal.find('.duplicator-modal-internal-message').hide();
261
+ $modal.find('.duplicator-modal-button-deactivate').removeAttr( 'disabled' );
262
+ //$modal.find('.duplicator-modal-button-skip').css('display', 'inline-block');
263
+ $modal.find('.duplicator-modal-resp-msg').show();
264
+
265
+ DuplicatorModalEnableDeactivateButton();
266
+
267
+ if (_parent.hasClass('has-internal-message')) {
268
+ _parent.find('.duplicator-modal-internal-message').show();
269
+ }
270
+
271
+ if (_parent.hasClass('has-input')) {
272
+ var reasonInputHtml = '<div class="duplicator-modal-reason-input"><span class="message"></span>' +
273
+ (('textfield' === _parent.data('input-type')) ? '<input type="text" />' : '<textarea rows="5" maxlength="200"></textarea>') + '</div>';
274
+
275
+ _parent.append($(reasonInputHtml));
276
+ _parent.find('input, textarea').attr('placeholder', _parent.data('input-placeholder')).focus();
277
+
278
+ /*if (DuplicatorModalIsReasonSelected('OTHER')) {
279
+ $modal.find('.message').text('<?php _e('Please tell us the reason so we can improve it.', 'duplicator'); ?>').show();
280
+ DuplicatorModalDisableDeactivateButton();
281
+ }*/
282
+ }
283
+ });
284
+
285
+ /* If the user has clicked outside the window, cancel it. */
286
+ $modal.on('click', function (evt) {
287
+ var $target = $(evt.target);
288
+
289
+ /* If the user has clicked anywhere in the modal dialog, just return. */
290
+ if ($target.hasClass('duplicator-modal-body') || $target.hasClass('duplicator-modal-footer')) {
291
+ return;
292
+ }
293
+
294
+ /* If the user has not clicked the close button and the clicked element is inside the modal dialog, just return. */
295
+ if (!$target.hasClass('duplicator-modal-button-close') && ($target.parents('.duplicator-modal-body').length > 0 || $target.parents('.duplicator-modal-footer').length > 0)) {
296
+ return;
297
+ }
298
+
299
+ /* Close the modal dialog */
300
+ $modal.removeClass('active');
301
+ $('body').removeClass('has-duplicator-modal');
302
+
303
+ return false;
304
+ });
305
+ }
306
+
307
+ function DuplicatorModalIsReasonSelected(reasonID) {
308
+ /* Get the selected radio input element.*/
309
+ return (reasonID == $modal.find('input[type="radio"]:checked').val());
310
+ }
311
+
312
+ function DuplicatorModalReset() {
313
+ selectedReasonID = false;
314
+
315
+ DuplicatorModalEnableDeactivateButton();
316
+
317
+ /* Uncheck all radio buttons.*/
318
+ $modal.find('input[type="radio"]').prop('checked', false);
319
+
320
+ /* Remove all input fields ( textfield, textarea ).*/
321
+ $modal.find('.duplicator-modal-reason-input').remove();
322
+
323
+ $modal.find('.message').hide();
324
+ var $deactivateButton = $modal.find('.duplicator-modal-button-deactivate');
325
+ $deactivateButton.addClass('allow-deactivate');
326
+ DuplicatorModalShowPanel();
327
+ }
328
+
329
+ function DuplicatorModalEnableDeactivateButton() {
330
+ $modal.find('.duplicator-modal-button-deactivate').removeClass('disabled');
331
+ }
332
+
333
+ function DuplicatorModalDisableDeactivateButton() {
334
+ $modal.find('.duplicator-modal-button-deactivate').addClass('disabled');
335
+ }
336
+
337
+ function DuplicatorModalShowPanel() {
338
+ $modal.find('.duplicator-modal-panel').addClass('active');
339
+ /* Update the deactivate button's text */
340
+ //$modal.find('.duplicator-modal-button-deactivate').text('<?php _e('Skip & Deactivate', 'duplicator'); ?>');
341
+ //$modal.find('.duplicator-modal-button-skip, .duplicator-modal-resp-msg').css('display', 'none');
342
+ }
343
+ })(jQuery);
344
+ </script>
345
+ <?php
346
+ }
347
+
348
+ }
349
+
350
+ /**
351
+ * Called after the user has submitted his reason for deactivating the plugin.
352
+ *
353
+ */
354
+ if (!function_exists('duplicator_submit_uninstall_reason_action')) {
355
+ function duplicator_submit_uninstall_reason_action()
356
+ {
357
+ DUP_Handler::init_error_handler();
358
+ $isValid = true;
359
+ $inputData = filter_input_array(INPUT_POST, array(
360
+ 'reason_id' => array(
361
+ 'filter' => FILTER_UNSAFE_RAW,
362
+ 'flags' => FILTER_REQUIRE_SCALAR,
363
+ 'options' => array(
364
+ 'default' => false
365
+ )
366
+ ),
367
+ 'plugin' => array(
368
+ 'filter' => FILTER_UNSAFE_RAW,
369
+ 'flags' => FILTER_REQUIRE_SCALAR,
370
+ 'options' => array(
371
+ 'default' => false
372
+ )
373
+ ),
374
+ 'reason_info' => array(
375
+ 'filter' => FILTER_UNSAFE_RAW,
376
+ 'flags' => FILTER_REQUIRE_SCALAR,
377
+ 'options' => array(
378
+ 'default' => ''
379
+ )
380
+ )
381
+ ));
382
+ $reason_id = $inputData['reason_id'];
383
+ $basename = $inputData['plugin'];
384
+ if (!$reason_id || !$basename) {
385
+ $isValid = false;
386
+ }
387
+
388
+ try {
389
+ if (!wp_verify_nonce($_POST['duplicator_ajax_nonce'], 'duplicator_ajax_nonce')) {
390
+ throw new Exception(__('Security issue', 'duplicator'));
391
+ }
392
+
393
+ DUP_Util::hasCapability('export', DUP_Util::SECURE_ISSUE_THROW);
394
+ if (!$isValid) {
395
+ throw new Exception(__('Invalid Request.', 'duplicator'));
396
+ }
397
+
398
+ $reason_info = isset($_REQUEST['reason_info']) ? stripcslashes(esc_html($_REQUEST['reason_info'])) : '';
399
+ if (!empty($reason_info)) {
400
+ $reason_info = substr($reason_info, 0, 255);
401
+ }
402
+
403
+ $options = array(
404
+ 'product' => $basename,
405
+ 'reason_id' => $reason_id,
406
+ 'reason_info' => $reason_info,
407
+ );
408
+
409
+ /* send data */
410
+ $raw_response = wp_remote_post(
411
+ 'https://snapcreekanalytics.com/wp-content/plugins/duplicator-statistics-plugin/deactivation-feedback/',
412
+ array(
413
+ 'method' => 'POST',
414
+ 'body' => $options,
415
+ 'timeout' => 15,
416
+ // 'sslverify' => FALSE
417
+ )
418
+ );
419
+ if (!is_wp_error($raw_response) && 200 == wp_remote_retrieve_response_code($raw_response)) {
420
+ echo 'done';
421
+ } else {
422
+ $error_msg = $raw_response->get_error_code() . ': ' . $raw_response->get_error_message();
423
+ error_log($error_msg);
424
+ throw new Exception($error_msg);
425
+ }
426
+ } catch (Exception $ex) {
427
+ echo $ex->getMessage();
428
+ }
429
+ exit;
430
+ }
431
+
432
+ }
433
+
434
+ add_action('wp_ajax_duplicator_submit_uninstall_reason_action', 'duplicator_submit_uninstall_reason_action');
 
 
 
 
 
 
define.php CHANGED
@@ -1,109 +1,131 @@
1
  <?php
 
2
  //Prevent directly browsing to the file
3
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
4
 
5
 
6
- if (function_exists('plugin_dir_url'))
7
- {
8
- define('DUPLICATOR_VERSION', '1.4.7.2');
9
- define('DUPLICATOR_VERSION_BUILD', '2022-08-15_11:00');
10
- define('DUPLICATOR_PLUGIN_URL', plugin_dir_url(__FILE__));
11
- define('DUPLICATOR_SITE_URL', get_site_url());
12
-
13
  /* Paths should ALWAYS read "/"
14
  uni: /home/path/file.txt
15
  win: D:/home/path/file.txt
16
  SSDIR = SnapShot Directory */
17
- if (!defined('ABSPATH')) {
18
- define('ABSPATH', dirname(__FILE__));
19
- }
20
-
21
- //PATH CONSTANTS
22
- if (! defined('DUPLICATOR_WPROOTPATH')) {
23
- define('DUPLICATOR_WPROOTPATH', str_replace('\\', '/', ABSPATH));
24
- }
25
-
26
- define('DUPLICATOR_PLUGIN_PATH', str_replace("\\", "/", plugin_dir_path(__FILE__)));
27
- define('DUPLICATOR_ZIPPED_LOG_FILENAME', 'duplicator_lite_log.zip');
28
- define('DUPLICATOR_INSTALL_PHP', 'installer.php');
29
- define('DUPLICATOR_INSTALL_BAK', 'installer-backup.php');
30
- define('DUPLICATOR_INSTALLER_HASH_PATTERN', '[a-z0-9][a-z0-9][a-z0-9][a-z0-9][a-z0-9][a-z0-9][a-z0-9]-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]');
31
- define('DUPLICATOR_INSTALL_SITE_OVERWRITE_ON', true);
32
-
33
- //GENERAL CONSTRAINTS
34
- define('DUPLICATOR_PHP_MAX_MEMORY', 4294967296); // 4096MB
35
- define('DUPLICATOR_DB_MAX_TIME', 5000);
36
- define('DUPLICATOR_DB_EOF_MARKER', 'DUPLICATOR_MYSQLDUMP_EOF');
37
- define("DUPLICATOR_DB_MYSQLDUMP_ERROR_CONTAINING_LINE_COUNT", 10);
38
- define("DUPLICATOR_DB_MYSQLDUMP_ERROR_CHARS_IN_LINE_COUNT", 1000);
39
- //SCANNER CONSTRAINTS
40
- define('DUPLICATOR_SCAN_SIZE_DEFAULT', 157286400); //150MB
41
- define('DUPLICATOR_SCAN_WARNFILESIZE', 3145728); //3MB
42
- define('DUPLICATOR_SCAN_CACHESIZE', 1048576); //1MB
43
- define('DUPLICATOR_SCAN_DB_ALL_ROWS', 500000); //500k per DB
44
- define('DUPLICATOR_SCAN_DB_ALL_SIZE', 52428800); //50MB DB
45
- define('DUPLICATOR_SCAN_DB_TBL_ROWS', 100000); //100K rows per table
46
- define('DUPLICATOR_SCAN_DB_TBL_SIZE', 10485760); //10MB Table
47
- define('DUPLICATOR_SCAN_TIMEOUT', 150); //Seconds
48
- define('DUPLICATOR_SCAN_MIN_WP', '4.7.0');
49
- define('DUPLICATOR_MAX_DUPARCHIVE_SIZE', 524288000); // 500 GB
50
-
51
- define('DUPLICATOR_TEMP_CLEANUP_SECONDS', 900); // 15 min = How many seconds to keep temp files around when delete is requested
52
- define('DUPLICATOR_MAX_BUILD_RETRIES', 10); // Max times to try a part of progressive build work
53
- define('DUPLICATOR_WEBCONFIG_ORIG_FILENAME', 'web.config.orig');
54
- define("DUPLICATOR_INSTALLER_DIRECTORY", duplicator_get_abs_path() . '/dup-installer');
55
- define('DUPLICATOR_MAX_LOG_SIZE', 400000); // The higher this is the more overhead
56
- define('DUPLICATOR_ZIP_ARCHIVE_ADD_FROM_STR', false);
57
- define('DUPLICATOR_DEACTIVATION_FEEDBACK', false);
58
- define("DUPLICATOR_BUFFER_READ_WRITE_SIZE", 4377);
59
- define("DUPLICATOR_ADMIN_NOTICES_USER_META_KEY", 'duplicator_admin_notices');
60
- define("DUPLICATOR_FEEDBACK_NOTICE_SHOW_AFTER_NO_PACKAGE", 5);
61
-
62
- $GLOBALS['DUPLICATOR_SERVER_LIST'] = array('Apache','LiteSpeed', 'Nginx', 'Lighttpd', 'IIS', 'WebServerX', 'uWSGI');
63
- $GLOBALS['DUPLICATOR_OPTS_DELETE'] = array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  'duplicator_ui_view_state',
65
  'duplicator_package_active',
66
  'duplicator_settings',
67
  'duplicator_is_pro_enable_notice_dismissed'
68
  );
69
- $GLOBALS['DUPLICATOR_GLOBAL_FILE_FILTERS_ON'] = true;
70
- $GLOBALS['DUPLICATOR_GLOBAL_FILE_FILTERS'] = array(
71
- 'error_log',
72
- 'error.log',
73
- 'debug_log',
74
- 'ws_ftp.log',
75
- 'dbcache',
76
- 'pgcache',
77
- 'objectcache',
78
- '.DS_Store'
79
- );
80
-
81
-
82
- /* Used to flush a response every N items.
83
- * Note: This value will cause the Zip file to double in size durning the creation process only*/
84
- define("DUPLICATOR_ZIP_FLUSH_TRIGGER", 1000);
85
-
86
- /* Let's setup few things to cover all PHP versions */
87
- if(!defined('PHP_VERSION'))
88
- {
89
- define('PHP_VERSION', phpversion());
90
- }
91
- if (!defined('PHP_VERSION_ID')) {
92
- $version = expl
1
  <?php
2
+
3
  //Prevent directly browsing to the file
4
  defined('ABSPATH') || defined('DUPXABSPATH') || exit;
5
 
6
 
7
+ if (function_exists('plugin_dir_url')) {
8
+ define('DUPLICATOR_VERSION', '1.5.0');
9
+ define('DUPLICATOR_VERSION_BUILD', '2022-08-28_19:00');
10
+ define('DUPLICATOR_PLUGIN_URL', plugin_dir_url(__FILE__));
11
+ define('DUPLICATOR_SITE_URL', get_site_url());
12
+
 
13
  /* Paths should ALWAYS read "/"
14
  uni: /home/path/file.txt
15
  win: D:/home/path/file.txt
16
  SSDIR = SnapShot Directory */
17
+ if (!defined('ABSPATH')) {
18
+ define('ABSPATH', dirname(__FILE__));
19
+ }
20
+
21
+ //PATH CONSTANTS
22
+ if (! defined('DUPLICATOR_WPROOTPATH')) {
23
+ define('DUPLICATOR_WPROOTPATH', str_replace('\\', '/', ABSPATH));
24
+ }
25
+
26
+ define('DUPLICATOR_PLUGIN_PATH', str_replace("\\", "/", plugin_dir_path(__FILE__)));
27
+ define('DUPLICATOR_ZIPPED_LOG_FILENAME', 'duplicator_lite_log.zip');
28
+ define('DUPLICATOR_INSTALL_PHP', 'installer.php');
29
+ define('DUPLICATOR_INSTALL_BAK', 'installer-backup.php');
30
+ define('DUPLICATOR_INSTALLER_HASH_PATTERN', '[a-z0-9][a-z0-9][a-z0-9][a-z0-9][a-z0-9][a-z0-9][a-z0-9]-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]');
31
+ define('DUPLICATOR_INSTALL_SITE_OVERWRITE_ON', true);
32
+
33
+ //GENERAL CONSTRAINTS
34
+ define('DUPLICATOR_PHP_MAX_MEMORY', 4294967296); // 4096MB
35
+ define('DUPLICATOR_DB_MAX_TIME', 5000);
36
+ define('DUPLICATOR_DB_EOF_MARKER', 'DUPLICATOR_MYSQLDUMP_EOF');
37
+ define("DUPLICATOR_DB_MYSQLDUMP_ERROR_CONTAINING_LINE_COUNT", 10);
38
+ define("DUPLICATOR_DB_MYSQLDUMP_ERROR_CHARS_IN_LINE_COUNT", 1000);
39
+ //SCANNER CONSTRAINTS
40
+ define('DUPLICATOR_SCAN_SIZE_DEFAULT', 157286400); //150MB
41
+ define('DUPLICATOR_SCAN_WARNFILESIZE', 3145728); //3MB
42
+ define('DUPLICATOR_SCAN_WARN_DIR_SIZE', 104857600);//100MB
43
+
44
+ define('DUPLICATOR_SCAN_CACHESIZE', 1048576); //1MB
45
+ define('DUPLICATOR_SCAN_DB_ALL_ROWS', 500000); //500k per DB
46
+ define('DUPLICATOR_SCAN_DB_ALL_SIZE', 52428800); //50MB DB
47
+ define('DUPLICATOR_SCAN_DB_TBL_ROWS', 100000); //100K rows per table
48
+ define('DUPLICATOR_SCAN_DB_TBL_SIZE', 10485760); //10MB Table
49
+ define('DUPLICATOR_SCAN_TIMEOUT', 150); //Seconds
50
+ define('DUPLICATOR_SCAN_MIN_WP', '4.7.0');
51
+ define('DUPLICATOR_MAX_DUPARCHIVE_SIZE', 524288000); // 500 GB
52
+
53
+ define('DUPLICATOR_TEMP_CLEANUP_SECONDS', 900); // 15 min = How many seconds to keep temp files around when delete is requested
54
+ define('DUPLICATOR_MAX_BUILD_RETRIES', 10); // Max times to try a part of progressive build work
55
+ define('DUPLICATOR_WEBCONFIG_ORIG_FILENAME', 'web.config.orig');
56
+ define("DUPLICATOR_INSTALLER_DIRECTORY", duplicator_get_abs_path() . '/dup-installer');
57
+ define('DUPLICATOR_MAX_LOG_SIZE', 400000); // The higher this is the more overhead
58
+ define('DUPLICATOR_ZIP_ARCHIVE_ADD_FROM_STR', false);
59
+ define('DUPLICATOR_DEACTIVATION_FEEDBACK', false);
60
+ define("DUPLICATOR_BUFFER_READ_WRITE_SIZE", 4377);
61
+ define("DUPLICATOR_ADMIN_NOTICES_USER_META_KEY", 'duplicator_admin_notices');
62
+ define("DUPLICATOR_FEEDBACK_NOTICE_SHOW_AFTER_NO_PACKAGE", 5);
63
+
64
+ define('DUPLICATOR_LOCAL_OVERWRITE_PARAMS', 'duplicator_pro_params_overwrite');
65
+
66
+ // MATCH archive pattern, matches[1] is archive name and hash
67
+ define('DUPLICATOR_ARCHIVE_REGEX_PATTERN', '/^(.+_[a-z0-9]{7,}_[0-9]{14})_archive\.(?:zip|daf)$/');
68
+ // MATCH installer.php installer-backup.php and full installer with hash
69
+ define('DUPLICATOR_INSTALLER_REGEX_PATTERN', '/^(?:.+_[a-z0-9]{7,}_[0-9]{14}_)?installer(?:-backup)?\.php$/');
70
+ // MATCH dup-installer and dup-installer-[HASH]
71
+ define('DUPLICATOR_DUP_INSTALLER_FOLDER_REGEX_PATTERN', '/^dup-installer(?:-[a-z0-9]{7,}-[0-9]{8})?$/');
72
+ define('DUPLICATOR_DUP_INSTALLER_BOOTLOG_REGEX_PATTERN', '/^dup-installer-bootlog__[a-z0-9]{7,}-[0-9]{8}.txt$/');
73
+ define('DUPLICATOR_DUP_INSTALLER_OWRPARAM_REGEX_PATTERN', '/^' . DUPLICATOR_LOCAL_OVERWRITE_PARAMS . '_[a-z0-9]{7,}-[0-9]{8}.json$/');
74
+ define("DUPLICATOR_ORIG_FOLDER_PREFIX", 'original_files_');
75
+ define('DUPLICATOR_CERT_PATH', apply_filters('duplicator_pro_certificate_path', DUPLICATOR_LITE_PATH . '/src/Libs/Certificates/cacert.pem'));
76
+
77
+
78
+ $GLOBALS['DUPLICATOR_SERVER_LIST'] = array('Apache','LiteSpeed', 'Nginx', 'Lighttpd', 'IIS', 'WebServerX', 'uWSGI');
79
+ $GLOBALS['DUPLICATOR_OPTS_DELETE'] = array(
80
  'duplicator_ui_view_state',
81
  'duplicator_package_active',
82
  'duplicator_settings',
83
  'duplicator_is_pro_enable_notice_dismissed'
84
  );