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 / Masterdata / MasterdataSync.php

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

406 lines 18.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPSynchro\Masterdata;
4
5 use WPSynchro\Database\DatabaseSync;
6 use WPSynchro\Migration\MigrationController;
7 use WPSynchro\Transport\Destination;
8 use WPSynchro\Utilities\CommonFunctions;
9 use WPSynchro\Utilities\Licensing\Licensing;
10 use WPSynchro\Utilities\SyncTimerList;
11 use WPSynchro\Migration\Migration;
12 use WPSynchro\Migration\Job;
13 use WPSynchro\Logger\FileLogger;
14
15 /**
16 * Class for handling the masterdata of the sync
17 *
18 */
19 class MasterdataSync
20 {
21 // Base data
22 public int $starttime = 0;
23 public ?Migration $migration = null;
24 public ?Job $job = null;
25 public ?\wpdb $remote_wpdb = null;
26 // Dependencies
27 public ?FileLogger $logger = null;
28 public ?SyncTimerList $timer = null;
29
30 /**
31 * Constructor
32 */
33 public function __construct()
34 {
35 $this->logger = MigrationController::getInstance()->getLogger();
36 $this->timer = SyncTimerList::getInstance();
37 }
38
39 /**
40 * Handle masterdata step
41 */
42 public function runMasterdataStep(Migration &$migration, Job &$job)
43 {
44 $masterdata_timer = $this->timer->startTimer("masterdata", "overall", "timer");
45
46 $this->migration = &$migration;
47 $this->job = &$job;
48
49 $this->logger->log("INFO", "Getting masterdata from source and target with remaining time:" . $this->timer->getRemainingSyncTime());
50
51 // Figure out what data is needed
52 $data_to_retrieve = [];
53 $data_to_retrieve[] = "dbdetails";
54 $data_to_retrieve[] = "filedetails";
55
56 // Retrieve data
57 $metadata_results = [];
58 $metadata_results['from'] = $this->retrieveMasterdata(new Destination(Destination::SOURCE), $data_to_retrieve);
59 $metadata_results['to'] = $this->retrieveMasterdata(new Destination(Destination::TARGET), $data_to_retrieve);
60
61 foreach ($metadata_results as $prefix => $masterdata_content) {
62 if (in_array("dbdetails", $data_to_retrieve)) {
63 if (!isset($masterdata_content->dbdetails) || $masterdata_content->dbdetails == null) {
64 $errormsg = sprintf(__("Did not retrieve correct database masterdata from target '%s' - See log file", "wpsynchro"), $prefix);
65 $this->job->errors[] = $errormsg;
66 $this->logger->log("CRITICAL", $errormsg, $metadata_results);
67 return;
68 }
69 }
70
71 // Process and map the data to Job object
72 $this->handleMasterdataMapping($prefix, $masterdata_content);
73 }
74
75 // Handle system search replaces depending on setup
76 $this->findSystemSearchReplaces();
77
78 // Initize some configuration in the job object
79 $this->inititializeConfigurations();
80
81 // Check that the migration is compatible with the masterdata gotten
82 $this->checkMigrationCompatibility();
83
84 if (count($this->job->errors) == 0) {
85 // Check that plugin versions are identical on both sides, otherwise raise error
86 if ($this->job->from_plugin_version != $this->job->to_plugin_version) {
87 $this->job->errors[] = sprintf(__("WP Synchro plugin versions do not match on both sides. One runs version %s and other runs %s. Make sure they use same version to prevent problems caused by different versions of plugin.", "wpsynchro"), $this->job->from_plugin_version, $this->job->to_plugin_version);
88 }
89
90 // Check if WP versions are different, then raise a warning, as it might not be a problem
91 if ($this->job->to_wp_version != $this->job->from_wp_version) {
92 $this->job->warnings[] = sprintf(__("WordPress versions are different on the source and target. Source WP version is: %s and target WP version is: %s. This is just a warning, as in most cases it will not cause problems.", "wpsynchro"), $this->job->from_wp_version, $this->job->to_wp_version);
93 }
94
95 // Check that prefix are the same or issue warning
96 if ($this->migration->sync_database && !$this->migration->db_table_prefix_change && $this->job->from_wpdb_prefix != $this->job->to_wpdb_prefix) {
97 $prefix_warning = sprintf(__("Database table prefixes are different on the source and target site. Source uses '%s' and target uses '%s'. Table prefix migration is not enabled in the migration configuration. This is just a warning, as the migration can complete, but the tables will not be used by the target site. Recommended action is to turn on the table prefix migration in the migration configuration.", "wpsynchro"), $this->job->from_wpdb_prefix, $this->job->to_wpdb_prefix);
98 $this->job->warnings[] = $prefix_warning;
99 $this->logger->log("WARNING", $prefix_warning);
100 }
101
102 // If doing file sync, make sure the MU plugin is enabled on the target to prevent all kinds of PHP errors for half-transferred code
103 if ($this->migration->sync_files && $this->job->to_mu_plugin_enabled == false) {
104 $this->job->errors[] = __("When doing file migrations, it is required to have the WP Synchro MU plugin active on the target site. It can be enabled on the target site in menu WP Synchro > Setup.", "wpsynchro");
105 }
106
107 // Check licensing
108 if (CommonFunctions::isPremiumVersion() && count($this->job->errors) === 0) {
109 $licensing = new Licensing();
110 $licens_sync_result = $licensing->verifyLicenseForMigration($this->job->from_client_home_url, $this->job->to_client_home_url);
111
112 if ($licens_sync_result->state === false) {
113 $this->job->errors = array_merge($this->job->errors, $licens_sync_result->errors);
114 }
115 }
116 }
117
118 $this->logger->log("INFO", "Completed masterdata on: " . $this->timer->endTimer($masterdata_timer) . " seconds");
119
120 if (count($this->job->errors) === 0) {
121 $this->job->masterdata_completed = true;
122 }
123 }
124
125 /**
126 * Check that the masterdata is compatible with the migration requested
127 */
128 public function checkMigrationCompatibility()
129 {
130 // Database migration checks
131 if ($this->migration->sync_database) {
132 // Make sure no tables using "json" column type is migrated to a pre 5.7 version.
133 foreach ($this->job->from_dbmasterdata as $table) {
134 if (version_compare($this->job->to_sql_version, '5.7', '<')) {
135 if ($table->column_types->isColumnTypeUsed('json')) {
136 $this->job->errors[] = sprintf(
137 __('The database table "%s" is not supported on the target database version (%s) and therefore it can not be migrated. To fix this error, either exclude the table from migration or upgrade the target database to a newer version.', 'wpsynchro'),
138 $table->name,
139 $this->job->to_sql_version
140 );
141 }
142 }
143 }
144 }
145 }
146
147 /**
148 * Masterdata mapping
149 */
150 public function handleMasterdataMapping($prefix, $masterdata_content)
151 {
152 /**
153 * Base mappings
154 */
155 if (isset($masterdata_content->base)) {
156 $mappings = [
157 "_client_home_url" => "client_home_url",
158 "_wpdb_prefix" => "wpdb_prefix",
159 "_wp_options_table" => "wp_options_table",
160 "_wp_users_table" => "wp_users_table",
161 "_wp_usermeta_table" => "wp_usermeta_table",
162 "_max_allowed_packet_size" => "max_allowed_packet_size",
163 "_max_post_size" => "max_post_size",
164 "_memory_limit" => "memory_limit",
165 "_sql_version" => "sql_version",
166 "_plugin_version" => "plugin_version",
167 "_wp_version" => "wp_version",
168 "_mu_plugin_enabled" => "mu_plugin_enabled",
169 ];
170
171 foreach ($mappings as $job_key => $masterdata_key) {
172 if (!isset($masterdata_content->base->$masterdata_key)) {
173 continue;
174 }
175 $tmp_var = $prefix . $job_key;
176 $this->job->$tmp_var = $masterdata_content->base->$masterdata_key;
177 }
178 }
179
180 /**
181 * Multisite mapping
182 */
183 if (isset($masterdata_content->multisite)) {
184 $key = $prefix . '_is_multisite';
185 $this->job->$key = $masterdata_content->multisite->is_multisite;
186 $key = $prefix . '_is_multisite_main_site';
187 $this->job->$key = $masterdata_content->multisite->is_main_site;
188 $key = $prefix . '_defined_uploads_location';
189 $this->job->$key = $masterdata_content->multisite->defined_uploads_location;
190
191 if ($masterdata_content->multisite->is_multisite) {
192 $key = $prefix . '_main_blog_id';
193 $this->job->$key = $masterdata_content->multisite->main_blog_id;
194 $key = $prefix . '_current_blog_id';
195 $this->job->$key = $masterdata_content->multisite->current_blog_id;
196 $key = $prefix . '_blogs';
197 $this->job->$key = $masterdata_content->multisite->blogs;
198 $key = $prefix . '_default_super_admin_id';
199 $this->job->$key = $masterdata_content->multisite->default_super_admin_id;
200 $key = $prefix . '_default_super_admin_username';
201 $this->job->$key = $masterdata_content->multisite->default_super_admin_username;
202 }
203 }
204
205
206 /**
207 * DB details mapping
208 */
209 if (isset($masterdata_content->dbdetails)) {
210 $tmp_var = $prefix . '_dbmasterdata';
211 $this->job->$tmp_var = $masterdata_content->dbdetails;
212 }
213
214 /**
215 * Files data mapping
216 */
217 if (isset($masterdata_content->files)) {
218 $mappings = [
219 "_files_above_webroot_dir" => "files_above_webroot_dir",
220 "_files_home_dir" => "files_home_dir",
221 "_files_wp_content_dir" => "files_wp_content_dir",
222 "_files_wp_dir" => "files_wp_dir",
223 "_files_uploads_dir" => "files_uploads_dir",
224 "_files_plugins_dir" => "files_plugins_dir",
225 "_files_themes_dir" => "files_themes_dir",
226 "_files_plugin_list" => "files_plugin_list",
227 "_files_theme_list" => "files_theme_list",
228 ];
229
230 foreach ($mappings as $job_key => $masterdata_key) {
231 if (!isset($masterdata_content->files->$masterdata_key)) {
232 continue;
233 }
234 $tmp_var = $prefix . $job_key;
235 $this->job->$tmp_var = $masterdata_content->files->$masterdata_key;
236 }
237 }
238
239 /**
240 * Debug data mapping
241 */
242 if (isset($masterdata_content->debug)) {
243 $tmp_var = $prefix . '_debug';
244 $this->job->$tmp_var = $masterdata_content->debug;
245 }
246 }
247
248 /**
249 * Retrieve masterdata
250 */
251 public function retrieveMasterdata(Destination $destination, $slugs_to_retrieve = [])
252 {
253 // Get masterdata retrival object
254 $retrieval = new MasterdataRetrieval($destination);
255 $retrieval->setDataToRetrieve($slugs_to_retrieve);
256
257 $this->logger->log("DEBUG", "Calling masterdata service on: " . $destination->getFullURL() . " with intent to user as '" . $destination->getDestination() . "'");
258 $result = $retrieval->getMasterdata();
259
260 // Check for errors
261 if ($result) {
262 return $retrieval->data;
263 } else {
264 $errormsg = sprintf(__("Could not retrieve masterdata from target '%s', which means we can not continue the migration.", "wpsynchro"), $destination->getFullURL());
265 $this->job->errors[] = $errormsg;
266 $this->logger->log("CRITICAL", $errormsg);
267 return [];
268 }
269 }
270
271 /**
272 * Initialize some configurations based on the data retrieved from masterdata
273 */
274 public function inititializeConfigurations()
275 {
276 // Add system search/replaces
277 $this->job->db_search_replaces = array_merge($this->migration->searchreplaces, $this->job->db_system_search_replaces);
278
279 // Regenerate the search/replaces if set in the migration
280 if ($this->migration->searchreplaces_regenerate) {
281 $common_functions = new CommonFunctions();
282 $this->job->db_search_replaces = $common_functions->generateDefaultSearchReplace(
283 $this->job->from_client_home_url,
284 $this->job->to_client_home_url,
285 $this->job->from_files_home_dir,
286 $this->job->to_files_home_dir
287 );
288 }
289
290 // Ignore all search/replaces - Only used for testing purposes
291 if ($this->migration->ignore_all_search_replaces) {
292 $this->job->db_search_replaces = [];
293 }
294
295 // Clear duplicates from search/replaces
296 $this->job->db_search_replaces = $this->removeDuplicatesFromSearchReplaces($this->job->db_search_replaces);
297
298 // Remove tables from dbdata, if not all tables should be synced
299 if ($this->migration->include_all_database_tables === false) {
300 $onlyinclude = $this->migration->only_include_database_table_names;
301 $newdbdata = [];
302 foreach ($this->job->from_dbmasterdata as $table) {
303 if (in_array($table->name, $onlyinclude)) {
304 $newdbdata[] = $table;
305 }
306 }
307 $this->job->from_dbmasterdata = $newdbdata;
308 }
309
310 // Check if any of the tables are actually views, to be handled in a special way
311 $views = [];
312 $this->job->from_dbmasterdata = array_filter($this->job->from_dbmasterdata, function ($table_obj) use (&$views) {
313 if ($table_obj->is_view) {
314 $views[] = $table_obj;
315 return false;
316 }
317 return true;
318 });
319 $this->job->db_views_to_be_synced = $views;
320
321 // Check if tables have 0 rows, so we mark them complete
322 foreach ($this->job->from_dbmasterdata as &$table) {
323 if ($table->rows == 0) {
324 $table->is_completed = true;
325 }
326 }
327
328 // Remove any temporary tables
329 $this->job->from_dbmasterdata = array_filter($this->job->from_dbmasterdata, function ($table_obj) {
330 if (strpos($table_obj->name, DatabaseSync::TMP_TABLE_PREFIX) === 0) {
331 return false;
332 }
333 return true;
334 });
335
336 // Set max packet size we can send to SQL server at a time
337 $this->job->masterdata_max_sql_packet_bytes = min($this->job->from_max_allowed_packet_size, $this->job->to_max_allowed_packet_size);
338 // Set max size request data that can be POST'ed to a remote service, based on the lowest on both clients
339 $this->job->masterdata_max_tcp_request_bytes = min($this->job->from_max_post_size, $this->job->to_max_post_size) * 0.9;
340 // Set the memory limit, based on the lowest on both clients
341 $this->job->masterdata_max_memory_limit_bytes = round((min($this->job->from_memory_limit, $this->job->to_memory_limit) - memory_get_usage()) * 0.7);
342 }
343
344 /**
345 * Clear duplicates for search/replaces
346 */
347 public function removeDuplicatesFromSearchReplaces(array $search_replaces)
348 {
349 $new_search_replaces = [];
350 $search_replace_hashes = [];
351 foreach ($search_replaces as $search_replace) {
352 $hash = md5($search_replace->to . '-' . $search_replace->from);
353 if ($search_replace->to != $search_replace->from && !isset($search_replace_hashes[$hash])) {
354 $new_search_replaces[] = $search_replace;
355 }
356 $search_replace_hashes[$hash] = true;
357 }
358 return $new_search_replaces;
359 }
360
361 /**
362 * Find needed system search/replaces
363 */
364 public function findSystemSearchReplaces()
365 {
366
367 // Check for multisite and if we need any search/replaces
368 if ($this->job->from_is_multisite || $this->job->to_is_multisite) {
369 // Exclude WP Synchro on these locations also
370 $this->job->files_population_source_excludes[] = $this->job->from_files_uploads_dir . "/wpsynchro";
371 $this->job->files_population_target_excludes[] = $this->job->to_files_uploads_dir . "/wpsynchro";
372
373 // If both are multisites - So either main<>main or subsite<>subsite
374 if ($this->job->from_is_multisite && $this->job->to_is_multisite) {
375 }
376
377 // Multisite subsite to singlesize or other way around
378 if (($this->job->from_is_multisite && !$this->job->to_is_multisite) || (!$this->job->from_is_multisite && $this->job->to_is_multisite)) {
379 // If the source is a multisite and not the main site, exclude all other sites uploads folder
380 if ($this->job->from_is_multisite && !$this->job->from_is_multisite_main_site) {
381 $uploads_sites_folder_on_source = dirname(dirname(trailingslashit($this->job->from_files_uploads_dir))) . "/sites/";
382 foreach ($this->job->from_blogs as $blog) {
383 if ($blog->id != $this->job->from_current_blog_id) {
384 $this->job->files_population_source_excludes[] = $uploads_sites_folder_on_source . $blog->id;
385 }
386 }
387 }
388 }
389
390 // If migrating from multisite subsite to single site, make sure that UPLOADS constant is defined for single site.
391 if ($this->job->from_is_multisite && !$this->job->to_is_multisite) {
392 $relative_uploads_dir = trim(str_replace($this->job->to_files_home_dir, "", $this->job->to_files_uploads_dir), "/");
393 $define_uploads_dir = "DEFINE('UPLOADS','" . $relative_uploads_dir . "');";
394 if ($this->job->to_defined_uploads_location != $relative_uploads_dir) {
395 $this->job->finalize_success_messages_frontend[] = sprintf(__("If you are moving uploads directory (media), you must add the following line to your wp-config.php on the site %s : <code>%s</code> - Make sure to add this line before loading WordPress in the bottom of wp-config.php", "wpsynchro"), untrailingslashit($this->job->to_client_home_url), $define_uploads_dir);
396 }
397 $this->job->finalize_success_messages_frontend[] = __("All users from multisite is moved - Make sure to check and remove unwanted users on single site.", "wpsynchro");
398 }
399 // If migrating from single site to multisite subsite
400 if (!$this->job->from_is_multisite && $this->job->to_is_multisite) {
401 $this->job->finalize_success_messages_frontend[] = __("Users from source single site was merged with the users on the multisite. Make sure to check that the users are as expected.", "wpsynchro");
402 }
403 }
404 }
405 }
406