PluginProbe ʕ •ᴥ•ʔ
Backup Migration / 2.1.7
Backup Migration v2.1.7
2.1.7 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 weeks ago local.php 2 weeks ago tastewp.php 2 weeks ago
tastewp.php
355 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->continueProcess();
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 continueProcess() {
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 // TODO: Make handshaking compatible with chained hash.
172 $md5 = md5_file($this->siteConfig['backupPath']);
173 $this->log('Calculated MD5 is: ' . $md5, 'verbose');
174 $this->log(__('File MD5 checksum saved for future.', 'backup-backup'), 'success');
175
176 $this->log(__('Making handshake with TasteWP and asking for blessing.', 'backup-backup'), 'step');
177 $this->log('Sending initial request to TasteWP', 'verbose');
178
179 $url = $this->tastewpURL . '/api/bmi/staging/init/upload';
180 $data = [ 'name' => $this->name, 'backupName' => $this->siteConfig['backupName'], 'md5' => $md5 ];
181 $response = $this->httpRequest($url, $data);
182
183 if (!isset($response['status']) || $response['status'] != 'success') {
184 if (isset($response['message'])) {
185 $this->returnError($response['message'], $response['message']);
186 } else {
187 $translatedUnknownResponseError = __('Reponse from TasteWP does not have any status code, please try again.', 'backup-backup');
188 $englishUnknownResponseError = 'Reponse from TasteWP does not have any status code, please try again (#210). ' . print_r($response, true);
189 $this->returnError($translatedUnknownResponseError, $englishUnknownResponseError);
190 }
191 }
192 $this->progress(5);
193
194 // Error handlers validate the response
195 $translatedKeyExistError = __('One or more keys does not exist in response, please try again.', 'backup-backup');
196 $englishKeyExistError = 'Missing required keys to continue the process (#209), the key is: ';
197 if (!isset($response['data'])) $this->returnError($translatedKeyExistError, $englishKeyExistError . 'data');
198 if (!isset($response['data']['secret'])) $this->returnError($translatedKeyExistError, $englishKeyExistError . 'secret');
199 if (!isset($response['data']['activation'])) $this->returnError($translatedKeyExistError, $englishKeyExistError . 'activation');
200 if (!isset($response['data']['uploadId'])) $this->returnError($translatedKeyExistError, $englishKeyExistError . 'uploadId');
201 if (!isset($response['data']['entry'])) $this->returnError($translatedKeyExistError, $englishKeyExistError . 'entry');
202 if (!isset($response['data']['maxUploadSize'])) $this->returnError($translatedKeyExistError, $englishKeyExistError . 'maxUploadSize');
203
204 $this->siteConfig['name'] = $this->name;
205 $this->siteConfig['sumOfTotalSize'] = filesize($this->siteConfig['backupPath']);
206 $this->siteConfig['display_name'] = $this->siteConfig['backupName'];
207 $this->siteConfig['url'] = $this->tastewpURL . '/stg/' . $response['data']['activation'];
208
209 $this->siteConfig['communication_secret'] = $response['data']['secret'];
210 $this->siteConfig['activation'] = $response['data']['activation'];
211 $this->siteConfig['uploadId'] = $response['data']['uploadId'];
212 $this->siteConfig['entry'] = $response['data']['entry'];
213 $this->siteConfig['maxUploadSize'] = $response['data']['maxUploadSize'];
214 $availableMemory = $this->getTotalAvailableMemory();
215
216 if ($availableMemory < intval($this->siteConfig['maxUploadSize'])) {
217 $this->siteConfig['maxUploadSize'] = $availableMemory;
218 }
219
220 $this->log(__('Blessing received, upload details saved...', 'backup-backup'), 'success');
221 $this->progress(8);
222 $this->setContinuation(2);
223
224 }
225
226 }
227
228 private function processWithUpload() {
229
230 $translatedUploadError1 = __('One or more variables are not defined, cannot calculate batching.', 'backup-backup');
231 $translatedUploadError2 = __('There was some error during the upload process, please try again.', 'backup-backup');
232 $englishUploadError1 = 'One or more variables are not defined, cannot calculate batching (#213).';
233 $englishUploadError2 = 'There was some error during the upload process, please try again (#214).';
234
235 $sizeOfFile = intval($this->siteConfig['sumOfTotalSize']);
236 $maxUploadLimitBytes = intval($this->siteConfig['maxUploadSize']); // By Default it was: 32 MBs, but it may be different for each user
237
238 if (!isset($this->siteConfig['batch']) || $this->siteConfig['batch'] == 1) {
239
240 $this->log(__('Calculating amount out total upload batches', 'backup-backup'), 'step');
241
242 if (!is_numeric($maxUploadLimitBytes) || is_nan($maxUploadLimitBytes) || !is_numeric($sizeOfFile) || is_nan($sizeOfFile)) {
243 $this->returnError($translatedUploadError1, $englishUploadError1);
244 }
245
246 $this->log(__('Max upload size in megabytes:', 'backup-backup') . ' ' . number_format($maxUploadLimitBytes / 1024 / 1024, 2) . ' MB');
247 $this->log(__('Size of backup file in megabytes:', 'backup-backup') . ' ' . number_format($sizeOfFile / 1024 / 1024, 2) . ' MB');
248 $this->log(__('Expected total amount of batches:', 'backup-backup') . ' ' . ceil($sizeOfFile / $maxUploadLimitBytes));
249
250 $this->log('Size of backup file in bytes: ' . $sizeOfFile . ' B', 'verbose');
251 $this->log('Size of backup file in megabytes: ' . number_format($sizeOfFile / 1024 / 1024, 2) . ' MB', 'verbose');
252 $this->log('Max upload size in bytes: ' . $maxUploadLimitBytes . ' B', 'verbose');
253 $this->log('Max upload size in megabytes: ' . number_format($maxUploadLimitBytes / 1024 / 1024, 2) . ' MB', 'verbose');
254
255 $this->log('Total amount of batched should be ceil(' . $sizeOfFile . ' / ' . $maxUploadLimitBytes . '): ' . ceil($sizeOfFile / $maxUploadLimitBytes), 'verbose');
256
257 $this->siteConfig['totalUploadBatches'] = ceil($sizeOfFile / $maxUploadLimitBytes);
258 $this->siteConfig['currentUploadBatch'] = 0;
259
260 $this->log(__('Backup upload to TasteWP', 'backup-backup'), 'step');
261 $this->progress(10);
262 $this->setContinuation(2, 2);
263
264 }
265
266 if (!isset($this->siteConfig['batch']) || $this->siteConfig['batch'] == 2) {
267
268 $totalSize = $sizeOfFile;
269 $maxBatchSize = $maxUploadLimitBytes;
270 $totalBatches = $this->siteConfig['totalUploadBatches'];
271 $currentBatch = $this->siteConfig['currentUploadBatch'];
272
273 if (intval($totalBatches) <= (intval($currentBatch) + 1)) $isLast = 'true';
274 else $isLast = 'false';
275
276 $start = $maxBatchSize * $currentBatch;
277 $end = $start + $maxBatchSize;
278 if ($end > $totalSize) $end = $totalSize;
279 $percent = number_format(($end / $totalSize) * 100, 2);
280 $this->progress(10 + intval(($end / $totalSize) * 80));
281
282 $backupFileHandler = fopen($this->siteConfig['backupPath'], 'r');
283 $batchData = stream_get_contents($backupFileHandler, $maxBatchSize, $start);
284 fclose($backupFileHandler);
285
286 $response = $this->sendUploadPing($batchData, $isLast);
287 if (!isset($response['status']) || $response['status'] != 'success') {
288 if (isset($response['message'])) $this->returnError($response['message'], $response['message']);
289 else $this->returnError($translatedUploadError2, $englishUploadError2);
290 }
291
292 $this->log(sprintf(__('Upload milestone: %s/%s (%s) [Batch: %s/%s]', 'backup-backup'), $end, $totalSize, $percent . '%', ($currentBatch + 1), $totalBatches), 'info');
293 $this->log('Chunk range: ' . $start . ' - ' . $end, 'verbose');
294
295 if ($isLast === 'true') {
296 $this->progress(90);
297 $this->setContinuation(3);
298 } else {
299 $this->siteConfig['currentUploadBatch']++;
300 $this->setContinuation(2, 2);
301 }
302
303 }
304
305 }
306
307 public function validateAndPrepareRecipe() {
308
309 $translatedUploadError2 = __('There was some error during the upload process, please try again.', 'backup-backup');
310 $englishUploadError2 = 'There was some error during the upload process, please try again (#316).';
311
312 if (!isset($this->siteConfig['batch']) || $this->siteConfig['batch'] == 1) {
313
314 $this->log(__('Upload process finished successfully.', 'backup-backup'), 'success');
315 $this->log(__('Preparing uploaded backup file for activation.', 'backup-backup'), 'step');
316 $this->log(__('This process may take a while.', 'backup-backup'), 'warn');
317 $this->log('At this moment TasteWP validates and prepares one-time use recipe.', 'verbose');
318
319 $url = $this->tastewpURL . '/api/bmi/staging/prepare';
320 $data = [
321 'secret' => $this->siteConfig['communication_secret'],
322 'upload' => $this->siteConfig['uploadId'],
323 'entry' => $this->siteConfig['entry']
324 ];
325
326 $response = $this->httpRequest($url, $data);
327
328 if (!isset($response['status']) || $response['status'] != 'success') {
329 if (isset($response['message'])) $this->returnError($response['message'], $response['message']);
330 else $this->returnError($translatedUploadError2, $englishUploadError2);
331 }
332
333 $this->progress(100);
334 $this->siteConfig['total_files'] = intval($response['fileAmount']);
335 $this->siteConfig['total_size'] = intval($response['recipeSize']);
336 $this->siteConfig['db_prefix'] = htmlspecialchars($response['tablePrefix']);
337
338 sleep(2);
339
340 $this->log(__('Your staging site is prepared for activation.', 'backup-backup'), 'success');
341 $this->log('It means everything is fine with the backup file and user can initialize the site now with activation URL.', 'verbose');
342
343 }
344
345 $this->setContinuation(4);
346
347 }
348
349 public function __destruct() {
350
351 parent::__destruct();
352
353 }
354
355 }