PluginProbe ʕ •ᴥ•ʔ
Hustle – Email Marketing, Lead Generation, Optins, Popups / 7.8.14
Hustle – Email Marketing, Lead Generation, Optins, Popups v7.8.14
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 / front / hustle-module-front.php
wordpress-popup / inc / front Last commit date
non-sshare-styles 1 month ago class-hustle-decorator-non-sshare.php 3 months ago class-hustle-decorator-sshare.php 3 months ago class-hustle-decorator_abstract.php 3 years ago class-hustle-module-preview.php 1 year ago hustle-module-front-ajax.php 4 days ago hustle-module-front.php 4 days ago hustle-module-inline-style-queue.php 3 months ago hustle-module-renderer.php 1 month ago hustle-renderer-abstract.php 3 months ago hustle-renderer-sshare.php 4 days ago
hustle-module-front.php
1164 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Hustle_Module_Front
4 *
5 * @package Hustle
6 */
7
8 /**
9 * Class Hustle_Module_Front
10 */
11 class Hustle_Module_Front {
12
13 /**
14 * Modules
15 *
16 * @var array
17 */
18 private $modules = array();
19
20 /**
21 * Contains the queued modules types as keys, 1 as the value.
22 * Used to queue the required styles only.
23 *
24 * @since 4.0.1
25 * @var array
26 */
27 private $module_types_to_display = array();
28 /**
29 * Non inline modules
30 *
31 * @var array
32 */
33 private $non_inline_modules = array();
34 /**
35 * Inline modules
36 *
37 * @var array
38 */
39 private $inline_modules = array();
40
41 /**
42 * Array with data about the modules.
43 * This is used to conditionally add scripts.
44 *
45 * @since 4.0.4
46 * @var array
47 */
48 private $modules_data_for_scripts = array();
49
50 /**
51 * Filter property for the_content
52 *
53 * @var int
54 */
55 private static $the_content_filter_priority = 20;
56
57 const SHORTCODE = 'wd_hustle';
58
59 /**
60 * Hustle_Module_Front constructor.
61 *
62 * @since unknown
63 */
64 public function __construct() {
65 // Schedule email cron action.
66 add_action( 'hustle_send_email', array( 'Hustle_Mail', 'send_email' ), 10, 3 );
67 add_action( 'hustle_aweber_token_refresh', array( 'Hustle_Aweber', 'refresh_token' ) );
68
69 // Used for Gutenberg.
70 add_action( 'wp_ajax_hustle_render_unsubscribe_form', array( $this, 'get_unsubscribe_form' ) );
71
72 $is_preview = filter_input( INPUT_GET, 'hustle_preview', FILTER_VALIDATE_BOOLEAN ) && Opt_In_Utils::is_user_allowed( 'hustle_edit_module' );
73
74 // Don't render Hustle's widgets and shortcodes on preview mode.
75 if ( ! $is_preview ) {
76 // These are used on admin side.
77 $this->register_shortcodes_and_widget();
78 }
79
80 /**
81 * Allow third-party devs to prevent Hustle from initializing on their frontend pages.
82 * This is useful on previews, for example.
83 *
84 * @since 4.4.5
85 */
86 $prevent_front_init = apply_filters( 'hustle_prevent_front_initialization', false );
87
88 // Abort if it's admin or is a preview.
89 // phpcs:disable WordPress.Security.NonceVerification.Recommended
90 if (
91 is_admin() ||
92 isset( $_GET['widgetPreview'] ) || // For the widgets preview.
93 isset( $_GET['ct_builder'] ) || // For Oxygen builder.
94 isset( $_GET['elementor-preview'] ) || // prevent initial in elementor.
95 $prevent_front_init
96 ) {
97 return;
98 }
99 // phpcs:enable
100
101 Hustle_Module_Inline_Style_Queue::init();
102
103 if ( ! $is_preview ) {
104 $this->prepare_for_front();
105 } else {
106 new Hustle_Module_Preview();
107 }
108
109 add_action( 'post_updated', array( __CLASS__, 'maybe_unsubscribe_page' ), 10, 3 );
110 }
111
112 /**
113 * Prepare for front
114 */
115 private function prepare_for_front() {
116 Hustle_Provider_Autoload::initiate_providers();
117 Hustle_Provider_Autoload::load_block_editor();
118
119 add_action(
120 'wp_enqueue_scripts',
121 array( $this, 'register_scripts' )
122 );
123
124 // Enqueue it in the footer to overrider all the css that comes with the popup.
125 add_action(
126 'hustle_after_enqueue_inline_styles',
127 array( $this, 'register_styles' )
128 );
129
130 add_action( 'wp_head', array( $this, 'preload_custom_font' ) );
131
132 add_action(
133 'template_redirect',
134 array( $this, 'create_modules' ),
135 0
136 );
137
138 add_action( 'template_redirect', array( $this, 'render_non_inline_modules' ) );
139
140 add_filter( 'get_the_excerpt', array( $this, 'remove_the_content_filter' ), 9 );
141 add_filter( 'wp_trim_excerpt', array( $this, 'restore_the_content_filter' ) );
142
143 add_filter(
144 'the_content',
145 array( $this, 'show_after_page_post_content' ),
146 self::$the_content_filter_priority
147 );
148
149 // NextGEN Gallery compat.
150 add_filter(
151 'run_ngg_resource_manager',
152 array( $this, 'nextgen_compat' )
153 );
154 }
155
156 /**
157 * Register shortcodes and widget.
158 *
159 * @since 4.3.1
160 *
161 * @return void
162 */
163 private function register_shortcodes_and_widget() {
164 if ( Hustle_Settings_Admin::global_tracking() ) {
165 add_action( 'widgets_init', array( $this, 'register_widget' ) );
166 }
167 add_shortcode( self::SHORTCODE, array( $this, 'shortcode' ) );
168
169 // Legacy custom content support.
170 add_shortcode(
171 'wd_hustle_cc',
172 array( $this, 'shortcode' )
173 );
174
175 // Legacy social sharing support.
176 add_shortcode(
177 'wd_hustle_ss',
178 array( $this, 'shortcode' )
179 );
180
181 // Unsubscribe shortcode.
182 add_shortcode(
183 'wd_hustle_unsubscribe',
184 array( $this, 'unsubscribe_shortcode' )
185 );
186 }
187
188 /**
189 * Don't apply the_content filter for excerpts.
190 *
191 * @param string $post_excerpt The post's excerpt.
192 */
193 public function remove_the_content_filter( $post_excerpt ) {
194 remove_filter( 'the_content', array( $this, 'show_after_page_post_content' ), self::$the_content_filter_priority );
195
196 return $post_excerpt;
197 }
198
199 /**
200 * Restore the content filter
201 *
202 * @param string $text Text.
203 * @return string
204 */
205 public function restore_the_content_filter( $text ) {
206 add_filter( 'the_content', array( $this, 'show_after_page_post_content' ), self::$the_content_filter_priority );
207
208 return $text;
209 }
210
211 /**
212 * Register widget
213 */
214 public function register_widget() {
215 register_widget( 'Hustle_Module_Widget' );
216 register_widget( 'Hustle_Module_Widget_Legacy' );
217 }
218
219 /**
220 * Register scripts
221 *
222 * @return null
223 */
224 public function register_scripts() {
225 global $post;
226 $unsubscribe_shortcode = false;
227 // Check for shortcode wd_hustle_unsubscribe.
228 if ( $post && preg_match( '/wd_hustle_unsubscribe/', $post->post_content ) ) {
229 $unsubscribe_shortcode = true;
230 }
231
232 // There aren't any published modules. We don't need scripts.
233 if ( ! count( $this->modules ) && ! $unsubscribe_shortcode ) {
234 return;
235 }
236
237 $is_on_upfront_builder = class_exists( 'UpfrontThemeExporter' ) && function_exists( 'upfront_exporter_is_running' ) && upfront_exporter_is_running();
238 if ( ! $is_on_upfront_builder ) {
239 if ( is_customize_preview() || isset( $_REQUEST['fl_builder'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
240 if ( ! is_singular() ) {
241 return;
242 }
243 if ( ! $unsubscribe_shortcode ) {
244 return;
245 }
246 }
247 }
248
249 // Fix for YITH Frontend Manager for WooCommerce.
250 if ( class_exists( 'YITH_Frontend_Manager' ) ) {
251 $default_page_id = get_option( 'yith_wcfm_main_page_id', false );
252 if ( $default_page_id && is_page( $default_page_id ) ) {
253 return;
254 }
255 }
256
257 // Register popup requirements.
258 wp_register_script(
259 'hustle_front',
260 Opt_In::$plugin_url . 'assets/js/front.min.js',
261 array( 'jquery', 'underscore' ),
262 Opt_In::VERSION,
263 true
264 );
265
266 $modules = apply_filters( 'hustle_front_modules', $this->modules );
267 wp_localize_script( 'hustle_front', 'Modules', $modules );
268
269 // force set archive page slug.
270 global $wp;
271 $slug = is_home() && is_front_page() ? 'hustle-front-blog-page' : sanitize_title( $wp->request );
272
273 $conditional_tags = array(
274 'is_single' => is_single(),
275 'is_singular' => is_singular(),
276 'is_tag' => is_tag(),
277 'is_category' => is_category(),
278 'is_author' => is_author(),
279 'is_date' => is_date(),
280 'is_post_type_archive' => is_post_type_archive(),
281 'is_404' => is_404(),
282 'is_front_page' => is_front_page(),
283 'is_search' => is_search(),
284 );
285
286 if ( Opt_In_Utils::is_woocommerce_active() ) {
287 $conditional_tags['is_product_tag'] = is_product_tag();
288 $conditional_tags['is_product_category'] = is_product_category();
289 $conditional_tags['is_shop'] = is_shop();
290 $conditional_tags['is_woocommerce'] = is_woocommerce();
291 $conditional_tags['is_checkout'] = is_checkout();
292 $conditional_tags['is_cart'] = is_cart();
293 $conditional_tags['is_account_page'] = is_account_page();
294 $conditional_tags['order-received'] = is_wc_endpoint_url( 'order-received' );
295 }
296
297 $vars = apply_filters(
298 'hustle_front_vars',
299 array(
300 'conditional_tags' => $conditional_tags,
301 'is_admin' => is_admin(),
302 'real_page_id' => Opt_In_Utils::get_real_page_id(),
303 'thereferrer' => Opt_In_Utils::get_referrer(),
304 'actual_url' => esc_url_raw( Opt_In_Utils::get_current_actual_url() ),
305 'full_actual_url' => esc_url_raw( Opt_In_Utils::get_current_actual_url( true ) ),
306 'native_share_enpoints' => Hustle_SShare_Model::get_sharing_endpoints( false ),
307 'ajaxurl' => admin_url( 'admin-ajax.php', is_ssl() ? 'https' : 'http' ),
308 'page_id' => get_queried_object_id(), // Used in many places to decide whether to show the module and cookies.
309 'page_slug' => $slug, // Used in many places to decide whether to show the module and cookies on archive pages.
310 'is_upfront' => class_exists( 'Upfront' ) && isset( $_GET['editmode'] ) && 'true' === $_GET['editmode'], // phpcs:ignore WordPress.Security.NonceVerification.Recommended
311 'script_delay' => apply_filters( 'hustle_lazy_load_script_delay', 3000 ), // to lazyload script for later on added elements.
312 'display_check_nonce' => wp_create_nonce( 'hustle_display_check' ),
313 'conversion_nonce' => wp_create_nonce( 'hustle_log_conversion' ),
314 'form_submit_nonce' => wp_create_nonce( 'hustle_module_form_submit' ),
315 )
316 );
317
318 $modules_deps = $this->modules_data_for_scripts;
319
320 // Datepicker. Add translated strings only if some module has a datepicker.
321 if ( ! empty( $modules_deps['datepicker'] ) ) {
322 $vars['days_and_months'] = array(
323 'days_full' => Hustle_Time_Helper::get_week_days(),
324 'days_short' => Hustle_Time_Helper::get_week_days( 'short' ),
325 'days_min' => Hustle_Time_Helper::get_week_days( 'min' ),
326 'months_full' => Hustle_Time_Helper::get_months(),
327 'months_short' => Hustle_Time_Helper::get_months( 'short' ),
328 );
329 wp_enqueue_script( 'jquery-ui-datepicker' );
330 }
331 wp_localize_script( 'hustle_front', 'incOpt', $vars );
332
333 do_action( 'hustle_register_scripts' );
334
335 // Queue adblocker if a module requires it.
336 if ( ! empty( $modules_deps['adblocker'] ) ) {
337 wp_enqueue_script(
338 'hustle_front_ads',
339 Opt_In::$plugin_url . 'assets/js/ad.min.js',
340 array(),
341 Opt_In::VERSION,
342 true
343 );
344 }
345
346 // Queue recaptchas if required. Only added if the keys are set.
347 if ( ! empty( $modules_deps['recaptcha'] ) ) {
348 $this->add_recaptcha_script( $modules_deps['recaptcha']['language'] );
349 }
350
351 // Queue Cloudflare Turnstile if required.
352 if ( ! empty( $modules_deps['turnstile'] ) ) {
353 self::add_turnstile_script();
354 }
355
356 // Queue Pinteres if required.
357 if ( ! empty( $modules_deps['pinterest'] ) ) {
358 wp_enqueue_script(
359 'hustle_sshare_pinterest',
360 '//assets.pinterest.com/js/pinit.js',
361 array(),
362 Opt_In::VERSION,
363 true
364 );
365 }
366
367 self::add_hui_scripts();
368 wp_enqueue_script( 'hustle_front' );
369
370 Opt_In_Utils::maybe_add_scripts_for_ie();
371 }
372
373 /**
374 * Add Hustle UI scripts.
375 * Used for displaying and previewing modules.
376 *
377 * @since 4.0
378 */
379 public static function add_hui_scripts() {
380 // Register Hustle UI functions.
381 wp_register_script(
382 'hui_scripts',
383 Opt_In::$plugin_url . 'assets/hustle-ui/js/hustle-ui.min.js',
384 array( 'jquery' ),
385 Opt_In::VERSION,
386 true
387 );
388
389 $settings = array(
390 'mobile_breakpoint' => Hustle_Settings_Admin::get_mobile_breakpoint(),
391 );
392 wp_localize_script( 'hui_scripts', 'hustleSettings', $settings );
393
394 wp_enqueue_script( 'hui_scripts' );
395 }
396
397 /**
398 * Enqueue the Cloudflare Turnstile script.
399 *
400 * @since 7.8.13
401 *
402 * @param bool $is_preview If it's preview.
403 * @param bool $is_return Is return.
404 */
405 public static function add_turnstile_script( $is_preview = false, $is_return = false ) {
406 $script_url = 'https://challenges.cloudflare.com/turnstile/v0/api.js';
407
408 if ( ! $is_return ) {
409 wp_enqueue_script( 'hustle_turnstile', $script_url, array(), null, true ); // phpcs:ignore -- Prevent from adding the version to the script URL. It isn't recommended to cache api.js script because it will cause Turnstile to fail when future updates are released.
410
411 } elseif ( $is_preview ) {
412 return $script_url;
413 }
414 }
415
416 /**
417 * Enqueue the recaptcha script if recaptcha is globally configured.
418 *
419 * @since 4.0
420 * @since 4.0.3 param $recaptcha_versions and $is_preview added
421 *
422 * @param string $language reCAPTCHA language.
423 * @param bool $is_preview if it's preview.
424 * @param bool $is_return Is return.
425 */
426 public static function add_recaptcha_script( $language = '', $is_preview = false, $is_return = false ) {
427
428 $recaptcha_settings = Hustle_Settings_Admin::get_recaptcha_settings();
429
430 if ( empty( $language ) || 'automatic' === $language ) {
431 $language = ! empty( $recaptcha_settings['language'] ) && 'automatic' !== $recaptcha_settings['language']
432 ? $recaptcha_settings['language'] : get_locale();
433 }
434 $script_url = 'https://www.google.com/recaptcha/api.js?render=explicit&hl=' . $language;
435
436 if ( ! $is_return ) {
437 wp_enqueue_script( 'recaptcha', $script_url, array(), 1, true );
438
439 } elseif ( $is_preview ) {
440 return $script_url;
441 }
442 }
443
444 /**
445 * Preload fonts
446 *
447 * @return string
448 */
449 public function preload_custom_font() {
450 if ( ! count( $this->modules ) ) {
451 // There aren't any published modules. We don't need to load fonts.
452 return;
453 }
454 $font_name = Opt_In::$plugin_url . 'assets/hustle-ui/fonts/hustle-icons-font';
455 ?>
456 <link rel="preload" href="<?php echo esc_url( $font_name . '.woff2' ); ?>" as="font" type="font/woff2" crossorigin>
457 <?php
458 }
459
460 /**
461 * Registeres front styles and fonts
462 */
463 public function register_styles() {
464
465 // There aren't any published modules. We don't need styles.
466 if ( ! count( $this->modules ) ) {
467 return;
468 }
469
470 $is_on_upfront_builder = class_exists( 'UpfrontThemeExporter' ) && function_exists( 'upfront_exporter_is_running' ) && upfront_exporter_is_running();
471
472 if ( ! $is_on_upfront_builder ) {
473 if ( ! $this->has_modules() || isset( $_REQUEST['fl_builder'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
474 return;
475 }
476 }
477
478 $module_types_to_display = array_keys( $this->module_types_to_display );
479
480 self::print_front_styles( $module_types_to_display, $this->modules_data_for_scripts );
481 self::print_front_fonts( $this->modules_data_for_scripts['fonts'] );
482 }
483
484 /**
485 * Register and enqueue the required styles according to the given module's types.
486 * The accepted module's types are:
487 * popup, slidein, embedded, social_sharing, optin, informational, floating (ssharing), inline (ssharing).
488 *
489 * @since 4.0
490 * @since 4.0.1 enequeues only the given module's types.
491 * @since 4.2.0 $dependencies param added.
492 *
493 * @param array $module_types_to_display Array with the module's type to be displayed.
494 * @param array $dependencies Array with the module's style dependencies.
495 */
496 public static function print_front_styles( $module_types_to_display = array(), $dependencies = array() ) {
497
498 if ( ! empty( $dependencies['select2'] ) ) {
499 wp_register_style(
500 'select2',
501 Opt_In::$plugin_url . 'assets/css/select2.min.css',
502 array(),
503 '4.0.6'
504 );
505 wp_enqueue_style( 'select2' );
506 }
507
508 wp_register_style(
509 'hustle_icons',
510 Opt_In::$plugin_url . 'assets/hustle-ui/css/hustle-icons.min.css',
511 array(),
512 Opt_In::VERSION
513 );
514 wp_enqueue_style( 'hustle_icons' );
515
516 wp_register_style(
517 'hustle_global',
518 Opt_In::$plugin_url . 'assets/hustle-ui/css/hustle-global.min.css',
519 array(),
520 Opt_In::VERSION
521 );
522 wp_enqueue_style( 'hustle_global' );
523
524 // Informational mode.
525 if ( ! $module_types_to_display || in_array( Hustle_Module_Model::INFORMATIONAL_MODE, $module_types_to_display, true ) ) {
526
527 wp_register_style(
528 'hustle_info',
529 Opt_In::$plugin_url . 'assets/hustle-ui/css/hustle-info.min.css',
530 array(),
531 Opt_In::VERSION
532 );
533 wp_enqueue_style( 'hustle_info' );
534 }
535
536 // Optin mode.
537 if ( ! $module_types_to_display || in_array( Hustle_Module_Model::OPTIN_MODE, $module_types_to_display, true ) ) {
538
539 wp_register_style(
540 'hustle_optin',
541 Opt_In::$plugin_url . 'assets/hustle-ui/css/hustle-optin.min.css',
542 array(),
543 Opt_In::VERSION
544 );
545 wp_enqueue_style( 'hustle_optin' );
546 }
547
548 // Popup type.
549 if ( ! $module_types_to_display || in_array( Hustle_Module_Model::POPUP_MODULE, $module_types_to_display, true ) ) {
550
551 wp_register_style(
552 'hustle_popup',
553 Opt_In::$plugin_url . 'assets/hustle-ui/css/hustle-popup.min.css',
554 array(),
555 Opt_In::VERSION
556 );
557 wp_enqueue_style( 'hustle_popup' );
558 }
559
560 // Slidein type.
561 if ( ! $module_types_to_display || in_array( Hustle_Module_Model::SLIDEIN_MODULE, $module_types_to_display, true ) ) {
562
563 wp_register_style(
564 'hustle_slidein',
565 Opt_In::$plugin_url . 'assets/hustle-ui/css/hustle-slidein.min.css',
566 array(),
567 Opt_In::VERSION
568 );
569 wp_enqueue_style( 'hustle_slidein' );
570 }
571
572 $has_social_sharing_module = in_array( Hustle_Module_Model::SOCIAL_SHARING_MODULE, $module_types_to_display, true );
573 $has_embedded_module = in_array( Hustle_Module_Model::EMBEDDED_MODULE, $module_types_to_display, true );
574
575 // Social share and Embedded both need hustle-inline CSS.
576 if ( ! $module_types_to_display || $has_social_sharing_module || $has_embedded_module ) {
577 wp_register_style(
578 'hustle_inline',
579 Opt_In::$plugin_url . 'assets/hustle-ui/css/hustle-inline.min.css',
580 array(),
581 Opt_In::VERSION
582 );
583 }
584
585 if ( ! $module_types_to_display || $has_embedded_module ) {
586 wp_enqueue_style( 'hustle_inline' );
587 }
588
589 // SSharing type.
590 if ( ! $module_types_to_display || $has_social_sharing_module ) {
591
592 wp_register_style(
593 'hustle_social',
594 Opt_In::$plugin_url . 'assets/hustle-ui/css/hustle-social.min.css',
595 array(),
596 Opt_In::VERSION
597 );
598 wp_enqueue_style( 'hustle_social' );
599
600 // Inline display.
601 if ( ! $module_types_to_display || in_array( Hustle_SShare_Model::INLINE_MODULE, $module_types_to_display, true ) ) {
602
603 wp_enqueue_style( 'hustle_inline' );
604 }
605
606 // Floating display.
607 if ( ! $module_types_to_display || in_array( Hustle_SShare_Model::FLOAT_MODULE, $module_types_to_display, true ) ) {
608
609 wp_register_style(
610 'hustle_float',
611 Opt_In::$plugin_url . 'assets/hustle-ui/css/hustle-float.min.css',
612 array(),
613 Opt_In::VERSION
614 );
615 wp_enqueue_style( 'hustle_float' );
616 }
617 }
618 }
619
620 /**
621 * Enqueues the required Google fonts to be included in front.
622 *
623 * @since unknown
624 * @param array $fonts Fonts.
625 * @param bool $is_ajax Is ajax.
626 * @return void|string
627 */
628 public static function print_front_fonts( $fonts, $is_ajax = false ) {
629 if ( empty( $fonts ) ) {
630 return;
631 }
632
633 $families_args = array();
634 foreach ( $fonts as $font_family => $variations ) {
635 $families_args[] = $font_family . ':' . implode( ',', array_unique( $variations ) );
636 }
637
638 // The final URL must have a 'family' parameter with all font families and variations
639 // formatted like ?family=Tangerine:bold,bolditalic|Inconsolata:italic|Droid+Sans .
640 $google_font_url = add_query_arg(
641 array(
642 'family' => implode( '|', $families_args ),
643 'display' => 'swap',
644 ),
645 'https://fonts.bunny.net/css'
646 );
647
648 $id = 'hustle-fonts';
649 if ( ! $is_ajax ) {
650 wp_enqueue_style( $id, $google_font_url, array(), '1.0' );
651 } else {
652 // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet
653 return '<link rel="stylesheet" id="' . $id . '" href="' . esc_url( $google_font_url ) . '" media="all">';
654 }
655 }
656
657 /**
658 * Enqueue modules to be displayed on Frontend.
659 */
660 public function create_modules() {
661
662 // Retrieve all active modules.
663 $modules = apply_filters( 'hustle_sort_modules', Hustle_Module_Collection::instance()->get_all( true ) );
664 $datepicker_found = false;
665 $recaptcha_found = false;
666 $turnstile_found = false;
667 $select2_found = false;
668 $recaptcha_language = '';
669 $enqueue_adblock = false;
670 $pinterest_found = false;
671
672 /**
673 * Disables the load of Google fonts in frontend.
674 *
675 * @since unknown
676 *
677 * @param bool Whether Google fonts should be used.
678 */
679 $use_google_fonts = apply_filters( 'hustle_load_google_fonts', true );
680 $google_fonts = array();
681
682 foreach ( $modules as $module ) {
683
684 if ( ! $module instanceof Hustle_Model || ! $module->active ) {
685 continue;
686 }
687
688 $is_non_inline_module = ( Hustle_Module_Model::POPUP_MODULE === $module->module_type || Hustle_Module_Model::SLIDEIN_MODULE === $module->module_type );
689
690 $avoid_static_cache = Opt_In_Utils::is_static_cache_enabled();
691 // Check `is_condition_allow` first to set self::$use_count_cookie.
692 if ( ! $module->is_condition_allow() && ! $avoid_static_cache ) {
693 // If shortcode is enabled for inline modules, don't abort.
694 // Shortcodes shouldn't follow the visibility conditions.
695 if ( ! $is_non_inline_module ) {
696 $display_options = $module->get_display()->to_array();
697 if ( '1' !== $display_options['shortcode_enabled'] ) {
698 continue;
699 }
700 } else {
701 continue;
702 }
703 }
704
705 // Setting up stuff for all modules except social sharing.
706 if ( Hustle_Module_Model::SOCIAL_SHARING_MODULE !== $module->module_type ) {
707 $settings = $module->get_settings();
708 // Check the schedule. Ssharing modules don't have schedules.
709 if ( ! $avoid_static_cache && ! $settings->is_currently_scheduled() ) {
710 continue;
711 }
712 $module->load();
713
714 // Skip if Google fonts were deativated via hook.
715 if ( $use_google_fonts ) {
716 $google_fonts = array_merge_recursive( $google_fonts, $module->get_google_fonts() );
717 }
718
719 if ( 'optin' === $module->module_mode ) {
720
721 if ( ! $datepicker_found || empty( $recaptcha_language ) ) {
722 $form_fields = $module->get_form_fields();
723
724 // Datepicker.
725 // Check if the module has a datepicker unless we already found one in other modules.
726 // We'll localize some variables if the modules have a datepicker.
727 if ( ! $datepicker_found ) {
728 $field_types = wp_list_pluck( $form_fields, 'type', true );
729 $datepicker_found = in_array( 'datepicker', $field_types, true );
730 }
731
732 // Recaptcha.
733 // Check if the module has a recaptcha to enqueue scripts unless we already found one.
734 // We'll queue the script afterwards.
735 if ( ! empty( $form_fields['recaptcha'] ) && empty( $recaptcha_language ) ) {
736
737 $recaptcha_found = true;
738
739 $recaptcha_field = $form_fields['recaptcha'];
740 // Get only first recaptcha language. Skip if not set or it's "automatic".
741 if ( ! empty( $recaptcha_field['recaptcha_language'] ) && 'automatic' !== $recaptcha_field['recaptcha_language'] ) {
742 $recaptcha_language = $recaptcha_field['recaptcha_language'];
743 }
744 }
745
746 // Cloudflare Turnstile.
747 // Check if the module has a Turnstile to enqueue scripts unless we already found one.
748 // We'll queue the script afterwards.
749 if ( ! empty( $form_fields['turnstile'] ) && ! $turnstile_found ) {
750 $turnstile_found = true;
751 }
752 }
753
754 // Select2.
755 // We're only using select2 for Mailchimp dropdown groups.
756 if ( ! $select2_found ) {
757 $mailchimp_settings = $module->get_provider_settings( 'mailchimp' );
758 if (
759 ! empty( $mailchimp_settings ) &&
760 ! is_null( $mailchimp_settings['group'] ) &&
761 '-1' !== $mailchimp_settings['group']
762 ) {
763 if (
764 isset( $mailchimp_settings['group_type'] ) &&
765 'dropdown' === $mailchimp_settings['group_type']
766 ) {
767 $select2_found = true;
768 }
769 }
770 }
771 }
772
773 // For popups and slideins.
774 if ( $is_non_inline_module ) {
775 $this->non_inline_modules[ $module->module_id ] = $module;
776
777 if ( ! $enqueue_adblock ) {
778
779 $settings = $settings->to_array();
780
781 // If trigger is adblock.
782 if ( in_array( 'adblock', $settings['triggers']['trigger'], true ) ) {
783 $enqueue_adblock = true;
784 }
785 }
786 } elseif ( Hustle_Module_Model::EMBEDDED_MODULE === $module->module_type ) {
787 $this->inline_modules[ $module->module_id ] = $module;
788 }
789 } else { // Social sharing modules.
790 $ssharing_networks = $module->get_content()->get_social_icons();
791 if (
792 ! empty( $ssharing_networks )
793 && in_array( 'pinterest', array_keys( $ssharing_networks ), true )
794 && empty( $ssharing_networks['pinterest']['link'] )
795 ) {
796 $pinterest_found = true;
797 }
798 $this->inline_modules[ $module->module_id ] = $module;
799
800 $this->non_inline_modules[ $module->module_id ] = $module;
801 }
802
803 $this->log_module_type_to_load_styles( $module );
804
805 $this->modules[] = $module->get_module_data_to_display();
806
807 } // End looping through the modules.
808
809 // Set flag for scripts: datepicker field.
810 if ( $datepicker_found ) {
811 $this->modules_data_for_scripts['datepicker'] = true;
812 }
813
814 // Set flag for scripts: adblocker.
815 if ( $enqueue_adblock ) {
816 $this->modules_data_for_scripts['adblocker'] = true;
817 }
818
819 // Set flag for scripts: select2.
820 if ( $select2_found ) {
821 $this->modules_data_for_scripts['select2'] = true;
822 }
823
824 // Set flag for scripts: recaptcha field.
825 if ( $recaptcha_found ) {
826 $this->modules_data_for_scripts['recaptcha'] = array( 'language' => $recaptcha_language );
827 }
828
829 // Set flag for scripts: Cloudflare Turnstile field.
830 if ( $turnstile_found ) {
831 $this->modules_data_for_scripts['turnstile'] = true;
832 }
833
834 if ( $pinterest_found ) {
835 $this->modules_data_for_scripts['pinterest'] = true;
836 }
837
838 // Before removing it in future - check shortcode method - it's a flag there.
839 $this->modules_data_for_scripts['fonts'] = $google_fonts;
840 }
841
842 /**
843 * Store the modules' types to be displayed in order to enqueue
844 * their required styles.
845 * Called within self::create_modules() method.
846 *
847 * @since 4.0.1
848 *
849 * @param Hustle_Model $module Current module to check.
850 */
851 private function log_module_type_to_load_styles( Hustle_Model $module ) {
852
853 // Keep track of the of the modules types and modes to display
854 // in order to queue the required styles only.
855 $this->module_types_to_display[ $module->module_type ] = 1;
856
857 // Register the module mode for non SSharing modules.
858 if ( Hustle_Module_Model::SOCIAL_SHARING_MODULE !== $module->module_type ) {
859 $this->module_types_to_display[ $module->module_mode ] = 1;
860
861 } else { // Register the module display type for SSharing modules.
862
863 // Floating display.
864 if (
865 $module->is_display_type_active( Hustle_SShare_Model::FLOAT_MOBILE ) ||
866 $module->is_display_type_active( Hustle_SShare_Model::FLOAT_DESKTOP )
867 ) {
868 $this->module_types_to_display[ Hustle_SShare_Model::FLOAT_MODULE ] = 1;
869 }
870
871 // Inline display.
872 if (
873 $module->is_display_type_active( Hustle_SShare_Model::INLINE_MODULE ) ||
874 $module->is_display_type_active( Hustle_SShare_Model::WIDGET_MODULE ) ||
875 $module->is_display_type_active( Hustle_SShare_Model::SHORTCODE_MODULE )
876 ) {
877 $this->module_types_to_display[ Hustle_SShare_Model::INLINE_MODULE ] = 1;
878 }
879 }
880 }
881
882 /**
883 * Check if current page has renderable opt-ins.
884 **/
885 public function has_modules() {
886 $has_modules = ! empty( $this->non_inline_modules ) || ! empty( $this->inline_modules );
887 return apply_filters( 'hustle_front_handler', $has_modules );
888 }
889
890 /**
891 * By-pass NextGEN Gallery resource manager
892 *
893 * @return false
894 */
895 public function nextgen_compat() {
896 return false;
897 }
898
899 /**
900 * Render non inline modules
901 */
902 public function render_non_inline_modules() {
903 $html = '';
904
905 foreach ( $this->non_inline_modules as $module ) {
906
907 if ( Hustle_Module_Model::SOCIAL_SHARING_MODULE !== $module->module_type ) {
908 $html .= $module->display();
909 } elseif ( $module->is_display_type_active( Hustle_SShare_Model::FLOAT_DESKTOP ) || $module->is_display_type_active( Hustle_SShare_Model::FLOAT_MOBILE ) ) {
910 $html .= $module->display( Hustle_SShare_Model::FLOAT_MODULE );
911 }
912 }
913
914 add_action(
915 'wp_footer',
916 function () use ( $html ) {
917 echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
918 }
919 );
920 }
921
922 /**
923 * Handles the data for the unsubscribe shortcode
924 *
925 * @since 3.0.5
926 * @param array $atts The values passed through the shortcode attributes.
927 * @return string The content to be rendered within the shortcode.
928 */
929 public function unsubscribe_shortcode( $atts ) {
930 $messages = Hustle_Settings_Admin::get_unsubscribe_messages();
931 $email = filter_input( INPUT_GET, 'email', FILTER_VALIDATE_EMAIL );
932 $nonce = filter_input( INPUT_GET, 'token', FILTER_SANITIZE_SPECIAL_CHARS );
933 if ( $nonce && $email ) {
934 $error_message = $messages['invalid_data'];
935
936 $entry = new Hustle_Entry_Model();
937 $unsubscribed = $entry->unsubscribe_email( $email, $nonce );
938 if ( $unsubscribed ) {
939 return $messages['successful_unsubscription'];
940 } else {
941 return $error_message;
942 }
943 }
944 // Show all modules' lists by default.
945 $attributes = shortcode_atts(
946 array(
947 'id' => '-1',
948 'skip_confirmation' => false,
949 ),
950 $atts
951 );
952 $params = array(
953 'ajax_step' => false,
954 'shortcode_attr_id' => $attributes['id'],
955 'skip_confirmation' => $attributes['skip_confirmation'],
956 'messages' => $messages,
957 );
958
959 $renderer = new Hustle_Layout_Helper();
960 $html = $renderer->render( 'general/unsubscribe-form', $params, true );
961 $html = apply_filters( 'hustle_render_unsubscribe_form_html', $html, $params );
962 return $html;
963 }
964
965 /**
966 * Get unsubscribe form.
967 */
968 public function get_unsubscribe_form() {
969 Opt_In_Utils::validate_ajax_call( 'hustle_gutenberg_get_unsubscribe_form' );
970
971 $atts = array();
972 $ids = filter_input( INPUT_GET, 'module_ids', FILTER_SANITIZE_SPECIAL_CHARS );
973 $skip = filter_input( INPUT_GET, 'skip_confirmation', FILTER_VALIDATE_BOOLEAN );
974
975 if ( $ids ) {
976 $atts['id'] = $ids;
977 }
978
979 if ( $skip ) {
980 $atts['skip_confirmation'] = true;
981 }
982
983 $html = $this->unsubscribe_shortcode( $atts );
984
985 wp_send_json_success( $html );
986 }
987
988 /**
989 * Render the modules' wrapper to render the actual module using their shortcodes.
990 *
991 * @since the beginning of time.
992 *
993 * @param array $atts Attrs.
994 * @param string $content Content.
995 * @return string
996 */
997 public function shortcode( $atts, $content ) {
998 $atts = shortcode_atts(
999 array(
1000 'id' => '',
1001 'type' => 'embedded',
1002 'css_class' => '',
1003 ),
1004 $atts,
1005 self::SHORTCODE
1006 );
1007
1008 if ( empty( $atts['id'] ) ) {
1009 return '';
1010 }
1011
1012 if ( ! $this->modules_data_for_scripts ) {
1013 // This case for AJAX.
1014 $this->create_modules();
1015 }
1016
1017 $type = $atts['type'];
1018
1019 // If shortcode type is not embed or sshare.
1020 if ( 'embedded' !== $type && 'social_sharing' !== $type ) {
1021 // Do not enforce embedded/social_sharing type.
1022 $enforce_type = false;
1023 } else {
1024 // Enforce embedded/social_sharing type.
1025 $enforce_type = true;
1026 }
1027
1028 $module_id = Hustle_Model::get_module_id_by_shortcode_id( $atts['id'] );
1029
1030 $custom_classes = esc_attr( $atts['css_class'] );
1031
1032 if ( isset( $this->inline_modules[ $module_id ] ) ) {
1033 $module = $this->inline_modules[ $module_id ];
1034
1035 if ( ! $module->is_display_type_active( Hustle_Module_Model::SHORTCODE_MODULE ) ) {
1036 return '';
1037 }
1038
1039 // Display the module.
1040 return $module->display( Hustle_Module_Model::SHORTCODE_MODULE, $custom_classes );
1041 }
1042
1043 if ( isset( $this->non_inline_modules[ $module_id ] ) && ! empty( $content ) ) {
1044 $module = $this->non_inline_modules[ $module_id ];
1045
1046 // If shortcode click trigger is disabled, print nothing.
1047 $settings = $module->get_settings()->to_array();
1048 if ( ! isset( $settings['triggers']['enable_on_click_shortcode'] ) || '0' === $settings['triggers']['enable_on_click_shortcode'] ) {
1049 return '';
1050 }
1051
1052 return sprintf(
1053 '<a href="javascript:void(0)" class="%s hustle_module_%s %s" data-id="%s" data-type="%s">%s</a>',
1054 'hustle_module_shortcode_trigger',
1055 esc_attr( $module->id ),
1056 esc_attr( $custom_classes ),
1057 esc_attr( $module->id ),
1058 esc_attr( $type ),
1059 wp_kses_post( $content )
1060 );
1061 }
1062
1063 return '';
1064 }
1065
1066 /**
1067 * Display inline modules.
1068 * Embedded and Social Sharing modules only.
1069 *
1070 * @since the beginning of time.
1071 *
1072 * @param string $content Content.
1073 * @return string
1074 */
1075 public function show_after_page_post_content( $content ) {
1076
1077 // Return the content immediately if there are no modules or the page doesn't have a content to embed into.
1078 if ( ! count( $this->inline_modules ) || isset( $_REQUEST['fl_builder'] ) || is_home() || is_archive() ) {// phpcs:ignore WordPress.Security.NonceVerification.Recommended
1079 return $content;
1080 }
1081
1082 $in_the_loop = in_the_loop();
1083
1084 /**
1085 * Filters whether to skip inline modules because we're not in da loop.
1086 * This can also be used to prevent loading the inline modules conditionally.
1087 *
1088 * @param bool $in_the_loop Returned value from WordPress' in_the_loop() function.
1089 * @since 4.2.0
1090 */
1091 $is_in_da_loop = apply_filters( 'hustle_inline_modules_render_in_the_loop', $in_the_loop );
1092
1093 // We only render the inline modules in the first call of 'the_content' filter.
1094 // Prevent the modules from being printed when they shouldn't and
1095 // leaving the page's main content without them.
1096 if ( ! $is_in_da_loop ) {
1097 return $content;
1098 }
1099
1100 $modules = apply_filters( 'hustle_inline_modules_to_display', $this->inline_modules );
1101
1102 foreach ( $modules as $module ) {
1103
1104 // Skip if "inline" display is disabled.
1105 if ( ! $module->is_display_type_active( Hustle_Module_Model::INLINE_MODULE ) ) {
1106 continue;
1107 }
1108
1109 $custom_classes = apply_filters( 'hustle_inline_module_custom_classes', '', $module );
1110 $module_markup = $module->display( Hustle_Module_Model::INLINE_MODULE, $custom_classes );
1111
1112 $display = $module->get_display()->to_array();
1113 $display_position = $display['inline_position'];
1114
1115 if ( 'both' === $display_position ) {
1116 $content = $module_markup . $content . $module_markup;
1117
1118 } elseif ( 'above' === $display_position ) {
1119 $content = $module_markup . $content;
1120
1121 } else { // Below.
1122 $content .= $module_markup;
1123
1124 }
1125 }
1126
1127 $is_render_inline_once = true;
1128 /**
1129 * Filters whether to render the inline modules once.
1130 * By default, we only render the inline modules in the first call of 'the_content' filter.
1131 * Allow rendering them in following calls of the filter if needed.
1132 *
1133 * @param bool $is_render_inline_once Whether to render the inline modules in several calls.
1134 * @since 4.2.0
1135 */
1136 $is_render_inline_once = apply_filters( 'hustle_inline_modules_render_once', $is_render_inline_once );
1137
1138 if ( $is_render_inline_once ) {
1139 remove_filter( 'the_content', array( $this, 'show_after_page_post_content' ), self::$the_content_filter_priority );
1140 }
1141
1142 return $content;
1143 }
1144
1145 /**
1146 * If new post content includes unsubscribe shortcode - safe the post URL.
1147 *
1148 * @param int $post_ID Post ID.
1149 * @param WP_Post $post_after Post object following the update.
1150 * @param WP_Post $post_before Post object before the update.
1151 * @return null|void
1152 */
1153 public static function maybe_unsubscribe_page( $post_ID, $post_after, $post_before ) {
1154 if ( ! strpos( $post_after->post_content, 'wd_hustle_unsubscribe' ) ) {
1155 return;
1156 }
1157
1158 $post_url = get_permalink( $post_after );
1159 if ( $post_url ) {
1160 update_option( 'hustle_unsubscribe_page', $post_url );
1161 }
1162 }
1163 }
1164