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