PluginProbe ʕ •ᴥ•ʔ
Backup Migration / 1.3.0
Backup Migration v1.3.0
2.1.6 2.1.5.2 trunk 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.6.1 1.4.7 1.4.8 1.4.9 1.4.9.1 2.0.0 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.5.1
backup-backup / includes / staging / tastewp.php
backup-backup / includes / staging Last commit date
controller.php 2 years ago local.php 2 years ago tastewp.php 2 years ago
tastewp.php
354 lines
1 <?php
2
3 // Namespace
4 namespace BMI\Plugin\Staging;
5
6 // Use
7 use BMI\Plugin\Backup_Migration_Plugin as BMP;
8
9 // Exit on direct access
10 if (!defined('ABSPATH')) exit;
11
12 // Require controller
13 require_once BMI_INCLUDES . '/staging/controller.php';
14
15 /**
16 * Subclass of main staging controller (tastewp/remote handler)
17 */
18 class BMI_Staging_TasteWP extends BMI_Staging {
19
20 /**
21 * Initialization of TasteWP Staging Site Handler
22 */
23 function __construct($name, $initialize = false, $backupName = false, $delete = false) {
24
25 parent::__construct(...func_get_args());
26
27 $this->backupName = $backupName;
28
29 if ($delete) $this->abort(true);
30 else {
31 if ($initialize) $this->initialization();
32 else $this->continue();
33 }
34
35 }
36
37 private function humanToBytes($num) {
38
39 $num_split = str_split(strval(strtolower($num)));
40 $num_split_temp = $num_split;
41 $num_end = end($num_split);
42 array_pop($num_split);
43 $num_join = implode('', $num_split);
44 if ('m' === $num_end) {
45 $num = doubleval($num_join) * 1048576;
46 } elseif ('k' === $num_end) {
47 $num = doubleval($num_join) * 1024;
48 } elseif ('g' === $num_end) {
49 $num = doubleval($num_join) * 1073741824;
50 }
51
52 return $num;
53
54 }
55
56 private function getTotalAvailableMemory() {
57
58 $wpMax = $this->humanToBytes(WP_MAX_MEMORY_LIMIT);
59 $memory = $this->humanToBytes(ini_get('memory_limit'));
60 $used = memory_get_usage(false);
61
62 $lowest = $wpMax;
63 if ($memory < $lowest) $lowest = $memory;
64
65 $lowest1 = $wpMax - (1024 * 5) - $used;
66 $lowest2 = $memory - (1024 * 5) - $used;
67
68 if ($lowest2 > 0) $lowest = $lowest2;
69 else if ($lowest1 > 0) $lowest = $lowest1;
70 else $lowest = $lowest - $used;
71
72 $lowest = intval(abs($lowest));
73
74 $this->log('WP MAX MEMORY LIMIT: ' . $wpMax, 'VERBOSE');
75 $this->log('MEMORY LIMIT PHP INI: ' . $memory, 'VERBOSE');
76 $this->log('USED MEMORY: ' . $used, 'VERBOSE');
77 $this->log('AVAILABLE MEMORY: ' . $lowest, 'VERBOSE');
78 $this->log('Safe limit of memory divided by 2: ' . ($lowest / 2), 'VERBOSE');
79
80 $this->log('Safe limit of memory divided by 2 in Megabytes: ' . number_format((($lowest / 2) / 1024 / 1024), 2), 'VERBOSE');
81
82 return abs($lowest / 2);
83
84 }
85
86 private function initialization() {
87
88 global $table_prefix;
89 $ip = $this->getIpAddress();
90
91 $this->printInitialLogs();
92 $this->siteConfig['backupName'] = $this->backupName;
93 $this->siteConfig['backupPath'] = BMP::fixSlashes(BMI_BACKUPS . DIRECTORY_SEPARATOR . $this->backupName);
94
95 $this->siteConfig['creation_date'] = time();
96 $this->siteConfig['creator_ip'] = $ip;
97 $this->siteConfig['db_prefix'] = $table_prefix;
98
99 $this->siteConfig['total_files'] = '---';
100 $this->siteConfig['expiration_time'] = time() + 60*60;
101
102 $this->log(__('Staging site remote creation process initialized', 'backup-backup'), 'success');
103 $this->setContinuation(1);
104
105 }
106
107 private function sendUploadPing(&$batchData, $last) {
108
109 $url = $this->tastewpURL . '/api/bmi/staging/upload';
110 $headers = [
111 'Content-Type' => 'application/octet-stream',
112 'tastewp-backupname' => $this->siteConfig['backupName'],
113 'tastewp-localname' => $this->name,
114 'tastewp-is-last' => $last,
115 'tastewp-secret' => $this->siteConfig['communication_secret'],
116 'tastewp-uploadid' => $this->siteConfig['uploadId'],
117 'tastewp-entry' => $this->siteConfig['entry']
118 ];
119
120 $response = $this->httpRequest($url, $batchData, 'PUT', $headers);
121 return $response;
122
123 }
124
125 private function continue() {
126
127 if (isset($this->siteConfig['step'])) {
128 $this->step = intval($this->siteConfig['step']);
129 } else {
130 $this->step = null;
131 }
132
133 if (!is_numeric($this->step)) {
134
135 // End with error
136 $this->log(__('Step code was not provided for the request, prevents continuation of the process...'), 'ERROR');
137 $this->log('Step code was not provided for the request, prevents continuation of the process...', 'VERBOSE');
138 $this->log('#201', 'END-CODE');
139 return ['status' => 'error'];
140
141 }
142
143 // Default error
144 $translatedMainError = __('Something unexpected happened, we need to abort the process.', 'backup-backup');
145 $englishMainError = 'Something unexpected happened, we need to abort the process (#207).';
146
147 // Step controller
148 if ($this->step == 1) $this->initializeHandshakeWithTasteWP();
149 if ($this->step == 2) $this->processWithUpload();
150 if ($this->step == 3) $this->validateAndPrepareRecipe();
151 else if (isset($this->step) && is_numeric($this->step)) $this->sendSuccess(true);
152 else $this->returnError($translatedMainError, $englishMainError);
153
154 }
155
156 private function initializeHandshakeWithTasteWP() {
157
158 // TODO: Check if the backup is stored locally, otherwise download from cloud
159 // if () {}
160
161 if (!isset($this->siteConfig['batch']) || $this->siteConfig['batch'] == 1) {
162
163 if (!isset($this->siteConfig['backupPath']) || !file_exists($this->siteConfig['backupPath']) || is_dir($this->siteConfig['backupPath'])) {
164 $translatedFileExistError = __('Backup path was not set or backup file does not exist.', 'backup-backup');
165 $englishFileExistError = 'Backup path was not set or backup file does not exist (#208).';
166 $this->returnError($translatedFileExistError, $englishFileExistError);
167 }
168
169 $this->log(__('Getting MD5 of selected backup file...', 'backup-backup'), 'step');
170 $this->log('Backup path: ' . $this->siteConfig['backupPath'], 'verbose');
171 $md5 = md5_file($this->siteConfig['backupPath']);
172 $this->log('Calculated MD5 is: ' . $md5, 'verbose');
173 $this->log(__('File MD5 checksum saved for future.', 'backup-backup'), 'success');
174
175 $this->log(__('Making handshake with TasteWP and asking for blessing.', 'backup-backup'), 'step');
176 $this->log('Sending initial request to TasteWP', 'verbose');
177
178 $url = $this->tastewpURL . '/api/bmi/staging/init/upload';
179 $data = [ 'name' => $this->name, 'backupName' => $this->siteConfig['backupName'], 'md5' => $md5 ];
180 $response = $this->httpRequest($url, $data);
181
182 if (!isset($response['status']) || $response['status'] != 'success') {
183 if (isset($response['message'])) {
184 $this->returnError($response['message'], $response['message']);
185 } else {
186 $translatedUnknownResponseError = __('Reponse from TasteWP does not have any status code, please try again.', 'backup-backup');
187 $englishUnknownResponseError = 'Reponse from TasteWP does not have any status code, please try again (#210). ' . print_r($response, true);
188 $this->returnError($translatedUnknownResponseError, $englishUnknownResponseError);
189 }
190 }
191 $this->progress(5);
192
193 // Error handlers validate the response
194 $translatedKeyExistError = __('One or more keys does not exist in response, please try again.', 'backup-backup');
195 $englishKeyExistError = 'Missing required keys to continue the process (#209), the key is: ';
196 if (!isset($response['data'])) $this->returnError($translatedKeyExistError, $englishKeyExistError . 'data');
197 if (!isset($response['data']['secret'])) $this->returnError($translatedKeyExistError, $englishKeyExistError . 'secret');
198 if (!isset($response['data']['activation'])) $this->returnError($translatedKeyExistError, $englishKeyExistError . 'activation');
199 if (!isset($response['data']['uploadId'])) $this->returnError($translatedKeyExistError, $englishKeyExistError . 'uploadId');
200 if (!isset($response['data']['entry'])) $this->returnError($translatedKeyExistError, $englishKeyExistError . 'entry');
201 if (!isset($response['data']['maxUploadSize'])) $this->returnError($translatedKeyExistError, $englishKeyExistError . 'maxUploadSize');
202
203 $this->siteConfig['name'] = $this->name;
204 $this->siteConfig['sumOfTotalSize'] = filesize($this->siteConfig['backupPath']);
205 $this->siteConfig['display_name'] = $this->siteConfig['backupName'];
206 $this->siteConfig['url'] = $this->tastewpURL . '/stg/' . $response['data']['activation'];
207
208 $this->siteConfig['communication_secret'] = $response['data']['secret'];
209 $this->siteConfig['activation'] = $response['data']['activation'];
210 $this->siteConfig['uploadId'] = $response['data']['uploadId'];
211 $this->siteConfig['entry'] = $response['data']['entry'];
212 $this->siteConfig['maxUploadSize'] = $response['data']['maxUploadSize'];
213 $availableMemory = $this->getTotalAvailableMemory();
214
215 if ($availableMemory < intval($this->siteConfig['maxUploadSize'])) {
216 $this->siteConfig['maxUploadSize'] = $availableMemory;
217 }
218
219 $this->log(__('Blessing received, upload details saved...', 'backup-backup'), 'success');
220 $this->progress(8);
221 $this->setContinuation(2);
222
223 }
224
225 }
226
227 private function processWithUpload() {
228
229 $translatedUploadError1 = __('One or more variables are not defined, cannot calculate batching.', 'backup-backup');
230 $translatedUploadError2 = __('There was some error during the upload process, please try again.', 'backup-backup');
231 $englishUploadError1 = 'One or more variables are not defined, cannot calculate batching (#213).';
232 $englishUploadError2 = 'There was some error during the upload process, please try again (#214).';
233
234 $sizeOfFile = intval($this->siteConfig['sumOfTotalSize']);
235 $maxUploadLimitBytes = intval($this->siteConfig['maxUploadSize']); // By Default it was: 32 MBs, but it may be different for each user
236
237 if (!isset($this->siteConfig['batch']) || $this->siteConfig['batch'] == 1) {
238
239 $this->log(__('Calculating amount out total upload batches', 'backup-backup'), 'step');
240
241 if (!is_numeric($maxUploadLimitBytes) || is_nan($maxUploadLimitBytes) || !is_numeric($sizeOfFile) || is_nan($sizeOfFile)) {
242 $this->returnError($translatedUploadError1, $englishUploadError1);
243 }
244
245 $this->log(__('Max upload size in megabytes:', 'backup-backup') . ' ' . number_format($maxUploadLimitBytes / 1024 / 1024, 2) . ' MB');
246 $this->log(__('Size of backup file in megabytes:', 'backup-backup') . ' ' . number_format($sizeOfFile / 1024 / 1024, 2) . ' MB');
247 $this->log(__('Expected total amount of batches:', 'backup-backup') . ' ' . ceil($sizeOfFile / $maxUploadLimitBytes));
248
249 $this->log('Size of backup file in bytes: ' . $sizeOfFile . ' B', 'verbose');
250 $this->log('Size of backup file in megabytes: ' . number_format($sizeOfFile / 1024 / 1024, 2) . ' MB', 'verbose');
251 $this->log('Max upload size in bytes: ' . $maxUploadLimitBytes . ' B', 'verbose');
252 $this->log('Max upload size in megabytes: ' . number_format($maxUploadLimitBytes / 1024 / 1024, 2) . ' MB', 'verbose');
253
254 $this->log('Total amount of batched should be ceil(' . $sizeOfFile . ' / ' . $maxUploadLimitBytes . '): ' . ceil($sizeOfFile / $maxUploadLimitBytes), 'verbose');
255
256 $this->siteConfig['totalUploadBatches'] = ceil($sizeOfFile / $maxUploadLimitBytes);
257 $this->siteConfig['currentUploadBatch'] = 0;
258
259 $this->log(__('Backup upload to TasteWP', 'backup-backup'), 'step');
260 $this->progress(10);
261 $this->setContinuation(2, 2);
262
263 }
264
265 if (!isset($this->siteConfig['batch']) || $this->siteConfig['batch'] == 2) {
266
267 $totalSize = $sizeOfFile;
268 $maxBatchSize = $maxUploadLimitBytes;
269 $totalBatches = $this->siteConfig['totalUploadBatches'];
270 $currentBatch = $this->siteConfig['currentUploadBatch'];
271
272 if (intval($totalBatches) <= (intval($currentBatch) + 1)) $isLast = 'true';
273 else $isLast = 'false';
274
275 $start = $maxBatchSize * $currentBatch;
276 $end = $start + $maxBatchSize;
277 if ($end > $totalSize) $end = $totalSize;
278 $percent = number_format(($end / $totalSize) * 100, 2);
279 $this->progress(10 + intval(($end / $totalSize) * 80));
280
281 $backupFileHandler = fopen($this->siteConfig['backupPath'], 'r');
282 $batchData = stream_get_contents($backupFileHandler, $maxBatchSize, $start);
283 fclose($backupFileHandler);
284
285 $response = $this->sendUploadPing($batchData, $isLast);
286 if (!isset($response['status']) || $response['status'] != 'success') {
287 if (isset($response['message'])) $this->returnError($response['message'], $response['message']);
288 else $this->returnError($translatedUploadError2, $englishUploadError2);
289 }
290
291 $this->log(sprintf(__('Upload milestone: %s/%s (%s) [Batch: %s/%s]', 'backup-backup'), $end, $totalSize, $percent . '%', ($currentBatch + 1), $totalBatches), 'info');
292 $this->log('Chunk range: ' . $start . ' - ' . $end, 'verbose');
293
294 if ($isLast === 'true') {
295 $this->progress(90);
296 $this->setContinuation(3);
297 } else {
298 $this->siteConfig['currentUploadBatch']++;
299 $this->setContinuation(2, 2);
300 }
301
302 }
303
304 }
305
306 public function validateAndPrepareRecipe() {
307
308 $translatedUploadError2 = __('There was some error during the upload process, please try again.', 'backup-backup');
309 $englishUploadError2 = 'There was some error during the upload process, please try again (#316).';
310
311 if (!isset($this->siteConfig['batch']) || $this->siteConfig['batch'] == 1) {
312
313 $this->log(__('Upload process finished successfully.', 'backup-backup'), 'success');
314 $this->log(__('Preparing uploaded backup file for activation.', 'backup-backup'), 'step');
315 $this->log(__('This process may take a while.', 'backup-backup'), 'warn');
316 $this->log('At this moment TasteWP validates and prepares one-time use recipe.', 'verbose');
317
318 $url = $this->tastewpURL . '/api/bmi/staging/prepare';
319 $data = [
320 'secret' => $this->siteConfig['communication_secret'],
321 'upload' => $this->siteConfig['uploadId'],
322 'entry' => $this->siteConfig['entry']
323 ];
324
325 $response = $this->httpRequest($url, $data);
326
327 if (!isset($response['status']) || $response['status'] != 'success') {
328 if (isset($response['message'])) $this->returnError($response['message'], $response['message']);
329 else $this->returnError($translatedUploadError2, $englishUploadError2);
330 }
331
332 $this->progress(100);
333 $this->siteConfig['total_files'] = intval($response['fileAmount']);
334 $this->siteConfig['total_size'] = intval($response['recipeSize']);
335 $this->siteConfig['db_prefix'] = htmlspecialchars($response['tablePrefix']);
336
337 sleep(2);
338
339 $this->log(__('Your staging site is prepared for activation.', 'backup-backup'), 'success');
340 $this->log('It means everything is fine with the backup file and user can initialize the site now with activation URL.', 'verbose');
341
342 }
343
344 $this->setContinuation(4);
345
346 }
347
348 public function __destruct() {
349
350 parent::__destruct();
351
352 }
353
354 }