PluginProbe
WP Synchro – The Ultimate WordPress Migration Tool / trunk
WP Synchro – The Ultimate WordPress Migration Tool vtrunk
1.16.1 1.16.0 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.1.0 1.10.0 1.11.0 1.11.1 1.11.2 1.11.3 1.11.4 1.11.5 1.12.0 1.13.0 1.14.0 1.15.0 1.2.0 1.3.0 1.3.1 1.3.2 All 45 releases
wpsynchro / src / API / MasterData.php

MasterData.php in WP Synchro – The Ultimate WordPress Migration Tool trunk, at src/API/MasterData.php

576 lines 20.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPSynchro\API;
4
5 use WPSynchro\Utilities\CommonFunctions;
6 use WPSynchro\Database\DatabaseSync;
7 use WPSynchro\Database\Table;
8 use WPSynchro\Database\TableColumns;
9 use WPSynchro\Transport\ReturnResult;
10 use WPSynchro\Utilities\DebugInformation;
11
12 /**
13 * Class for handling service "masterdata"
14 * Call should already be verified by permissions callback
15 *
16 */
17 class MasterData extends WPSynchroService
18 {
19 // Column types - in how they are inserted into database
20 public $string_columns_types = ["decimal", "dec", "fixed", "numeric", "json", "date", "datetime", "timestamp", "time", "year", "char", "varchar", "tinytext", "text", "mediumtext", "longtext", "enum", "set"];
21 public $numeric_column_types = ["tinyint", "smallint", "mediumint", "int", "bigint", "float", "double", "real"];
22 public $bit_column_types = ["bit"];
23 public $binary_column_types = ["blob", "tinyblob", "mediumblob", "longblob", "binary", "varbinary", "point", "geometry", "linestring", "polygon", "multipoint", "multilinestring", "multipolygon", "geometrycollection"];
24 // Tables to exclude
25 public $tables_to_exclude = ["wpsynchro_sync_list", "wpsynchro_file_population_list"];
26
27 public function service()
28 {
29 $result = new \stdClass();
30
31 // Check php/mysql/wp requirements
32 $commonfunctions = new CommonFunctions();
33 $compat_errors = $commonfunctions->checkEnvCompatability();
34
35 if (count($compat_errors) > 0) {
36 // @codeCoverageIgnoreStart
37 foreach ($compat_errors as &$error) {
38 $error = __("Error from remote server:", "wpsynchro") . " " . $error;
39 }
40 $result->errors = $compat_errors;
41
42 $returnresult = new ReturnResult();
43 $returnresult->init();
44 $returnresult->setDataObject($result);
45 $returnresult->setHTTPStatus(500);
46 return $returnresult->echoDataFromServiceAndExit();
47 // @codeCoverageIgnoreEnd
48 }
49
50 if (isset($_REQUEST['type'])) {
51 $type = $_REQUEST['type'];
52 } else {
53 $type = [];
54 }
55
56 /**
57 * Insert standard information on site
58 */
59 $result->base = $this->getBaseSiteData();
60
61 /**
62 * Multisite data
63 */
64 $result->multisite = $this->getMultisiteData();
65
66 /**
67 * Get tables in database
68 */
69 if (in_array('dbtables', $type)) {
70 $result->dbtables = $this->getBasicDatabaseData();
71 }
72
73 /**
74 * Get detailed listing of database tables and sizes
75 */
76 if (in_array('dbdetails', $type)) {
77 $dbdetails = $this->getDetailsDatabaseData();
78 $result->dbdetails = $dbdetails->dbdetails;
79 $result->tmptables_dbdetails = $dbdetails->tmptables_dbdetails;
80 }
81
82 /**
83 * Get information needed for files
84 */
85 if (in_array('filedetails', $type)) {
86 $result->files = $this->getFileDetailsData();
87 }
88
89 /**
90 * Include debug data for log
91 */
92 $debuginformation = new DebugInformation();
93 $result->debug = $debuginformation->getAllDebugInformation();
94
95 // Return
96 $return_result = new ReturnResult();
97 $return_result->init();
98 $return_result->setDataObject($result);
99 return $return_result->echoDataFromServiceAndExit();
100 }
101
102 /**
103 * Get base data for site
104 */
105 public function getBaseSiteData()
106 {
107 global $wpdb;
108 $commonfunctions = new CommonFunctions();
109
110 $result = new \stdClass();
111
112 $result->client_home_url = home_url('/');
113 $result->wpdb_prefix = $wpdb->prefix;
114 $result->wp_options_table = $wpdb->options;
115 $result->wp_users_table = $wpdb->users;
116 $result->wp_usermeta_table = $wpdb->usermeta;
117
118 $home_url_from_db = $wpdb->get_var("select option_value from " . $wpdb->options . " where option_name='home'");
119 if (!$home_url_from_db) {
120 $home_url_from_db = "";
121 }
122
123 $result->home_url_db = trim($home_url_from_db, "/");
124 $result->home_url_constant = trim((defined('WP_HOME') ? WP_HOME : ""), "/");
125
126 // Get max allowed packet size from sql
127 $result->max_allowed_packet_size = (int) $wpdb->get_row("SHOW VARIABLES LIKE 'max_allowed_packet'")->Value;
128 // Get max post size
129 $result->max_post_size = $commonfunctions->convertPHPSizeToBytes(ini_get('post_max_size'));
130 if ($result->max_post_size < 1) {
131 // If set to 0, which mean unlimited, we just set it to 100mb
132 $result->max_post_size = 104857600;
133 }
134 // Get memory limit
135 $result->memory_limit = $commonfunctions->convertPHPSizeToBytes(ini_get('memory_limit'));
136 // If set to -1, which mean unlimited, we just set it to 512mb
137 if ($result->memory_limit < 1) {
138 $result->memory_limit = 536870912;
139 }
140 // MySQL version
141 $result->sql_version = $wpdb->get_var("select VERSION()");
142 // WP Synchro plugin version
143 $result->plugin_version = WPSYNCHRO_VERSION . " " . (\WPSynchro\Utilities\CommonFunctions::isPremiumVersion() ? 'PRO' : 'FREE');
144 // WP version
145 include ABSPATH . WPINC . '/version.php';
146 $result->wp_version = $wp_version;
147 // Get whether the MU plugin is enabled
148 $result->mu_plugin_enabled = defined('WPSYNCHRO_MU_COMPATIBILITY_VERSION');
149
150 // Add whether database contains tables with different prefix
151 $database_helper_functions = new \WPSynchro\Database\DatabaseHelperFunctions();
152 $result->database_contains_tables_with_different_prefix = $database_helper_functions->databaseContainsTablesWithDifferentPrefix();
153
154 return $result;
155 }
156
157 /**
158 * Get data on multisite, if it is multisite, ofc...
159 */
160 public function getMultisiteData()
161 {
162 $multisite = new \stdClass();
163 $is_multisite = false;
164 if (is_multisite()) {
165 $is_multisite = true;
166 $multisite->is_multisite = true;
167
168 // Detect the "main" site blog id
169 $multisite->main_blog_id = get_network()->site_id;
170 $multisite->current_blog_id = get_current_blog_id();
171 $multisite->is_main_site = ($multisite->main_blog_id === $multisite->current_blog_id);
172
173 // Add list of other blogs
174 $sitelist = get_sites();
175 $multisite->blogs = [];
176 foreach ($sitelist as $site) {
177 $blog = new \stdClass();
178 $blog->id = $site->blog_id;
179 $blog->domain = $site->domain;
180 $blog->path = $site->path;
181 $multisite->blogs[] = $blog;
182 }
183
184 // Get the first super admin, to have a user_id to assign content to
185 $super_admin = get_super_admins();
186 if (count($super_admin) > 0) {
187 $multisite->default_super_admin_id = username_exists($super_admin[0]);
188 $multisite->default_super_admin_username = $super_admin[0];
189 } else {
190 $multisite->default_super_admin_id = 0;
191 $multisite->default_super_admin_username = "";
192 }
193 } else {
194 $multisite->is_multisite = false;
195 $multisite->is_main_site = false;
196 }
197 $multisite->defined_uploads_location = defined("UPLOADS") ? UPLOADS : "";
198
199 return $multisite;
200 }
201
202 /**
203 * Get basic data on database
204 */
205 public function getBasicDatabaseData()
206 {
207 global $wpdb;
208
209 $tables_sql = $wpdb->get_col('SHOW TABLES');
210
211 $tables = [];
212 foreach ($tables_sql as $tablename) {
213 // If temp table, just skip it
214 if (strpos($tablename, DatabaseSync::TMP_TABLE_PREFIX) === 0) {
215 continue;
216 }
217 // Check if table should be excluded, such as WP Synchro tables
218 if ($this->shouldTableBeExcluded($tablename)) {
219 continue;
220 }
221 $tables[] = $tablename;
222 }
223 return $tables;
224 }
225
226 /**
227 * Get details on database
228 */
229 public function getDetailsDatabaseData()
230 {
231 global $wpdb;
232 $result = new \stdClass();
233 $tables_sql = $wpdb->get_results('SHOW TABLE STATUS');
234 $tables_details = [];
235 $table_tmptables_details = [];
236 foreach ($tables_sql as $tb) {
237 // Check if table should be excluded, such as WP Synchro tables
238 if ($this->shouldTableBeExcluded($tb->Name)) {
239 continue;
240 }
241
242 // Get the actual count on rows, because show table status is not precise
243 $exactrows = $wpdb->get_var("SELECT COUNT(*) FROM `" . $tb->Name . "`");
244 $new_table = new Table();
245 $new_table->name = $tb->Name;
246 $new_table->rows = intval($exactrows);
247 $new_table->completed_rows = 0;
248 $new_table->row_avg_bytes = $tb->Avg_row_length;
249 $new_table->data_total_bytes = $tb->Data_length;
250
251 // Fix some cases, where row_avg_bytes are reported to be 0, even if there is some rows
252 if ($new_table->rows > 0 && $new_table->row_avg_bytes == 0) {
253 $new_table->row_avg_bytes = 8192;
254 }
255
256 // If temp table, add to seperate array (mostly used in finalize)
257 if (strpos($new_table->name, DatabaseSync::TMP_TABLE_PREFIX) === 0) {
258 $table_tmptables_details[] = $new_table;
259 } else {
260 $tables_details[] = $new_table;
261 }
262 }
263
264 // Show create table
265 foreach ($tables_details as &$tb) {
266 $createsql = $wpdb->get_row('show create table `' . $tb->name . '`', ARRAY_N);
267 $createsql[1] = mb_convert_encoding($createsql[1], 'UTF-8', 'UTF-8');
268 $tb->create_table = $createsql[1];
269
270 // If it is a view, make sure we mark it as one and clean it up a bit
271 if (preg_match('#\CREATE[^\]]+\VIEW `#', $tb->create_table)) {
272 $cleaned_view_create = preg_replace('#\CREATE[^\]]+\VIEW `#', '', $tb->create_table);
273 $tb->create_table = "CREATE VIEW `" . $cleaned_view_create;
274 $tb->is_view = true;
275 }
276
277 // Clean up CREATE statement
278 $tb->create_table = $this->cleanUpCreateStatement($tb->create_table);
279 }
280
281 // Get primary key (for faster data fetch)
282 foreach ($tables_details as &$tb) {
283 $primarysql_key = $wpdb->get_results('SHOW KEYS FROM `' . $tb->name . '` WHERE Key_name = "PRIMARY"', ARRAY_N);
284 // Check if composite key
285 if ($primarysql_key && count($primarysql_key) > 1) {
286 // Primary key is composite, so we dont use it
287 $tb->primary_key_column = "";
288 } elseif (isset($primarysql_key[0][4])) {
289 $tb->primary_key_column = $primarysql_key[0][4];
290 if (!$this->isPrimaryIndexNumeric($tb->create_table, $tb->primary_key_column)) {
291 $tb->primary_key_column = "";
292 }
293 } else {
294 $tb->primary_key_column = "";
295 }
296 }
297
298 // Check for speciel columns, ex blob's
299 foreach ($tables_details as &$tb) {
300 $tb->column_types = $this->extractColumnsTypeFromSQLCreate($tb->create_table);
301 }
302
303 $result->dbdetails = $tables_details;
304 $result->tmptables_dbdetails = $table_tmptables_details;
305
306 return $result;
307 }
308
309 /**
310 * Clean up create statement
311 */
312 public function cleanUpCreateStatement($create_statement)
313 {
314 // Change any fields enclosed with " to backticks ` (looking at you MySQL 8...)
315 $backtick_matches = [];
316 preg_match_all('/\s+"(\S+)"\s/', $create_statement, $backtick_matches);
317 if (isset($backtick_matches[1])) {
318 foreach ($backtick_matches[1] as $match) {
319 $create_statement = str_replace('"' . $match . '"', '`' . $match . '`', $create_statement);
320 }
321 }
322
323 return $create_statement;
324 }
325
326
327 /**
328 * Get file details data
329 */
330 public function getFileDetailsData()
331 {
332 $commonfunctions = new CommonFunctions();
333
334 $result = new \stdClass();
335
336 // Web root
337 $documentroot = untrailingslashit($commonfunctions->fixPath(realpath($_SERVER['DOCUMENT_ROOT'])));
338 if (is_multisite()) {
339 $homeurl = parse_url(network_site_url('/'));
340 } else {
341 $homeurl = parse_url(home_url('/'));
342 }
343 $pathcomponent = $commonfunctions->fixPath($homeurl['path']);
344 $home_dir = untrailingslashit($documentroot . $pathcomponent);
345
346 $result->files_home_dir_readwrite = static::checkReadWriteOnDir($home_dir);
347 $result->files_home_dir = $home_dir;
348
349 // One dir above webroot
350 $files_above_webroot_dir = untrailingslashit(dirname($result->files_home_dir));
351 $result->files_above_webroot_dir_readwrite = static::checkReadWriteOnDir($files_above_webroot_dir);
352 $result->files_above_webroot_dir = $commonfunctions->fixPath($files_above_webroot_dir);
353
354 // Absolut directory of WordPress root folder
355 $result->files_wp_dir = untrailingslashit($commonfunctions->fixPath(ABSPATH));
356 $result->files_wp_dir_readwrite = static::checkReadWriteOnDir($result->files_wp_dir);
357
358 // Absolut directory of WP_CONTENT folder, or whatever it is called
359 $result->files_wp_content_dir = untrailingslashit($commonfunctions->fixPath(WP_CONTENT_DIR));
360 $result->files_wp_content_dir_readwrite = static::checkReadWriteOnDir($result->files_wp_content_dir);
361
362 // Plugins dir
363 $result->files_plugins_dir = untrailingslashit($commonfunctions->fixPath(WP_PLUGIN_DIR));
364 $result->files_plugins_dir_readwrite = static::checkReadWriteOnDir($result->files_plugins_dir);
365
366 // Themes dir
367 $result->files_themes_dir = untrailingslashit($commonfunctions->fixPath(get_theme_root()));
368 $result->files_themes_dir_readwrite = static::checkReadWriteOnDir($result->files_themes_dir);
369
370 // Uploads dir
371 $upload_dir_obj = wp_upload_dir();
372 $result->files_uploads_dir = untrailingslashit($commonfunctions->fixPath($upload_dir_obj['basedir']));
373 $result->files_uploads_dir_readwrite = static::checkReadWriteOnDir($result->files_uploads_dir);
374
375 // Get plugin list
376 $result->files_plugin_list = [];
377 if (!function_exists('get_plugins')) {
378 require_once(ABSPATH . '/wp-admin/includes/plugin.php');
379 }
380 $all_pluginlist = \get_plugins();
381 foreach ($all_pluginlist as $pluginslug => $plugindata) {
382 $tmp_arr = [];
383 $tmp_arr['slug'] = $pluginslug;
384 $tmp_arr['name'] = $plugindata['Name'];
385
386 $result->files_plugin_list[] = $tmp_arr;
387 }
388 // Get theme list
389 $result->files_theme_list = [];
390 $all_themeslist = \wp_get_themes();
391 foreach ($all_themeslist as $themeslug => $wp_theme) {
392 $tmp_arr = [];
393 $tmp_arr['slug'] = $themeslug;
394 $tmp_arr['name'] = $wp_theme->get("Name");
395
396 $result->files_theme_list[] = $tmp_arr;
397 }
398 return $result;
399 }
400
401 /**
402 * Check if table should be excluded
403 */
404 public function shouldTableBeExcluded($tablename)
405 {
406 $exclude_this_table = false;
407
408 if (strpos($tablename, DatabaseSync::TMP_TABLE_PREFIX) === 0) {
409 return false;
410 }
411
412 foreach ($this->tables_to_exclude as $excludedtable) {
413 if (stripos($tablename, $excludedtable) !== false) {
414 $exclude_this_table = true;
415 break;
416 }
417 }
418 if ($exclude_this_table) {
419 return true;
420 }
421
422 // If it is multisite and mainsite, show all. If subsite, only show global users and usermeta and the subsite tables
423 if (is_multisite() && get_current_blog_id() != get_network()->site_id) {
424 global $wpdb;
425 $currentblog_id = get_current_blog_id();
426 // Get global prefix
427 switch_to_blog(get_network()->site_id);
428 $global_tables = [$wpdb->prefix . "users", $wpdb->prefix . "usermeta"];
429 restore_current_blog();
430 if (strpos($tablename, $wpdb->prefix) !== 0) {
431 $exclude_this_table = true;
432 }
433 if (in_array($tablename, $global_tables)) {
434 $exclude_this_table = false;
435 }
436 }
437
438 return $exclude_this_table;
439 }
440
441 /**
442 * Function to extract the columns "super"-type
443 */
444 public function extractColumnsTypeFromSQLCreate($sqlcreate)
445 {
446 $columns = new TableColumns();
447
448 $lines = explode("\n", $sqlcreate);
449 foreach ($lines as $line) {
450 if (strpos(trim($line), "`") != 0) {
451 continue;
452 }
453
454 $parts = explode("`", $line);
455 if (isset($parts[1]) && isset($parts[2])) {
456 // Search the column type for types we know to be
457 $splitted = preg_split("/[\s,(]+/", trim($parts[2]));
458 $columntype = strtolower($splitted[0]);
459
460 $found = false;
461 $colname = trim($parts[1]);
462
463 // Check if we have a generated column
464 if (strpos($line, ' GENERATED ') !== false) {
465 $columns->generated[$colname] = $colname;
466 }
467
468 // Check if string type insert
469 foreach ($this->string_columns_types as $search) {
470 if ($columntype == $search) {
471 $found = true;
472 $columns->string[$colname] = $colname;
473 $columns->addColumnTypeUsed($columntype);
474 break;
475 }
476 }
477
478 if ($found) {
479 continue;
480 }
481
482 // Check if numeric type insert
483 foreach ($this->numeric_column_types as $search) {
484 if ($columntype == $search) {
485 $found = true;
486 $columns->numeric[$colname] = $colname;
487 $columns->addColumnTypeUsed($columntype);
488 break;
489 }
490 }
491
492 if ($found) {
493 continue;
494 }
495
496 // Check if numeric type insert
497 foreach ($this->binary_column_types as $search) {
498 if ($columntype == $search) {
499 $found = true;
500 $columns->binary[$colname] = $colname;
501 $columns->addColumnTypeUsed($columntype);
502 break;
503 }
504 }
505
506 if ($found) {
507 continue;
508 }
509
510 // Check if numeric type insert
511 foreach ($this->bit_column_types as $search) {
512 if ($columntype == $search) {
513 $found = true;
514 $columns->bit[$colname] = $colname;
515 $columns->addColumnTypeUsed($columntype);
516 break;
517 }
518 }
519
520 if (!$found) {
521 $columns->unknown[$colname] = $colname;
522 }
523 }
524 }
525
526 return $columns;
527 }
528
529 /**
530 * Function to determine if primary index is numeric
531 */
532 public function isPrimaryIndexNumeric($sqlcreate, $column)
533 {
534 if ($column == "") {
535 return false;
536 }
537
538 $lines = explode("\n", $sqlcreate);
539 $column = '`' . $column . '`';
540 foreach ($lines as $line) {
541 if (strpos($line, $column) > -1) {
542 $parts = explode("`", $line);
543 $col_part = trim($parts[2]);
544 $col_parts = explode(" ", $col_part);
545
546 foreach ($this->numeric_column_types as $num_col_type) {
547 if (strpos($col_parts[0], $num_col_type) > -1) {
548 return true;
549 }
550 }
551 }
552 }
553 return false;
554 }
555
556 /**
557 * Function to check if dir can be read/written to
558 */
559 public static function checkReadWriteOnDir($dir)
560 {
561 // Default error handler is required
562 set_error_handler(null);
563 @trigger_error('__clean_error_info');
564
565 // Testing...
566 @is_writable($dir);
567 @is_readable($dir);
568
569 // Restore previous error handler
570 restore_error_handler();
571
572 $error = error_get_last();
573 return $error['message'] === '__clean_error_info';
574 }
575 }
576