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