PluginProbe ʕ •ᴥ•ʔ
Hustle – Email Marketing, Lead Generation, Optins, Popups / 7.8.9
Hustle – Email Marketing, Lead Generation, Optins, Popups v7.8.9
7.8.13 7.8.13.1 trunk 3.0 3.1 3.1.1 3.1.2 3.1.3 3.1.4 4.3.2 4.4.4 4.4.5 4.4.5.1 4.4.5.4 4.6 4.6.1.1 4.6.1.4 4.7.0.2 4.7.0.3 4.7.0.7 4.7.0.9 4.7.1.0 4.7.1.1 4.8.0.0 5.0.0 5.0.1 5.0.1.1 5.0.1.2 5.1 5.1.1 5.1.2 5.1.3 5.1.3.1 5.1.3.2 5.1.4 5.1.5 6.0 6.0.1 6.0.2 6.0.3 6.0.4.2 6.0.5 6.0.6.1 6.0.7 6.0.8.1 6.0.9 7.0.0.1 7.0.2 7.0.3 7.0.4 7.1.0 7.1.1 7.2.0 7.2.1 7.3.0 7.3.1 7.3.3 7.3.5 7.3.6 7.3.7 7.4.0 7.4.1 7.4.11 7.4.13 7.4.13.1 7.4.2 7.4.3 7.4.4 7.4.5 7.4.5.1 7.4.5.2 7.4.6 7.4.7 7.5.0 7.6.0 7.6.1 7.6.3 7.6.4 7.6.6 7.7.0 7.7.1 7.8.0 7.8.1 7.8.10 7.8.10.1 7.8.10.2 7.8.11 7.8.12 7.8.12.1 7.8.2 7.8.3 7.8.4 7.8.5 7.8.6 7.8.7 7.8.8 7.8.9 7.8.9.1 7.8.9.2 7.8.9.3
wordpress-popup / inc / hustle-migration.php
wordpress-popup / inc Last commit date
display-conditions 3 years ago front 9 months ago helpers 9 months ago metas 3 years ago multisite 3 years ago palettes 3 years ago provider 3 years ago providers 9 months ago templates 3 years ago update 3 years ago class-hustle-admin-page-abstract.php 9 months ago class-hustle-black-friday-campaign.php 7 months ago class-hustle-condition-factory.php 6 years ago class-hustle-cross-sell.php 9 months ago class-hustle-dashboard-admin.php 3 years ago class-hustle-data.php 3 years ago class-hustle-db.php 2 years ago class-hustle-installer.php 4 years ago class-hustle-meta.php 3 years ago class-hustle-module-admin.php 9 months ago class-hustle-module-collection.php 3 years ago class-hustle-module-page-abstract.php 2 years ago class-hustle-notifications.php 3 years ago class-hustle-settings-admin.php 3 years ago class-hustle-tutorials-page.php 4 years ago class-hustle-wp-dashboard-page.php 3 years ago hustle-deletion.php 3 years ago hustle-embedded-admin.php 3 years ago hustle-entries-admin.php 3 years ago hustle-entry-model.php 3 years ago hustle-general-data-protection.php 3 years ago hustle-init.php 7 months ago hustle-mail.php 3 years ago hustle-migration.php 3 years ago hustle-model.php 3 years ago hustle-module-model.php 9 months ago hustle-module-widget-legacy.php 3 years ago hustle-module-widget.php 3 years ago hustle-modules-common-admin-ajax.php 9 months ago hustle-popup-admin.php 3 years ago hustle-providers-admin.php 3 years ago hustle-providers.php 3 years ago hustle-settings-admin-ajax.php 3 years ago hustle-settings-page.php 3 years ago hustle-slidein-admin.php 3 years ago hustle-sshare-admin.php 3 years ago hustle-sshare-model.php 3 years ago hustle-tracking-model.php 3 years ago opt-in-geo.php 9 months ago opt-in-utils.php 3 years ago opt-in-wpmudev-api.php 3 years ago
hustle-migration.php
1669 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Hustle_Migration
4 *
5 * @package Hustle
6 */
7
8 /**
9 * Class Hustle_Migration
10 *
11 * @class Hustle_Migration
12 */
13 class Hustle_Migration {
14
15 /**
16 * Is multisite
17 *
18 * @var bool
19 */
20 private $is_multisite = false;
21
22 /**
23 * Instance of Hustle_410_Migration.
24 *
25 * @since 4.1.0
26 * @var Hustle_410_Migration
27 */
28 public $migration_410;
29
30 /**
31 * Instance of Hustle_430_Migration.
32 *
33 * @since 4.3.0
34 * @var Hustle_430_Migration
35 */
36 private $migration_430;
37
38 /**
39 * Hustle_Migration instance.
40 *
41 * @since 4.1.0
42 * @var null
43 */
44 private static $instance = null;
45
46 /**
47 * Whether any of the modules had custom css.
48 *
49 * @since 4.0
50 * @var boolean
51 */
52 private $custom_css_migrated = false;
53
54 /**
55 * Tracking meta keys
56 *
57 * @var array
58 */
59 private static $tracking_meta_keys = array(
60 'popup_view',
61 'popup_conversion',
62 'slidein_view',
63 'slidein_conversion',
64 'after_content_view',
65 'shortcode_view',
66 'floating_social_view',
67 'floating_social_conversion',
68 'widget_view',
69 'after_content_conversion',
70 'shortcode_conversion',
71 'widget_conversion',
72 'subscription',
73 );
74
75 /**
76 * Get an istance of this class.
77 *
78 * @since 4.1.0
79 */
80 public static function get_instance() {
81 if ( is_null( self::$instance ) ) {
82 self::$instance = new self();
83 }
84 return self::$instance;
85 }
86
87 /**
88 * Constructor
89 */
90 public function __construct() {
91
92 $this->is_multisite = is_multisite();
93
94 add_action( 'wp_ajax_hustle_migrate_tracking', array( $this, 'migrate_tracking_and_subscriptions' ) );
95
96 if ( $this->is_migration() ) {
97 add_action( 'init', array( $this, 'do_hustle_30_migration' ) );
98 }
99
100 $this->migration_410 = new Hustle_410_Migration();
101
102 $this->migration_430 = new Hustle_430_Migration();
103 }
104
105 /**
106 * Check whether we should run da migration.
107 *
108 * @since 4.0
109 * @return boolean
110 */
111 private function is_migration() {
112
113 // If migration is being forced, do it.
114 if ( filter_input( INPUT_GET, 'reset_migration', FILTER_VALIDATE_BOOLEAN ) ) {
115 return true;
116 }
117
118 // If migration was already done, skip.
119 if ( self::is_migrated( 'hustle_30_migrated' ) ) {
120 return false;
121 }
122
123 // If it's a fresh install, no need to migrate.
124 return self::did_hustle_exist();
125 }
126
127 /**
128 * Get the previously installed version according to our flag.
129 *
130 * @since 4.2.1
131 *
132 * @return string|false
133 */
134 public static function get_previous_installed_version() {
135 return get_site_option( 'hustle_previous_version', false );
136 }
137
138 /**
139 * Check if a spesific migration is passed
140 *
141 * @param string $key Migration key.
142 * @return bool
143 */
144 public static function is_migrated( $key ) {
145 $keys = get_option( 'hustle_migrations', null );
146 if ( is_null( $keys ) ) {
147 self::change_migration_options();
148 $keys = get_option( 'hustle_migrations', array() );
149 }
150
151 return in_array( $key, $keys, true );
152 }
153
154 /**
155 * Save migration key
156 *
157 * @param string $key Migration key.
158 */
159 public static function migration_passed( $key ) {
160 $keys = get_option( 'hustle_migrations', array() );
161 if ( ! in_array( $key, $keys, true ) ) {
162 $keys[] = $key;
163 update_option( 'hustle_migrations', $keys );
164 }
165 }
166
167 /**
168 * Remove the passed migration flag.
169 *
170 * @since 4.1.0
171 *
172 * @param string $flag Flag name.
173 */
174 public static function remove_migration_passed_flag( $flag ) {
175 $keys = get_option( 'hustle_migrations', array() );
176 if ( in_array( $flag, $keys, true ) ) {
177 $key = array_search( $flag, $keys, true );
178
179 if ( false !== $key ) {
180 unset( $keys[ $key ] );
181 update_option( 'hustle_migrations', $keys );
182 }
183 }
184 }
185
186 /**
187 * Resave migration keys to a new format
188 */
189 private static function change_migration_options() {
190 $keys = array(
191 'hustle_20_migrated',
192 'hustle_30_migrated',
193 'hustle_30_tracking_migrated',
194 );
195
196 foreach ( $keys as $key ) {
197 $option = get_option( $key );
198 if ( $option ) {
199 self::migration_passed( $key );
200 delete_option( $key );
201 }
202 }
203 }
204
205 /**
206 * Check whether the tracking and subscriptions data needs to be migrated.
207 *
208 * @since 4.0
209 * @return bool
210 */
211 public static function check_tracking_needs_migration() {
212
213 // If migration was already done, skip.
214 if ( self::is_migrated( 'hustle_30_tracking_migrated' ) ) {
215 return false;
216 }
217
218 // If it's a fresh install, no need to migrate.
219 if ( ! self::did_hustle_exist() ) {
220 return false;
221 }
222
223 // If there isn't data to migrate, we're done.
224 return self::is_tracking_subscription_data_to_migrate();
225 }
226
227 /**
228 * Check whether this is a new 4.0 installation.
229 *
230 * @since 4.0
231 * @return bool
232 */
233 public static function did_hustle_exist() {
234 $hustle_20_migrated = self::is_migrated( 'hustle_20_migrated' );
235
236 return $hustle_20_migrated;
237 }
238
239 /**
240 * Migrating from Hustle 3.x
241 */
242 public function do_hustle_30_migration() {
243
244 // Update tables on migration.
245 Hustle_Db::maybe_create_tables( true );
246
247 // Migrate global settings.
248 $this->migrate_settings();
249
250 $modules = $this->get_all_hustle_modules();
251 if ( ! empty( $modules ) ) {
252 array_map( array( __CLASS__, 'migrate_hustle_30' ), $modules );
253 }
254
255 if ( ! $this->custom_css_migrated ) {
256 Hustle_Notifications::add_dismissed_notification( 'show_review_css_after_migration_notice' );
257 }
258
259 self::migration_passed( 'hustle_30_migrated' );
260 }
261
262 /**
263 * Migrate hustle 3.0
264 *
265 * @param object $old_module Old module.
266 */
267 public function migrate_hustle_30( $old_module ) {
268
269 // Don't migrate the modules that don't belong to the blog requesting the migration (useful on MU).
270 if ( get_current_blog_id() !== (int) $old_module->blog_id ) {
271 return;
272 }
273
274 if ( Hustle_Module_Model::SOCIAL_SHARING_MODULE !== $old_module->module_type ) {
275 $this->migrate_non_sshare_module( $old_module );
276 } else {
277 $this->migrate_sshare_module( $old_module );
278 }
279
280 }
281
282 /**
283 * Get all hustle modules
284 *
285 * @return array
286 */
287 public function get_all_hustle_modules() {
288 $module_collection_instance = Hustle_Module_Collection::instance();
289 return $module_collection_instance->get_hustle_30_modules( get_current_blog_id() );
290 }
291
292 /**
293 * Migrate Social Sharing module
294 *
295 * @param object $old_module Old module.
296 */
297 private function migrate_sshare_module( $old_module ) {
298
299 if ( ! $this->is_multisite || is_main_site( get_current_blog_id() ) ) {
300 $module = new Hustle_SShare_Model( $old_module->module_id );
301 $module->save();
302
303 } else {
304
305 // The tables in multisite are no longer shared between the sites of the network.
306 // Instead, each site has its own tables, so they're empty and we should move the content there.
307 $module = new Hustle_SShare_Model();
308
309 $module->module_id = $old_module->module_id;
310 $module->active = $old_module->active;
311 $module->module_name = $old_module->module_name;
312 $module->module_type = $old_module->module_type;
313 $module->save_from_migration();
314
315 // Shortcode.
316 $module->update_meta( Hustle_Module_Model::KEY_SHORTCODE_ID, $old_module->meta['shortcode_id'] );
317
318 // Track types.
319 if ( isset( $old_module->meta['track_types'] ) ) {
320
321 // Change 'floating_social' track type to 4.0 'floating' one.
322 if ( isset( $old_module->meta['track_types']['floating_social'] ) ) {
323 $old_module->meta['track_types']['floating'] = $old_module->meta['track_types']['floating_social'];
324 unset( $old_module->meta['track_types']['floating_social'] );
325 }
326
327 $module->update_meta( Hustle_Module_Model::TRACK_TYPES, $old_module->meta['track_types'] );
328 }
329 }
330
331 // Services.
332 $content = $this->parse_sshare_content_meta( $module, $old_module );
333
334 // Display.
335 $display = $this->parse_sshare_display_meta( $module, $old_module );
336
337 // Appearance.
338 $design = $this->parse_sshare_design_meta( $module, $old_module );
339
340 // Visibility.
341 $visibility = $this->parse_visibility_meta( $module, $old_module );
342
343 // Edit roles.
344 $edit_roles = ! is_null( get_role( 'administrator' ) ) ? array( 'administrator' ) : array();
345
346 $data = array(
347 'id' => $module->id,
348 'content' => $content,
349 'design' => $design,
350 'display' => $display,
351 'visibility' => $visibility,
352 'edit_roles' => $edit_roles,
353 );
354
355 $module->update_module( $data );
356 }
357
358 /**
359 * Parse the old content to the new format.
360 *
361 * @since 4.0
362 *
363 * @param Hustle_SShare_Model $module Module.
364 * @param object $old_module Old module.
365 * @return array
366 */
367 private function parse_sshare_content_meta( $module, $old_module ) {
368 $content = $module->get_content()->to_array();
369
370 if ( $this->is_multisite ) {
371 $content = array_merge( $content, $old_module->meta['content'] );
372 }
373
374 if ( 'native' !== $content['service_type'] || 'none' === $content['click_counter'] || '0' === $content['click_counter'] ) {
375 $content['counter_enabled'] = '0';
376 } else {
377 $content['counter_enabled'] = '1';
378 }
379
380 if ( isset( $content['social_icons'] ) && ! empty( $content['social_icons'] ) ) {
381
382 if ( isset( $content['social_icons']['google'] ) ) {
383 unset( $content['social_icons']['google'] );
384 }
385
386 $platforms_with_counter_endpoint = Hustle_SShare_Model::get_networks_counter_endpoint();
387
388 $social_platforms = Hustle_SShare_Model::get_social_platform_names();
389
390 foreach ( $content['social_icons'] as $platform => $data ) {
391
392 if ( 'native' === $content['click_counter'] ) {
393 // Set to 'native' only if the platform has a native counter.
394 $counter_type = in_array( $platform, $platforms_with_counter_endpoint, true ) ? 'native' : 'click';
395 } else {
396 // Applies for both 'click', '0', and 'none' click_counter.
397 $counter_type = 'click';
398 }
399 $data['platform'] = $platform;
400 $data['type'] = $counter_type;
401 $data['label'] = ! empty( $social_platforms[ $platform ] ) ? $social_platforms[ $platform ] : ucfirst( $platform );
402 $content['social_icons'][ $platform ] = $data;
403 }
404 }
405
406 return $content;
407 }
408
409 /**
410 * Parse the old design to the new one.
411 *
412 * @since 4.0
413 *
414 * @param Hustle_SShare_Model $module Module.
415 * @param object $old_module Old module.
416 * @return array
417 */
418 private function parse_sshare_design_meta( $module, $old_module ) {
419
420 $design = $module->get_design()->to_array();
421 $old_design = $old_module->meta['design'];
422
423 if ( $this->is_multisite ) {
424 $design = array_merge( $design, $old_module->meta['design'] );
425 }
426
427 $design['floating_customize_colors'] = $this->is_true( $old_design['customize_colors'] ) ? '1' : '0';
428 $design['floating_icon_bg_color'] = $old_design['icon_bg_color'];
429 $design['floating_icon_color'] = $old_design['icon_color'];
430 $design['floating_bg_color'] = $old_design['floating_social_bg'];
431 $design['floating_animate_icons'] = $this->is_true( $old_design['floating_social_animate_icons'] ) ? '1' : '0';
432 $design['floating_drop_shadow'] = $this->is_true( $old_design['drop_shadow'] ) ? '1' : '0';
433 $design['floating_drop_shadow_x'] = $old_design['drop_shadow_x'];
434 $design['floating_drop_shadow_y'] = $old_design['drop_shadow_y'];
435 $design['floating_drop_shadow_blur'] = $old_design['drop_shadow_blur'];
436 $design['floating_drop_shadow_spread'] = $old_design['drop_shadow_spread'];
437 $design['floating_drop_shadow_color'] = $old_design['drop_shadow_color'];
438 $design['floating_inline_count'] = $this->is_true( $old_design['floating_inline_count'] ) ? '1' : '0';
439
440 $design['widget_customize_colors'] = $this->is_true( $old_design['customize_widget_colors'] ) ? '1' : '0';
441
442 // Same keys, making sure the value type is correct. String '1'|'0'.
443 $design['widget_animate_icons'] = $this->is_true( $old_design['widget_animate_icons'] ) ? '1' : '0';
444 $design['widget_drop_shadow'] = $this->is_true( $old_design['widget_drop_shadow'] ) ? '1' : '0';
445 $design['widget_inline_count'] = $this->is_true( $old_design['widget_inline_count'] ) ? '1' : '0';
446
447 return $design;
448 }
449
450 /**
451 * Parse ssharing specific display settings.
452 *
453 * @since 4.0
454 *
455 * @param Hustle_SShare_Model $module Module.
456 * @param object $old_module Old module.
457 * @return array
458 */
459 private function parse_sshare_display_meta( $module, $old_module ) {
460
461 $display = $this->parse_display_meta( $module, $old_module );
462 $old_settings = $old_module->meta['settings'];
463
464 $test_types = isset( $old_module->meta['test_types'] ) ? $old_module->meta['test_types'] : array();
465
466 if ( ! $this->is_true( $old_settings['floating_social_enabled'] ) ) {
467 $display['float_desktop_enabled'] = '0';
468 $display['float_mobile_enabled'] = '0';
469
470 } elseif ( isset( $test_types['floating_social'] ) && $this->is_true( $test_types['floating_social'] ) ) {
471 $display['float_desktop_enabled'] = '0';
472 $display['float_mobile_enabled'] = '0';
473 }
474
475 // We didn't differentiate 'mobile' and 'desktop' floating in 3.x,
476 // so the old settings apply to both.
477
478 // We're removing old 'content' location since it never worked.
479 $location_type = 'selector' === $old_settings['location_type'] ? 'css_selector' : 'screen';
480 $display['float_desktop_offset'] = $location_type;
481 $display['float_mobile_offset'] = $location_type;
482
483 $display['float_desktop_css_selector'] = $old_settings['location_target'];
484 $display['float_mobile_css_selector'] = $old_settings['location_target'];
485
486 $display['float_desktop_position'] = $old_settings['location_align_x'];
487 $display['float_mobile_position'] = $old_settings['location_align_x'];
488
489 $display['float_desktop_position_y'] = $old_settings['location_align_y'];
490 $display['float_mobile_position_y'] = $old_settings['location_align_y'];
491
492 $offset_y = 'top' === $old_settings['location_align_y'] ? $old_settings['location_top'] : $old_settings['location_bottom'];
493 $display['float_desktop_offset_y'] = $offset_y;
494 $display['float_mobile_offset_y'] = $offset_y;
495
496 $offset_x = 'right' === $old_settings['location_align_x'] ? $old_settings['location_right'] : $old_settings['location_left'];
497 $display['float_desktop_offset_x'] = $offset_x;
498 $display['float_mobile_offset_x'] = $offset_x;
499
500 return $display;
501 }
502
503 /**
504 * Migrate the modules that are popups, slideins and embedded.
505 *
506 * @since 4.0
507 * @param object $old_module Old module.
508 */
509 private function migrate_non_sshare_module( $old_module ) {
510
511 if ( ! $this->is_multisite || is_main_site( get_current_blog_id() ) ) {
512 $module = new Hustle_Module_Model( $old_module->module_id );
513
514 // Modules with 'test mode' enabled should be drafts.
515 if ( $this->is_true( $old_module->test_mode ) ) {
516 $module->active = '0';
517 }
518
519 // Add the new 'module_mode' property.
520 $module->module_mode = $this->get_module_mode( $old_module->meta['content'] );
521 $module->save();
522
523 } else {
524
525 // The tables in multisite are no longer shared between the sites of the network.
526 // Instead, each site has its own tables, so they're empty and we should move the content there.
527 $module = new Hustle_Module_Model();
528
529 // Modules with 'test mode' enabled should be drafts.
530 $module->active = ! $this->is_true( $old_module->test_mode ) ? $old_module->active : '0';
531
532 $module->module_id = $old_module->module_id;
533 $module->module_name = $old_module->module_name;
534 $module->module_type = $old_module->module_type;
535 $module->module_mode = $this->get_module_mode( $old_module->meta['content'] );
536 $module->save_from_migration();
537
538 // Shortcode.
539 $module->update_meta( Hustle_Module_Model::KEY_SHORTCODE_ID, $old_module->meta['shortcode_id'] );
540
541 // Track types.
542 if ( isset( $old_module->meta['track_types'] ) && ! empty( $old_module->meta['track_types'] ) ) {
543
544 // Change 'after_content' track type to 4.0 'inline' one.
545 if ( isset( $old_module->meta['track_types']['after_content'] ) ) {
546 $old_module->meta['track_types']['inline'] = $old_module->meta['track_types']['after_content'];
547 unset( $old_module->meta['track_types']['after_content'] );
548 }
549 $module->update_meta( Hustle_Module_Model::TRACK_TYPES, $old_module->meta['track_types'] );
550 }
551 }
552
553 // Handling metas.
554 // Content.
555 $content = $this->parse_content_meta( $module, $old_module );
556
557 // Emails.
558 $emails = $this->parse_email_meta( $module, $old_module );
559
560 // Integrations. For 'optins' only.
561 $integrations_settings = array();
562 if ( 'optin' === $module->module_mode ) {
563 $integrations_settings = $this->migrate_integrations( $module, $old_module );
564 }
565
566 // Appearance.
567 $design = $this->parse_design_meta( $module, $old_module );
568
569 // Display options. For Embedded modules only.
570 if ( Hustle_Module_Model::EMBEDDED_MODULE === $old_module->module_type ) {
571 $display = $this->parse_display_meta( $module, $old_module );
572 } else {
573 $display = array();
574 }
575
576 // Visibility.
577 $visibility = $this->parse_visibility_meta( $module, $old_module );
578
579 // Behavior.
580 $settings = $this->parse_settings_meta( $module, $old_module );
581
582 // Edit roles.
583 $edit_roles = ! is_null( get_role( 'administrator' ) ) ? array( 'administrator' ) : array();
584
585 $data = array(
586 'id' => $module->id,
587 'content' => $content,
588 'emails' => $emails,
589 'design' => $design,
590 'integrations_settings' => $integrations_settings,
591 'display' => $display,
592 'visibility' => $visibility,
593 'settings' => $settings,
594 'edit_roles' => $edit_roles,
595 );
596
597 $module->update_module( $data );
598
599 }
600
601 /**
602 * Get the module's mode according to the old content.
603 * 'optin' if email collection was enabled, 'informational' otherwise.
604 * Empty string for Social sharing modules that doesn't have email collection.
605 *
606 * @since 4.0
607 *
608 * @param array $content Content.
609 * @return string
610 */
611 private function get_module_mode( $content ) {
612 $mode = 'informational';
613 if ( isset( $content['use_email_collection'] ) && $this->is_true( $content['use_email_collection'] ) ) {
614 $mode = 'optin';
615 }
616
617 return $mode;
618 }
619
620 /**
621 * Create the new 'content' settings according to the old module.
622 * The old data is used to replace the defaults.
623 * The old unused data is not being removed atm.
624 *
625 * @since 4.0
626 *
627 * @param Hustle_Module_Model $module Module.
628 * @param object $old_module Old module.
629 * @return array
630 */
631 private function parse_content_meta( $module, $old_module ) {
632 $content = $module->get_content()->to_array();
633
634 if ( $this->is_multisite ) {
635 $content = array_merge( $content, $old_module->meta['content'] );
636 }
637
638 if ( ! $this->is_true( $content['has_title'] ) ) {
639 $content['title'] = '';
640 $content['sub_title'] = '';
641 }
642
643 if ( ! $this->is_true( $content['use_feature_image'] ) ) {
644 $content['feature_image'] = '';
645 }
646
647 $content['show_cta'] = $this->is_true( $content['show_cta'] ) ? '1' : '0';
648
649 return $content;
650 }
651
652 /**
653 * Create the new 'emails' settings according to the old module.
654 *
655 * @since 4.0
656 *
657 * @param Hustle_Module_Model $module Module.
658 * @param object $old_module Old module.
659 * @return array
660 */
661 private function parse_email_meta( $module, $old_module ) {
662 $emails = $module->get_emails()->to_array();
663 $old_content = $old_module->meta['content'];
664
665 if ( ! isset( $old_content['form_elements'] ) ) {
666 return $emails;
667 }
668
669 $old_form_fields = $old_content['form_elements'];
670
671 if ( is_string( $old_form_fields ) ) {
672 $old_form_fields = json_decode( $old_form_fields, true );
673 }
674
675 foreach ( $old_form_fields as $name => $properties ) {
676
677 if ( true === $old_form_fields[ $name ]['required'] ) {
678 $old_form_fields[ $name ]['required'] = 'true';
679 }
680
681 if ( 'url' === $old_form_fields[ $name ]['type'] || 'email' === $old_form_fields[ $name ]['type'] ) {
682 $old_form_fields[ $name ]['validate'] = 'true';
683 }
684
685 if ( isset( $old_form_fields[ $name ]['delete'] ) ) {
686 $can_delete = $old_form_fields[ $name ]['delete'];
687 unset( $old_form_fields[ $name ]['delete'] );
688 } else {
689 $can_delete = true;
690 }
691 $old_form_fields[ $name ]['can_delete'] = $can_delete;
692
693 }
694
695 // Replace old 'f_name' by 'first_name' so we can stop doing legacy conversions along the plugin.
696 if ( isset( $old_form_fields['f_name'] ) ) {
697 $old_form_fields['f_name']['name'] = 'first_name';
698 $old_form_fields = Opt_In_Utils::replace_array_key( 'f_name', 'first_name', $old_form_fields );
699 }
700
701 // Replace old 'l_name' by 'last_name' so we can stop doing legacy conversions along the plugin.
702 if ( isset( $old_form_fields['l_name'] ) ) {
703 $old_form_fields['l_name']['name'] = 'last_name';
704 $old_form_fields = Opt_In_Utils::replace_array_key( 'l_name', 'last_name', $old_form_fields );
705 }
706
707 // Set the new recaptcha properties according to what was used in 3.x.
708 if ( isset( $old_form_fields['recaptcha'] ) ) {
709 $old_form_fields['recaptcha']['recaptcha_type'] = 'full';
710 $old_form_fields['recaptcha']['recaptcha_theme'] = 'light';
711 }
712
713 // Use the 4.0 error message.
714 if ( isset( $old_form_fields['submit'] ) ) {
715 $old_form_fields['submit']['error_message'] = __( 'Please fill out all required fields.', 'hustle' );
716 }
717
718 // Make gdpr a form field for optins.
719 if ( isset( $old_content['show_gdpr'] ) && $this->is_true( $old_content['show_gdpr'] ) ) {
720 $old_form_fields['gdpr'] = array(
721 'label' => 'gdpr',
722 'required' => 'true',
723 'css_classes' => '',
724 'type' => 'gdpr',
725 'name' => 'gdpr',
726 'can_delete' => 'true',
727 'placeholder' => '',
728 'gdpr_message' => $old_content['gdpr_message'],
729 );
730 }
731
732 $emails['form_elements'] = $module->sanitize_form_elements( $old_form_fields );
733
734 $emails['after_successful_submission'] = $old_content['after_successful_submission'];
735 $emails['success_message'] = $old_content['success_message'];
736 $emails['auto_close_success_message'] = $this->is_true( $old_content['auto_close_success_message'] ) ? '1' : '0';
737
738 if ( isset( $old_content['auto_close_time'] ) ) {
739 $emails['auto_close_time'] = $old_content['auto_close_time'];
740 }
741
742 if ( isset( $old_content['auto_close_unit'] ) ) {
743 $emails['auto_close_unit'] = $old_content['auto_close_unit'];
744 }
745
746 if ( isset( $old_content['redirect_url'] ) ) {
747 $emails['redirect_url'] = $old_content['redirect_url'];
748 }
749
750 return $emails;
751 }
752
753 /**
754 * Create the new 'design' settings according to the old module.
755 * The old data is used to replace the defaults.
756 * The old unused data is not being removed atm.
757 *
758 * @since 4.0
759 *
760 * @param Hustle_Module_Model $module Module.
761 * @param object $old_module Old module.
762 * @return array
763 */
764 private function parse_design_meta( $module, $old_module ) {
765 $design = $module->get_design()->to_array();
766 if ( $this->is_multisite ) {
767 $design = array_merge( $design, $old_module->meta['design'] );
768 }
769 $old_content = $old_module->meta['content'];
770
771 $design['feature_image_hide_on_mobile'] = $this->is_true( $old_content['feature_image_hide_on_mobile'] ) ? '1' : '0';
772
773 // There's a bug in 3.x that applied customized colors even when disabled.
774 // Turning on "customize_colors" to keep the same appearance in front after migration.
775 $design['customize_colors'] = '1';
776 $design['border'] = $this->is_true( $design['border'] ) ? '1' : '0';
777 $design['drop_shadow'] = $this->is_true( $design['drop_shadow'] ) ? '1' : '0';
778 $design['customize_size'] = $this->is_true( $design['customize_size'] ) ? '1' : '0';
779
780 $is_optin = 'optin' === $module->module_mode;
781 if ( $is_optin ) {
782
783 // When making a module 'optin' in 3.x, the selected palette remained as the informational one.
784 if ( in_array( $design['style'], Hustle_Palettes_Helper::get_palettes_names(), true ) ) {
785 $design['color_palette'] = $design['style'];
786 } else {
787 $design['color_palette'] = 'gray_slate';
788 }
789
790 if ( isset( $design['button_border_color'] ) ) {
791 $design['optin_submit_button_static_bo'] = $design['button_border_color'];
792 $design['optin_submit_button_active_bo'] = $design['button_border_color'];
793 $design['optin_submit_button_active_bo'] = $design['button_border_color'];
794 $design['optin_submit_button_hover_bo'] = $design['button_border_color'];
795 }
796
797 // When input's borders is disabled...
798 if ( ! $this->is_true( $design['form_fields_border'] ) ) {
799
800 // Always make the input's style "outlined" instead of "flat" in order
801 // to keep the input's borders highlighted on error.
802 $design['form_fields_border'] = '1';
803
804 // Make the borders invisible in all states, except for the error one.
805 $design['optin_input_static_bo'] = $design['optin_input_static_bg'];
806 $design['optin_input_hover_bo'] = $design['optin_input_hover_bg'];
807 $design['optin_input_active_bo'] = $design['optin_input_active_bg'];
808
809 // And make the border's attributes match the 3.x on error one.
810 $design['form_fields_border_radius'] = '0';
811 $design['form_fields_border_weight'] = '1';
812 $design['form_fields_border_type'] = 'solid';
813
814 } elseif ( isset( $design['form_fields_border_color'] ) ) {
815
816 $design['optin_input_static_bo'] = $design['form_fields_border_color'];
817 $design['optin_input_hover_bo'] = $design['form_fields_border_color'];
818 $design['optin_input_active_bo'] = $design['form_fields_border_color'];
819 }
820
821 if ( isset( $design['optin_input_icon'] ) ) {
822 $design['optin_input_icon_hover'] = $design['optin_input_icon'];
823 $design['optin_input_icon_focus'] = $design['optin_input_icon'];
824 }
825
826 if ( isset( $design['optin_check_radio_bg'] ) ) {
827 $design['optin_check_radio_bg_checked'] = $design['optin_check_radio_bg'];
828 }
829
830 // Modules before 3.0.3 don't have gdpr options.
831 if ( isset( $design['gdpr_border_color'] ) ) {
832 $design['gdpr_chechbox_border_static'] = $design['gdpr_border_color'];
833 $design['gdpr_chechbox_border_active'] = $design['gdpr_border_color'];
834 }
835
836 // When gdpr checkbox's border is disabled...
837 if ( isset( $design['gdpr_border'] ) && ! $this->is_true( $design['gdpr_border'] ) ) {
838
839 // Always make the input's style "outlined" instead of "flat" in order
840 // to keep the input's borders highlighted on error.
841 $design['gdpr_border'] = '1';
842
843 // Make the borders invisible in all states, except for the error one.
844 $design['gdpr_chechbox_border_static'] = $design['gdpr_chechbox_background_static'];
845 $design['gdpr_chechbox_border_active'] = $design['gdpr_checkbox_background_active'];
846
847 // And make the border's attributes match the 3.x on error one.
848 $design['gdpr_border_radius'] = '0';
849 $design['gdpr_border_weight'] = '2';
850 $design['gdpr_border_type'] = 'solid';
851
852 }
853
854 $design['optin_input_error_background'] = $design['optin_input_static_bg'];
855
856 $design['form_fields_style'] = empty( $design['form_fields_border'] ) || 'false' === $design['form_fields_border'] ? 'flat' : 'outlined';
857 $design['button_style'] = empty( $design['button_border'] ) || 'false' === $design['button_border'] ? 'flat' : 'outlined';
858 $design['gdpr_checkbox_style'] = empty( $design['gdpr_border'] ) || 'false' === $design['gdpr_border'] ? 'flat' : 'outlined';
859
860 } else {
861 $design['title_color_alt'] = $design['title_color'];
862 $design['subtitle_color_alt'] = $design['subtitle_color'];
863 }
864
865 if ( ! empty( trim( $design['custom_css'] ) ) ) {
866 $this->custom_css_migrated = true;
867 $new_css = $this->parse_custom_css( $design['custom_css'], $is_optin );
868 $design['custom_css'] = $new_css . ' /*' . $design['custom_css'] . '*/';
869 }
870
871 return $design;
872 }
873
874 /**
875 * Migrate the old providers to the new format.
876 *
877 * @uses Hustle_Provider_Abstract::migrate_30
878 * @since 4.0
879 *
880 * @param Hustle_Module_Model $module Module.
881 * @param object $old_module Old module.
882 * @return array
883 */
884 private function migrate_integrations( $module, $old_module ) {
885 $old_content = $old_module->meta['content'];
886 $integrations_settings = array();
887
888 if ( $this->is_true( $old_content['save_local_list'] ) ) {
889 $local_list = Hustle_Provider_Utils::get_provider_by_slug( 'local_list' );
890 if ( isset( $old_content['local_list_name'] ) && ! empty( $old_content['local_list_name'] ) ) {
891 $list_name = $old_content['local_list_name'];
892 } else {
893 $list_name = __( 'List', 'hustle' ) . ' ' . $module->module_id;
894 }
895
896 $local_list_form_settings = $local_list->get_provider_form_settings( $module->module_id );
897 $local_list_form_settings->save_form_settings_values( array( 'local_list_name' => $list_name ) );
898 }
899
900 if ( ! empty( $old_content['email_services'] ) ) {
901
902 foreach ( $old_content['email_services'] as $slug => $data ) {
903
904 $provider = Hustle_Provider_Utils::get_provider_by_slug( $slug );
905 if ( $provider instanceof Hustle_Provider_Abstract ) {
906
907 $migrated = $provider->migrate_30( $module, $old_module );
908
909 if ( ! $migrated ) {
910 Opt_In_Utils::maybe_log( __METHOD__ . ': Module ' . $module->module_id . ' with email provider ' . $slug . ' could not be migrated.' );
911 }
912 }
913 }
914
915 $active_email_service = $old_content['active_email_service'];
916 if ( 'mailchimp' === $active_email_service ) {
917 $mailchimp_settings = $old_content['email_services']['mailchimp'];
918 if ( isset( $mailchimp_settings['allow_subscribed_users'] ) && 'allow' === $mailchimp_settings['allow_subscribed_users'] ) {
919 $integrations_settings['allow_subscribed_users'] = '1';
920 }
921 }
922
923 $integrations_settings['active_integrations'] = $active_email_service;
924 }
925
926 return $integrations_settings;
927 }
928
929 /**
930 * Create the new 'display options' settings according to the old module.
931 * Used by Embedded and Social sharing modules only.
932 *
933 * @since 4.0
934 *
935 * @param Hustle_Module_Model $module Module.
936 * @param object $old_module Old module.
937 * @return array
938 */
939 private function parse_display_meta( $module, $old_module ) {
940 $display = $module->get_display()->to_array();
941 $old_settings = $old_module->meta['settings'];
942 $test_types = isset( $old_module->meta['test_types'] ) ? $old_module->meta['test_types'] : array();
943
944 if ( isset( $old_settings['after_content_enabled'] ) && $this->is_true( $old_settings['after_content_enabled'] ) ) {
945 if ( ! isset( $test_types['after_content'] ) || ! $this->is_true( $test_types['after_content'] ) ) {
946 $display['inline_enabled'] = '1';
947 }
948 }
949
950 if ( isset( $old_settings['widget_enabled'] ) && ! $this->is_true( $old_settings['widget_enabled'] ) ) {
951 $display['widget_enabled'] = '0';
952
953 } elseif ( isset( $test_types['widget'] ) && $this->is_true( $test_types['widget'] ) ) {
954 $display['widget_enabled'] = '0';
955 }
956
957 if ( isset( $old_settings['shortcode_enabled'] ) && ! $this->is_true( $old_settings['shortcode_enabled'] ) ) {
958 $display['shortcode_enabled'] = '0';
959
960 } elseif ( isset( $test_types['shortcode'] ) && $this->is_true( $test_types['shortcode'] ) ) {
961 $display['shortcode_enabled'] = '0';
962 }
963
964 return $display;
965 }
966
967 /**
968 * Create the new 'settings' settings according to the old module.
969 * The old data is used to replace the defaults.
970 * The old unused data is not being removed atm.
971 *
972 * @since 4.0
973 *
974 * @param Hustle_Module_Model $module Module.
975 * @param object $old_module Old module.
976 * @return array
977 */
978 private function parse_settings_meta( $module, $old_module ) {
979 $settings = $module->get_settings()->to_array();
980 $old_settings = $old_module->meta['settings'];
981 $old_content = $old_module->meta['content'];
982 if ( $this->is_multisite ) {
983 $settings = array_merge( $settings, $old_settings );
984 }
985
986 if ( isset( $old_settings['allow_scroll_page'] ) ) {
987 $settings['allow_scroll_page'] = $this->is_true( $old_settings['allow_scroll_page'] ) ? '1' : '0';
988 }
989
990 if ( isset( $old_settings['not_close_on_background_click'] ) ) {
991 $settings['close_on_background_click'] = ! $this->is_true( $old_settings['not_close_on_background_click'] ) ? '1' : '0';
992 }
993
994 if ( isset( $old_settings['auto_hide'] ) ) {
995 $settings['auto_hide'] = $this->is_true( $old_settings['auto_hide'] ) ? '1' : '0';
996 }
997
998 // The 3.x default was an empty string, which behaved as "no_animation".
999 if ( isset( $old_settings['animation_in'] ) && '' === $old_settings['animation_in'] ) {
1000 $settings['animation_in'] = 'no_animation';
1001 }
1002
1003 // The 3.x default was an empty string, which behaved as "no_animation".
1004 if ( isset( $old_settings['animation_out'] ) && '' === $old_settings['animation_out'] ) {
1005 $settings['animation_out'] = 'no_animation';
1006 }
1007
1008 // An old bug where this setting was empty, and the wrong option showed up selected in wizard.
1009 if ( empty( $old_settings['after_close'] ) ) {
1010 $settings['after_close'] = 'keep_show';
1011 }
1012
1013 if ( isset( $old_content['after_subscription'] ) ) {
1014 $settings['hide_after_subscription'] = $old_content['after_subscription'];
1015 }
1016
1017 if ( Hustle_Module_Model::EMBEDDED_MODULE !== $module->module_type && isset( $old_settings['triggers'] ) ) {
1018
1019 // Check for click trigger.
1020 if ( isset( $old_settings['triggers']['trigger'] ) && 'click' === $old_settings['triggers']['trigger'] ) {
1021 $settings['triggers']['enable_on_click_shortcode'] = '1';
1022 $settings['triggers']['enable_on_click_element'] = '1';
1023 }
1024
1025 // The time trigger switch was removed, so make the time to show '0' if it was turend off.
1026 if ( ! $this->is_true( $old_settings['triggers']['on_time'] ) ) {
1027 $settings['triggers']['on_time_delay'] = '0';
1028 }
1029
1030 // Same keys. Making sure the value's type is the same that we're using in 4.0.
1031 if ( isset( $old_settings['triggers']['on_exit_intent_per_session'] ) ) {
1032 $settings['triggers']['on_exit_intent_per_session'] = $this->is_true( $old_settings['triggers']['on_exit_intent_per_session'] ) ? '1' : '0';
1033 }
1034
1035 if ( isset( $old_settings['triggers']['on_exit_intent_delayed'] ) ) {
1036 $settings['triggers']['on_exit_intent_delayed'] = $this->is_true( $old_settings['triggers']['on_exit_intent_delayed'] ) ? '1' : '0';
1037 }
1038
1039 if ( isset( $old_settings['on_adblock']['on_exit_intent_delayed'] ) ) {
1040 $settings['triggers']['on_adblock'] = $this->is_true( $old_settings['triggers']['on_adblock'] ) ? '1' : '0';
1041 }
1042 }
1043
1044 return $settings;
1045 }
1046
1047 /**
1048 * Create the new 'visibility' settings according to the old module.
1049 *
1050 * @since 4.0
1051 *
1052 * @param Hustle_Module_Model $module Module.
1053 * @param object $old_module Old module.
1054 * @return array
1055 */
1056 private function parse_visibility_meta( $module, $old_module ) {
1057 $conditions = $module->get_visibility()->to_array();
1058 $old_settings = $old_module->meta['settings'];
1059
1060 if ( isset( $old_settings['conditions'] ) ) {
1061
1062 $old_conditions = $old_settings['conditions'];
1063
1064 $group_id = substr( md5( wp_rand() ), 0, 10 );
1065
1066 $new_conditions = array();
1067
1068 // Visitor logged in status.
1069 if ( isset( $old_conditions['visitor_logged_in'] ) && 'true' === $old_conditions['visitor_logged_in'] ) {
1070 $new_conditions['visitor_logged_in_status']['show_to'] = 'logged_in';
1071
1072 } elseif ( isset( $old_conditions['visitor_not_logged_in'] ) && 'true' === $old_conditions['visitor_not_logged_in'] ) {
1073 $new_conditions['visitor_logged_in_status']['show_to'] = 'logged_out';
1074 }
1075
1076 // Visitor's device.
1077 if ( isset( $old_conditions['only_on_mobile'] ) && 'true' === $old_conditions['only_on_mobile'] ) {
1078 $new_conditions['visitor_device']['filter_type'] = 'mobile';
1079
1080 } elseif ( isset( $old_conditions['not_on_mobile'] ) && 'true' === $old_conditions['not_on_mobile'] ) {
1081 $new_conditions['visitor_device']['filter_type'] = 'not_mobile';
1082 }
1083
1084 // Referrer.
1085 if ( isset( $old_conditions['from_specific_ref'] ) ) {
1086 $new_conditions['from_referrer']['filter_type'] = 'true';
1087 $new_conditions['from_referrer']['refs'] = $old_conditions['from_specific_ref']['refs'];
1088
1089 } elseif ( isset( $old_conditions['not_from_specific_ref'] ) ) {
1090 $new_conditions['from_referrer']['filter_type'] = 'false';
1091 $new_conditions['from_referrer']['refs'] = $old_conditions['from_specific_ref']['refs'];
1092 }
1093
1094 // Source of arrival.
1095 if ( isset( $old_conditions['not_from_internal_link'] ) || isset( $old_conditions['from_search_engine'] ) ) {
1096
1097 if ( isset( $old_conditions['not_from_internal_link'] ) && 'true' === $old_conditions['not_from_internal_link'] ) {
1098 $new_conditions['source_of_arrival']['source_external'] = 'true';
1099
1100 }
1101
1102 if ( isset( $old_conditions['from_search_engine'] ) && 'true' === $old_conditions['from_search_engine'] ) {
1103 $new_conditions['source_of_arrival']['source_search'] = 'true';
1104 }
1105 }
1106
1107 // On URL.
1108 if ( isset( $old_conditions['on_specific_url'] ) ) {
1109 $new_conditions['on_url']['filter_type'] = 'only';
1110 $new_conditions['on_url']['urls'] = $old_conditions['on_specific_url']['urls'];
1111
1112 } elseif ( isset( $old_conditions['not_on_specific_url'] ) ) {
1113 $new_conditions['on_url']['filter_type'] = 'except';
1114 $new_conditions['on_url']['urls'] = $old_conditions['not_on_specific_url']['urls'];
1115 }
1116
1117 // Visitor has commented.
1118 if ( isset( $old_conditions['visitor_has_commented'] ) && 'true' === $old_conditions['visitor_has_commented'] ) {
1119 $new_conditions['visitor_commented']['filter_type'] = 'true';
1120
1121 } elseif ( isset( $old_conditions['visitor_has_never_commented'] ) && 'true' === $old_conditions['visitor_has_never_commented'] ) {
1122 $new_conditions['visitor_commented']['filter_type'] = 'false';
1123 }
1124
1125 // Country.
1126 if ( isset( $old_conditions['in_a_country'] ) ) {
1127 $new_conditions['visitor_country']['filter_type'] = 'only';
1128 $new_conditions['visitor_country']['countries'] = $old_conditions['in_a_country']['countries'];
1129
1130 } elseif ( isset( $old_conditions['not_in_a_country'] ) ) {
1131 $new_conditions['visitor_country']['filter_type'] = 'except';
1132 $new_conditions['visitor_country']['countries'] = $old_conditions['not_in_a_country']['countries'];
1133 }
1134
1135 // 404.
1136 if ( isset( $old_conditions['only_on_not_found'] ) && 'true' === $old_conditions['only_on_not_found'] ) {
1137 $new_conditions['page_404']['show'] = 'true';
1138 }
1139
1140 // Module shown less than.
1141 if ( isset( $old_conditions['shown_less_than'] ) ) {
1142 $new_conditions['shown_less_than']['filter_type'] = 'limited';
1143 $new_conditions['shown_less_than']['less_than'] = $old_conditions['shown_less_than']['less_than'];
1144 }
1145
1146 // Custom Post Types.
1147 $post_types = Opt_In_Utils::get_post_types();
1148 $cpts = wp_list_pluck( $post_types, 'label', 'name' );
1149 foreach ( $cpts as $slug => $label ) {
1150 if ( isset( $old_conditions[ $label ] ) ) {
1151 $new_conditions[ $slug ] = $old_conditions[ $label ];
1152 }
1153 }
1154
1155 $regular_conditions_keys = array( 'pages', 'posts', 'categories', 'tags' );
1156 foreach ( $regular_conditions_keys as $key ) {
1157 if ( isset( $old_conditions[ $key ] ) ) {
1158 $new_conditions[ $key ] = $old_conditions[ $key ];
1159 }
1160 }
1161
1162 $new_visibility = array();
1163 $new_visibility[ $group_id ] = $new_conditions;
1164 $new_visibility[ $group_id ]['group_id'] = $group_id;
1165 $new_visibility[ $group_id ]['filter_type'] = 'any';
1166
1167 $visibility = array( 'conditions' => $new_visibility );
1168
1169 } else {
1170 $visibility = array();
1171 }
1172
1173 return $visibility;
1174 }
1175
1176 /**
1177 * Migrate global settings.
1178 *
1179 * @since 4.0
1180 */
1181 private function migrate_settings() {
1182
1183 $current_settings = get_option( 'hustle_settings', array() );
1184
1185 // Email sender address and name settings.
1186 $old_email_sender_settings = get_option( 'hustle_global_email_settings' );
1187 if ( $old_email_sender_settings ) {
1188 $current_settings['general']['sender_email_address'] = isset( $old_email_sender_settings['sender_email_address'] ) ? $old_email_sender_settings['sender_email_address'] : get_option( 'admin_email', '' );
1189 $current_settings['general']['sender_email_name'] = isset( $old_email_sender_settings['sender_email_name'] ) ? $old_email_sender_settings['sender_email_name'] : get_option( 'blogname', '' );
1190 }
1191
1192 // Unsubscription email and messages.
1193 $old_unsubscription_settings = get_option( 'hustle_global_unsubscription_settings' );
1194 if ( $old_unsubscription_settings ) {
1195 $current_settings['unsubscribe']['messages'] = isset( $old_unsubscription_settings['messages'] ) ? $old_unsubscription_settings['messages'] : '';
1196 $current_settings['unsubscribe']['email'] = isset( $old_unsubscription_settings['email'] ) ? $old_unsubscription_settings['email'] : '';
1197 }
1198
1199 update_option( 'hustle_settings', $current_settings );
1200 }
1201
1202 /**
1203 * Take all classes and replace them with the new ones.
1204 *
1205 * @since 4.0
1206 *
1207 * @param string $custom_css Custom CSS.
1208 * @param bool $is_optin Is optin or not.
1209 * @return string
1210 */
1211 private function parse_custom_css( $custom_css, $is_optin ) {
1212
1213 $replace_values = array(
1214 '.wph-modal' => '', // Main wrapper (no need to migrate this, main wrapper "hustle-ui" it's automatically added on 4.0).
1215 '.hustle-modal' => '.hustle-layout', // Content wrapper.
1216 '.wph-modal-active' => '.hustle-show', // Active class.
1217 '.hustle-modal-title' => '.hustle-title', // Title.
1218 '.hustle-modal-subtitle' => '.hustle-subtitle', // Subtitle.
1219 'section .hustle-modal-article' => '.hustle-content',
1220 '.hustle-modal-article' => '.hustle-content',
1221 'section' => '.hustle-content',
1222 '.hustle-layout .hustle-modal-close' => '.hustle-modal-close', // .hustle-layout (previously .hustle-modal) is no longer a parent.
1223 '.hustle-modal-close .hustle-icon' => '.hustle-button-close [class*="hustle-icon-"]', // Close button (icon).
1224 '.hustle-modal-close' => '.hustle-button-close', // Close button.
1225 '.hustle-modal-image' => '.hustle-image', // Feat. image.
1226 '.hustle-modal-cta' => '.hustle-button-cta', // Call to action.
1227 '.hustle-modal-image_only' => '.hustle-image-only', // Image only.
1228 '.hustle-modal-mobile_hidden' => '.hustle-hide-until-sm', // Mobile hidden.
1229 '.hustle-modal-content' => '.hustle-layout-content',
1230 '.hustle-modal-footer' => '.hustle-layout-footer',
1231 );
1232
1233 if ( $is_optin ) {
1234
1235 $extra_classes = array(
1236 '.hustle-modal-body' => '.hustle-layout-body', // Body.
1237 'footer' => '.hustle-layout-form', // Form container.
1238 '.hustle-modal-optin_form' => '.hustle-layout-form', // Form container.
1239 '.hustle-modal-optin_field' => '.hustle-field', // Form field(s).
1240 '.hustle-modal-optin_group' => '.hustle-form-options', // Provider's extra options.
1241 '.hustle-modal-optin_button button' => '.hustle-button-submit', // Submit button.
1242 '.hustle-modal-optin_button' => '.hustle-button-submit', // Submit button.
1243 '.hustle-modal-optin_field input' => '.hustle-input', // Inputs.
1244 '.hustle-modal-provider-args-container' => '.hustle-form-options', // Provider's extra options.
1245 '.hustle-modal-one' => '.hustle-optin--default', // Layout 1 - Default.
1246 '.hustle-modal-two' => '.hustle-optin--compact', // Layout 2 - Compact.
1247 '.hustle-modal-three' => '.hustle-optin--focus-optin', // Layout 3 (Optin Focus).
1248 '.hustle-modal-four' => '.hustle-optin--focus-content', // Layout 4 (Content Focus).
1249 '.hustle-layout .hustle-modal-success' => '.hustle-success',
1250 '.hustle-modal-success' => '.hustle-success',
1251 );
1252
1253 } else {
1254
1255 $extra_classes = array(
1256 '.hustle-layout .hustle-modal-body' => '.hustle-layout', // Body.
1257 '.hustle-modal-body' => '.hustle-layout', // Body.
1258 '.hustle-modal-simple' => '.hustle-info--compact', // Simple - Compact.
1259 '.hustle-modal-minimal' => '.hustle-info--default', // Minimal - Default.
1260 '.hustle-modal-cabriolet' => '.hustle-info--stacked', // Cabriolet (Stacked).
1261 '.hustle-modal-header' => '.hustle-layout-header',
1262 );
1263 }
1264
1265 $replace_values = array_merge( $replace_values, $extra_classes );
1266
1267 foreach ( $replace_values as $old => $new ) {
1268 $custom_css = preg_replace( '/' . $old . '(?!-|[a-z])/m', $new, $custom_css );
1269 }
1270
1271 return $custom_css;
1272
1273 }
1274
1275 /**
1276 * Finish tracking subscription migration
1277 *
1278 * @param int $migrated_rows Migrated rows.
1279 */
1280 private function finish_tracking_subscription_migration( $migrated_rows = 0 ) {
1281 // Set the flag that we already migrated the tracking.
1282 self::mark_tracking_migration_as_completed();
1283 wp_send_json_success(
1284 array(
1285 'current_meta' => 'done',
1286 'migrated_rows' => $migrated_rows,
1287 )
1288 );
1289 }
1290
1291 /**
1292 * Mark tracking migration as completed
1293 */
1294 public static function mark_tracking_migration_as_completed() {
1295 delete_option( 'hustle_30_migration_data' );
1296 self::migration_passed( 'hustle_30_tracking_migrated' );
1297 }
1298
1299 /**
1300 * Check whether there's tracking and subscriptions data to be migrated.
1301 *
1302 * @since 4.0
1303 *
1304 * @return boolean
1305 */
1306 public static function is_tracking_subscription_data_to_migrate() {
1307
1308 $migration_process_data = get_option( 'hustle_30_migration_data', array() );
1309
1310 if ( ! empty( $migration_process_data ) ) {
1311 return true;
1312 }
1313
1314 $blog_modules_id = Hustle_Module_Collection::instance()->get_30_modules_ids_by_blog( get_current_blog_id() );
1315
1316 // If we don't have modules, finish.
1317 if ( empty( $blog_modules_id ) ) {
1318 self::mark_tracking_migration_as_completed();
1319 return false;
1320 }
1321
1322 $total_entries = self::get_tracking_submissions_count( $blog_modules_id );
1323
1324 // If we don't have tracking nor submissions, finish.
1325 if ( ! $total_entries ) {
1326 self::mark_tracking_migration_as_completed();
1327 return false;
1328 }
1329
1330 return true;
1331 }
1332
1333 /**
1334 * Migrate tracking and subscription data.
1335 * This is done via ajax in order to avoid timeouts.
1336 *
1337 * @since 4.0
1338 */
1339 public function migrate_tracking_and_subscriptions() {
1340 Opt_In_Utils::validate_ajax_call( 'hustle-migrate-tracking-and-subscriptions' );
1341
1342 global $wpdb;
1343 $main_site_table = $wpdb->base_prefix . Hustle_Db::TABLE_HUSTLE_MODULES_META;
1344 $batch_limit = intval( apply_filters( 'hustle_migration_tracking_batch_limit', 50 ) );
1345
1346 $migration_data = get_option( 'hustle_30_migration_data', array() );
1347
1348 // Things to get in the first run only.
1349 if ( ! empty( $migration_data ) ) {
1350 $blog_modules_id = $migration_data['blog_modules_id'];
1351 $current_meta = $migration_data['current_meta'];
1352
1353 } else {
1354
1355 $blog_modules_id = Hustle_Module_Collection::instance()->get_30_modules_ids_by_blog( get_current_blog_id() );
1356
1357 // If we don't have modules, finish.
1358 if ( empty( $blog_modules_id ) ) {
1359 $this->finish_tracking_subscription_migration();
1360 }
1361
1362 $total_entries = self::get_tracking_submissions_count( $blog_modules_id, $wpdb );
1363
1364 // If we don't have tracking nor submissions, finish.
1365 if ( ! $total_entries ) {
1366 $this->finish_tracking_subscription_migration();
1367 }
1368
1369 $current_meta = 0;
1370
1371 // If there's enough data for 1 run only.
1372 if ( $batch_limit > $total_entries ) {
1373 $total_batches = 1;
1374 } else {
1375 $total_batches = round( intval( $total_entries ) / intval( $batch_limit ) );
1376 }
1377
1378 $migration_data = array(
1379 'blog_modules_id' => $blog_modules_id,
1380 'current_meta' => $current_meta,
1381 'total_entries' => $total_entries,
1382 'migrated_rows' => 0,
1383 'percentage_per_batch' => 100 / $total_batches,
1384 'migrated_percentage' => 0,
1385 );
1386
1387 update_option( 'hustle_30_migration_data', $migration_data );
1388 }
1389 $migrated_rows = $migration_data['migrated_rows'];
1390
1391 $metas = $this->get_paged_metas( $blog_modules_id, $current_meta, $batch_limit, $wpdb );
1392
1393 // If there aren't more metas, we finished.
1394 if ( ! $metas ) {
1395 $this->finish_tracking_subscription_migration( $migrated_rows );
1396 }
1397
1398 foreach ( $metas as $meta ) {
1399
1400 $migrated_rows++;
1401
1402 // Store the new views, conversions, and subscriptions.
1403 if ( false !== stripos( $meta->meta_key, 'view' ) ) {
1404 $current_meta = $this->migrate_tracking( $meta, 'view' );
1405
1406 } elseif ( false !== stripos( $meta->meta_key, 'conversion' ) ) {
1407 $current_meta = $this->migrate_tracking( $meta, 'conversion' );
1408
1409 } elseif ( 'subscription' === $meta->meta_key ) {
1410 $current_meta = $this->migrate_subscription( $meta );
1411
1412 } elseif ( false !== stripos( $meta->meta_key, 'page_shares' ) ) {
1413 $current_meta = $this->migrate_sshare_page_counter( $meta );
1414 }
1415 }
1416
1417 // If there aren't more metas, we finished.
1418 if ( ! $current_meta ) {
1419 $this->finish_tracking_subscription_migration( $migrated_rows );
1420 }
1421
1422 // Update last the stored data of the last batch.
1423 $migration_data['current_meta'] = $current_meta;
1424 $migration_data['migrated_rows'] = $migrated_rows;
1425 $migration_data['migrated_percentage'] += $migration_data['percentage_per_batch'];
1426 update_option( 'hustle_30_migration_data', $migration_data );
1427
1428 $response = array(
1429 'migrated_percentage' => round( $migration_data['migrated_percentage'], 2 ),
1430 'migrated_rows' => $migrated_rows,
1431 'total_entries' => $migration_data['total_entries'],
1432 );
1433
1434 wp_send_json_success( $response );
1435
1436 }
1437
1438 /**
1439 * Get the 3.x metas of a module, paginated.
1440 * This just retrieves tracking (views and conversions) and subscriptions.
1441 *
1442 * @since 4.0
1443 *
1444 * @param array $modules_id Module IDs.
1445 * @param int $current_meta Current meta.
1446 * @param ont $limit Limit.
1447 * @param boolean $wpdb WPDB.
1448 * @return array
1449 */
1450 private function get_paged_metas( $modules_id, $current_meta, $limit = 10, $wpdb = false ) {
1451
1452 if ( ! $wpdb ) {
1453 global $wpdb;
1454 }
1455
1456 $meta_keys_placeholders = implode( ', ', array_fill( 0, count( self::$tracking_meta_keys ), '%s' ) );
1457 $meta_key_query = $wpdb->prepare(
1458 "`meta_key` IN ({$meta_keys_placeholders})", // phpcs:ignore
1459 self::$tracking_meta_keys
1460 );
1461
1462 $modules_id_placeholders = implode( ', ', array_fill( 0, count( $modules_id ), '%d' ) );
1463 $modules_id_query = $wpdb->prepare(
1464 "`module_id` IN ({$modules_id_placeholders})", // phpcs:ignore
1465 $modules_id
1466 );
1467
1468 // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1469 $query = $wpdb->prepare(
1470 'SELECT *
1471 FROM `' . $wpdb->base_prefix . "hustle_modules_meta`
1472 WHERE `meta_id` > %d
1473 AND (({$modules_id_query}
1474 AND {$meta_key_query})
1475 OR `meta_key` LIKE %s)
1476 ORDER BY `meta_id` ASC
1477 LIMIT %d",
1478 $current_meta,
1479 '%page_shares',
1480 $limit
1481 );
1482 // phpcs:enable
1483
1484 $metas = $wpdb->get_results( $query ); // phpcs:ignore
1485
1486 return $metas;
1487 }
1488
1489 /**
1490 * Get tracking submissions count
1491 *
1492 * @param array $modules_id Module ID.
1493 * @param abject $wpdb WPDB.
1494 * @return string
1495 */
1496 private static function get_tracking_submissions_count( $modules_id, $wpdb = false ) {
1497
1498 if ( ! $wpdb ) {
1499 global $wpdb;
1500 }
1501 $modules_id_placeholders = implode( ', ', array_fill( 0, count( $modules_id ), '%d' ) );
1502
1503 $modules_id_query = $wpdb->prepare( "`module_id` IN ({$modules_id_placeholders})", $modules_id );// phpcs:ignore
1504
1505 $meta_keys_placeholders = implode( ', ', array_fill( 0, count( self::$tracking_meta_keys ), '%s' ) );
1506
1507 $meta_keys_query = $wpdb->prepare( "`meta_key` IN ({$meta_keys_placeholders})", self::$tracking_meta_keys );// phpcs:ignore
1508
1509 // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1510 $query = $wpdb->prepare(
1511 "SELECT COUNT(*)
1512 FROM `{$wpdb->base_prefix}hustle_modules_meta`
1513 WHERE ({$modules_id_query}
1514 AND {$meta_keys_query})
1515 OR `meta_key` LIKE %s",
1516 '%page_shares'
1517 );
1518 // phpcs:enable
1519
1520 return $wpdb->get_var( $query ); // phpcs:ignore
1521 }
1522
1523 /**
1524 * Store the new tracking view.
1525 *
1526 * @since 4.0
1527 * @param object $old_view Old view.
1528 * @param string $tracking_type view|conversion.
1529 */
1530 private function migrate_tracking( $old_view, $tracking_type ) {
1531
1532 $old_data = json_decode( $old_view->meta_value, true );
1533
1534 // Data coming from 2.x has 'optin_id' instead of 'module_id'.
1535 $module_id = isset( $old_data['module_id'] ) ? $old_data['module_id'] : $old_data['optin_id'];
1536
1537 if ( isset( $old_data['module_type'] ) ) {
1538 $module_type = $old_data['module_type'];
1539 } else {
1540 // Conversions didn't store the module_type. Try to get it without making a db call.
1541 if ( false !== stripos( $old_view->meta_key, 'popup' ) ) {
1542 $module_type = Hustle_Module_Model::POPUP_MODULE;
1543
1544 } elseif ( false !== stripos( $old_view->meta_key, 'slidein' ) ) {
1545 $module_type = Hustle_Module_Model::SLIDEIN_MODULE;
1546
1547 } else {
1548 // It can be either an embed or ssharing module. No way to know it unless retrieving it.
1549 $module_type = $this->get_module_type_by_module_id( $module_id );
1550 }
1551 }
1552 $meta_key = $old_view->meta_key;
1553 $date_created = date_i18n( 'Y-m-d H:i:s', $old_data['date'] );
1554 $module_sub_type = null;
1555
1556 // Define the subtype for embeds and social sharing modules.
1557 if ( Hustle_Module_Model::EMBEDDED_MODULE === $module_type || Hustle_Module_Model::SOCIAL_SHARING_MODULE === $module_type ) {
1558
1559 // TODO: use constants here instead.
1560 if ( false !== stripos( $meta_key, 'shortcode' ) ) {
1561 $module_sub_type = 'shortcode';
1562 } elseif ( false !== stripos( $meta_key, 'widget' ) ) {
1563 $module_sub_type = 'widget';
1564 } elseif ( false !== stripos( $meta_key, 'after_content' ) ) {
1565 $module_sub_type = 'inline';
1566 } elseif ( false !== stripos( $meta_key, 'floating' ) ) {
1567 $module_sub_type = 'floating';
1568 }
1569 }
1570
1571 $tracking = Hustle_Tracking_Model::get_instance();
1572 $tracking->save_tracking( $module_id, $tracking_type, $module_type, $old_data['page_id'], $module_sub_type, $date_created, $old_data['ip'] );
1573
1574 return $old_view->meta_id;
1575 }
1576
1577 /**
1578 * Migrate 3.x subscription.
1579 *
1580 * @since 4.0
1581 *
1582 * @param object $old_subscription Old subscription.
1583 * @return int
1584 */
1585 private function migrate_subscription( $old_subscription ) {
1586
1587 $data = json_decode( $old_subscription->meta_value, true );
1588
1589 $date_created = date_i18n( 'Y-m-d H:i:s', $data['time'] );
1590 $entry = new Hustle_Entry_Model();
1591 $entry->entry_type = $data['module_type'];
1592 $entry->module_id = $old_subscription->module_id;
1593
1594 $entry->save( $date_created );
1595
1596 $entry_data = array();
1597 foreach ( $data as $name => $value ) {
1598 if ( 'time' === $name ) {
1599 continue;
1600 }
1601
1602 // Getting rid of legacy stuff by transforming it already.
1603 if ( 'l_name' === $name ) {
1604 $name = 'last_name';
1605 } elseif ( 'f_name' === $name ) {
1606 $name = 'first_name';
1607 }
1608 $entry_data[] = array(
1609 // Remove trailing underscores. Used in 3.x when the fields' name had spaces.
1610 'name' => preg_replace( '/_+$/', '', $name ),
1611 'value' => $value,
1612 );
1613 }
1614 $entry->set_fields( $entry_data, $date_created );
1615
1616 return $old_subscription->meta_id;
1617 }
1618
1619 /**
1620 * Migrate 3.x per page ssharing count.
1621 *
1622 * @since 4.0
1623 *
1624 * @param object $old_counter Old counter.
1625 * @return int
1626 */
1627 private function migrate_sshare_page_counter( $old_counter ) {
1628
1629 $page_id = $old_counter->module_id;
1630 $count = $old_counter->meta_value;
1631
1632 $tracking = Hustle_Tracking_Model::get_instance();
1633 $tracking->save_old_migrated_sshare_page_count( $page_id, $count );
1634
1635 return $old_counter->meta_id;
1636 }
1637
1638 /**
1639 * Get the module_type by the module_id.
1640 *
1641 * @since 4.0
1642 *
1643 * @param int $module_id Module.ID.
1644 * @return string
1645 */
1646 private function get_module_type_by_module_id( $module_id ) {
1647 global $wpdb;
1648
1649 // TODO: This should be cached as long as the query is the same in the same load.
1650 $module_type = $wpdb->get_var( $wpdb->prepare( 'SELECT `module_type` FROM ' . Hustle_Db::modules_table() . ' WHERE `module_id`=%d', $module_id ) ); // phpcs:ignore
1651
1652 return $module_type;
1653 }
1654
1655 /**
1656 * Helper function to check different values
1657 * previously given to properties which all mean true.
1658 *
1659 * @param mixed $value Value.
1660 * @return boolean
1661 */
1662 private function is_true( $value ) {
1663 if ( '1' === $value || 'true' === $value || 1 === $value || true === $value ) {
1664 return true;
1665 }
1666 return false;
1667 }
1668 }
1669