PluginProbe
YayMail – WooCommerce Email Customizer / 4.4.7
YayMail – WooCommerce Email Customizer v4.4.7
4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 trunk 1.9.6 2.1.4 2.1.5 3.2.2 3.2.6 3.2.7.1 3.2.8.1 3.2.9 3.3 3.3.1 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 3.3.9 3.4 All 58 releases
yaymail / src / Models / MigrationModel.php

MigrationModel.php in YayMail – WooCommerce Email Customizer 4.4.7, at src/Models/MigrationModel.php

303 lines 12.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace YayMail\Models;
3
4 use YayMail\Migrations\AbstractMigration;
5 use YayMail\Utils\SafeUnserializer;
6 use YayMail\Utils\SingletonTrait;
7 use YayMail\Utils\Logger;
8 use YayMail\Migrations\MainMigration;
9 use YayMail\Migrations\MigrationHelper;
10
11 /**
12 * Migration Model
13 *
14 * @method static MigrationModel get_instance()
15 */
16 class MigrationModel {
17 use SingletonTrait;
18
19 /** @var Logger */
20 private $logger;
21
22 private function __construct() {
23 $this->logger = new Logger();
24 }
25
26 /**
27 * Decode internal backup options for the listing UI (read-only).
28 *
29 * @param mixed $value The value to unserialize.
30 * @return mixed
31 */
32 private function safe_maybe_unserialize( $value ) {
33 $decoded = SafeUnserializer::decode( $value );
34
35 return is_object( $decoded ) ? $value : $decoded;
36 }
37
38 public function get_onload_data() {
39 return [
40 'required_migrations' => $this->get_required_migration_names(),
41 'backups' => $this->get_backups(),
42 ];
43 }
44
45 public function migrate() {
46 $is_migration_succeeded = MainMigration::get_instance()->migrate();
47 if ( ! $is_migration_succeeded ) {
48 throw new \Exception( esc_html__( 'Migration failed, please contact our Customer Support for help!', 'yaymail' ), 500 );
49 }
50 return [ 'is_critical_migration_required' => $this->check_if_critical_migration_required() ];
51 }
52
53 /**
54 * Restore backup to a specific version
55 *
56 * @param array $backup Backup data containing posts, postmeta, options, metadata, and version.
57 *
58 * @return array Returns array with 'is_critical_migration_required' boolean flag.
59 * @throws \Exception When backup doesn't exist or restoration fails.
60 */
61 public function reset( $backup ) {
62 global $wpdb;
63
64 if ( ! $backup || ! $backup['version'] || ! $backup['name'] ) {
65 throw new \Exception( 'Back up does not exist', 500 );
66 }
67
68 try {
69 $backup_name = $backup['name'];
70 $version = $backup['version'];
71
72 $wpdb->query( 'START TRANSACTION' );
73 $this->logger->log( '***** Start reset transaction ****' );
74 $this->logger->log( "Backup name: $backup_name" );
75
76 // Get IDs of posts to be deleted
77 $post_ids_to_delete = $wpdb->get_col(
78 $wpdb->prepare(
79 "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s",
80 'yaymail_template'
81 )
82 );
83 if ( $post_ids_to_delete ) {
84 // Delete current post meta data
85 $delete_postmeta_query = sprintf(
86 "DELETE FROM {$wpdb->postmeta} WHERE post_id IN (%s)",
87 implode( ',', array_map( 'intval', $post_ids_to_delete ) )
88 );
89 $this->logger->log( $delete_postmeta_query );
90 $wpdb->query( $delete_postmeta_query );// phpcs:ignore
91
92 // Delete the posts
93 $delete_template_posts_query = sprintf(
94 "DELETE FROM {$wpdb->posts} WHERE ID IN (%s)",
95 implode( ',', array_map( 'intval', $post_ids_to_delete ) )
96 );
97 $this->logger->log( $delete_template_posts_query );
98 $wpdb->query( $delete_template_posts_query );// phpcs:ignore
99 }
100
101 $delete_template_posts_query = $wpdb->prepare(
102 "DELETE FROM {$wpdb->posts} WHERE post_type = %s",
103 'yaymail_template'
104 );
105 $this->logger->log( $delete_template_posts_query );
106 $wpdb->query( $delete_template_posts_query );// phpcs:ignore
107
108 // Restore the backed-up posts
109 $backed_up_posts = $backup['posts'];
110 $backed_up_postmeta = $backup['postmeta'];
111 foreach ( $backed_up_posts as $post ) {
112 if ( ! isset( $post->post_type ) || 'yaymail_template' !== $post->post_type ) {
113 continue;
114 }
115 $wpdb->insert(
116 $wpdb->posts,
117 [
118 'post_author' => $post->post_author,
119 'post_date' => $post->post_date,
120 'post_date_gmt' => $post->post_date_gmt,
121 'post_content' => $post->post_content,
122 'post_title' => $post->post_title,
123 'post_excerpt' => $post->post_excerpt,
124 'post_status' => $post->post_status,
125 'comment_status' => $post->comment_status,
126 'ping_status' => $post->ping_status,
127 'post_password' => $post->post_password,
128 'post_name' => $post->post_name,
129 'to_ping' => $post->to_ping,
130 'pinged' => $post->pinged,
131 'post_modified' => $post->post_modified,
132 'post_modified_gmt' => $post->post_modified_gmt,
133 'post_content_filtered' => $post->post_content_filtered,
134 'post_parent' => $post->post_parent,
135 'guid' => $post->guid,
136 'menu_order' => $post->menu_order,
137 'post_type' => $post->post_type,
138 'post_mime_type' => $post->post_mime_type,
139 'comment_count' => $post->comment_count,
140 ]
141 );
142
143 // Insert post meta data
144 $inserted_post_id = $wpdb->insert_id;
145 $postmetas_for_current_post = array_filter(
146 $backed_up_postmeta,
147 function( $postmeta ) use ( $post ) {
148 return $postmeta->post_id === $post->ID;
149 }
150 );
151 if ( ! empty( $postmetas_for_current_post ) ) {
152 foreach ( $postmetas_for_current_post as $postmeta ) {
153 if ( ! isset( $postmeta->meta_key ) || strpos( (string) $postmeta->meta_key, 'yaymail' ) === false ) {
154 continue;
155 }
156 add_post_meta(
157 $inserted_post_id,
158 $postmeta->meta_key,
159 SafeUnserializer::decode_strict( $postmeta->meta_value )
160 );
161 }
162 }
163 }//end foreach
164
165 // Restore backed-up options
166 foreach ( $backup['options'] as $option ) {
167 // Only restore options that belong to YayMail (same pattern as export)
168 if ( strpos( $option->option_name, 'yaymail' ) !== false ) {
169 update_option( $option->option_name, SafeUnserializer::decode_strict( $option->option_value ) );
170 }
171 }
172
173 // Remove the succeeded migration log from db
174 $successful_migrations = get_option( AbstractMigration::SUCCESSFUL_MIGRATIONS, null );
175 $removed_migrations = [];
176 if ( ! empty( $successful_migrations ) ) {
177 $filtered_successful_migrations = array_filter(
178 $successful_migrations,
179 function( $migration ) use ( $version, $backup ) {
180 $backup_date = new \DateTime( $backup['created_date'], wp_timezone() );
181 $migration_date = new \DateTime( $migration['created_date'], wp_timezone() );
182 $should_remove = $migration_date >= $backup_date;
183
184 return ! $should_remove;
185 }
186 );
187
188 update_option( AbstractMigration::SUCCESSFUL_MIGRATIONS, $filtered_successful_migrations );
189 $removed_migrations = array_udiff(
190 $successful_migrations,
191 $filtered_successful_migrations,
192 function( $a, $b ) {
193 return strcmp( serialize( $a ), serialize( $b ) );
194 }
195 );
196 }//end if
197
198 // Restore core version
199 update_option( 'yaymail_version', $version );
200 wp_cache_delete( 'yaymail_version', 'options' );
201
202 // Restore addon versions
203 $addon_versions = get_option( 'yaymail_addon_versions', [] );
204 $restored_addon_versions = array_filter(
205 $addon_versions,
206 function( $addon_version, $addon_namespace ) use ( $removed_migrations ) {
207 foreach ( $removed_migrations as $migration ) {
208 if ( ! empty( $migration['addon_namespace'] ) && $migration['addon_namespace'] === $addon_namespace && version_compare( $addon_version, $migration['to_version'], '<=' ) ) {
209 return false;
210 }
211 }
212 return true;
213 },
214 ARRAY_FILTER_USE_BOTH
215 );
216 update_option( 'yaymail_addon_versions', $restored_addon_versions );
217
218 do_action( 'yaymail_before_reset_migration_commit', $backup, $removed_migrations );
219
220 $wpdb->query( 'COMMIT' );
221 $this->logger->log( '***** Finish reset transaction ****' );
222 $this->logger->log( "$backup_name restored successfully!" );
223
224 return [ 'is_critical_migration_required' => $this->check_if_critical_migration_required() ];
225
226 } catch ( \Exception $e ) {
227 $wpdb->query( 'ROLLBACK' );
228 $this->logger->log( '***** Transaction rolled back. ' . $e->getMessage() );
229 throw new \Exception( esc_html__( 'Restoration failed', 'yaymail' ), 500 );
230 }//end try
231 }
232
233 public function check_if_critical_migration_required() {
234 $old_version = get_option( 'yaymail_version' );
235 if ( $old_version ) {
236 return version_compare( $old_version, '4.0.0', '<' );
237 }
238 return false;
239 }
240
241 private function get_required_migration_names() {
242 $old_version = MigrationHelper::format_version_number( get_option( 'yaymail_version' ) );
243 $new_version = MigrationHelper::format_version_number( yaymail_version() );
244 $successful_migrations = get_option( AbstractMigration::SUCCESSFUL_MIGRATIONS, [] );
245
246 // If freshly installed or already on latest version with no migrations needed
247 if ( ( version_compare( $old_version, $new_version, '>=' ) && empty( $successful_migrations ) ) ) {
248 return apply_filters( 'yaymail_required_migration_names', [] );
249 }
250
251 $args = [
252 'post_type' => 'yaymail_template',
253 'post_status' => 'any',
254 'posts_per_page' => -1,
255 ];
256
257 $query = new \WP_Query( $args );
258
259 $has_yaymail_template = $query->have_posts();
260 if ( ( $old_version == null && ! $has_yaymail_template ) && empty( $successful_migrations ) ) {
261 return [];
262 }
263
264 // Check if any migrations have already been run up to current version
265 foreach ( $successful_migrations as $migration ) {
266 if ( empty( $migration['addon_namespace'] ) && version_compare( $migration['to_version'], yaymail_version(), '>=' ) ) {
267 return apply_filters( 'yaymail_required_migration_names', [] );
268 }
269 }
270
271 // Get available migrations that need to be run
272 $core_migrations = MainMigration::CORE_MIGRATIONS;
273 $filtered_migrations = MigrationHelper::filter_migrations( $core_migrations, $old_version, $new_version );
274
275 $needed_migrations = empty( $filtered_migrations ) ? [] : [ 'YayMail' ];
276
277 return apply_filters( 'yaymail_required_migration_names', $needed_migrations );
278 }
279
280 private function get_backups() {
281 global $wpdb;
282
283 $backup_prefix = AbstractMigration::BACKUP_PREFIX;
284
285 $backup_options_query = $wpdb->prepare(
286 "SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name LIKE %s",
287 $backup_prefix . '%'
288 );
289 $backups_result = $wpdb->get_results( $backup_options_query );// phpcs:ignore
290
291 $backups = [];
292 foreach ( $backups_result as $backup ) {
293 $data = $this->safe_maybe_unserialize( $backup->option_value );
294 $backups[] = [
295 'created_date' => $data['created_date'] ?? 'created_date not found',
296 'name' => $backup->option_name ?? 'backup name not found',
297 ];
298 }
299
300 return $backups;
301 }
302 }
303