PluginProbe ʕ •ᴥ•ʔ
Hustle – Email Marketing, Lead Generation, Optins, Popups / trunk
Hustle – Email Marketing, Lead Generation, Optins, Popups vtrunk
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-renderer.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 1 month ago hustle-module-front.php 1 month 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 5 months ago
hustle-module-renderer.php
2273 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Hustle_Module_Renderer
4 *
5 * @package Hustle
6 */
7
8 /**
9 * Class Hustle_Module_Renderer
10 * Used to render Embedded, Popup, and Slidein modules.
11 *
12 * @since 4.0
13 */
14 class Hustle_Module_Renderer extends Hustle_Renderer_Abstract {
15
16 /**
17 * Is optin
18 *
19 * @var bool
20 */
21 protected $is_optin = null;
22
23 /**
24 * Whether the module is "optin" or "informational.
25 *
26 * @since 4.0
27 *
28 * @return boolean
29 */
30 protected function is_optin() {
31
32 if ( is_null( $this->is_optin ) ) {
33 $this->is_optin = ( 'optin' === $this->module->module_mode );
34 }
35
36 return $this->is_optin;
37 }
38
39 /**
40 * Get main wrapper
41 *
42 * @since 4.0
43 * @param string $subtype Sub types.
44 * @param string $custom_classes Custom classes.
45 * @return string
46 */
47 public function get_wrapper_main( $subtype, $custom_classes = '' ) {
48
49 $content = $this->module->content;
50 $design = $this->module->design;
51 $settings = $this->module->settings;
52 $module_type = $this->module->module_type;
53 $module_subtype = $subtype ? $subtype : $module_type;
54
55 $id = $this->module->module_id;
56 $module_id = sprintf( 'hustle-%s-id-%d', $module_type, $id );
57 $module_id_class = sprintf(
58 'hustle_module_id_%d module_id_%d',
59 $id,
60 $id
61 );
62
63 $module_palette = str_replace( '.', '_', $design->color_palette );
64
65 $tracking_enabled_data = $this->module->is_tracking_enabled( $module_subtype ) ? 'enabled' : 'disabled';
66 $module_data = sprintf(
67 '
68 data-id="%d"
69 data-render-id="%d"
70 data-tracking="%s"
71 ',
72 $id,
73 self::$render_ids[ $id ],
74 esc_attr( $tracking_enabled_data )
75 );
76
77 if ( Hustle_Module_Model::EMBEDDED_MODULE === $module_type ) {
78
79 $module_type = 'inline';
80 $animation_intro = ( '' !== $settings->animation_in ) ? $settings->animation_in : 'no_animation';
81
82 $module_data .= sprintf(
83 '
84 data-intro="%s"
85 data-sub-type="%s"
86 ',
87 esc_attr( $animation_intro ),
88 esc_attr( $subtype )
89 );
90
91 if ( '1' === $design->customize_size ) {
92
93 if ( '' !== $design->custom_width || '' !== $design->custom_height ) {
94 $custom_classes .= ' hustle-size--custom';
95 }
96 }
97 } elseif ( Hustle_Module_Model::POPUP_MODULE === $module_type ) {
98
99 $animation_intro = ( '' !== $settings->animation_in ) ? $settings->animation_in : 'no_animation';
100 $animation_outro = ( '' !== $settings->animation_out ) ? $settings->animation_out : 'no_animation';
101
102 $auto_close = '1' === $settings->auto_hide ? Hustle_Time_Helper::to_microseconds( $settings->auto_hide_time, $settings->auto_hide_unit ) : 'false';
103
104 // TODO: remove when the preview view is updated.
105 // This is forced on preview so modules like 'Adblock' can still be closed when previewing.
106 $overlay_can_close = ! self::$is_preview ? $settings->close_on_background_click : '1';
107
108 $module_data .= sprintf(
109 '
110 role="dialog"
111 aria-modal="true"
112 data-intro="%s"
113 data-outro="%s"
114 data-overlay-close="%s"
115 data-close-delay="%s"
116 ',
117 esc_attr( $animation_intro ),
118 esc_attr( $animation_outro ),
119 esc_attr( $overlay_can_close ),
120 esc_attr( $auto_close )
121 );
122
123 } elseif ( Hustle_Module_Model::SLIDEIN_MODULE === $module_type ) {
124
125 $position = $settings->display_position;
126
127 $auto_close = '1' === $settings->auto_hide ? Hustle_Time_Helper::to_microseconds( $settings->auto_hide_time, $settings->auto_hide_unit ) : 'false';
128
129 $module_data .= sprintf(
130 '
131 role="dialog"
132 aria-modal="true"
133 data-position="%s"
134 data-close-delay="%s"
135 ',
136 esc_attr( $position ),
137 esc_attr( $auto_close )
138 );
139
140 if ( '1' === $design->customize_size ) {
141
142 if ( '' !== $design->custom_width || '' !== $design->custom_height ) {
143 $custom_classes .= ' hustle-size--custom';
144 }
145 }
146 }
147
148 $image_class = '';
149
150 if (
151 '' !== $content->feature_image && // Feat image exists.
152 (
153 '' === $content->title && // Title is empty.
154 '' === $content->sub_title && // Sub-title is empty.
155 '' === $content->main_content && // Content is empty.
156 $this->is_show_cta( $content ) // CTA button is hidden.
157 )
158 ) {
159 $image_class = 'hustle-image-only';
160 }
161
162 $inline_style = ! self::$is_preview ? 'style="opacity: 0;"' : 'style="opacity: 1;"';
163 $aria_label = $content->title . ' ' . esc_html__( 'popup', 'hustle' );
164
165 $html = sprintf(
166 '<div
167 id="%s"
168 class="hustle-ui hustle-%s hustle-palette--%s %s %s %s"
169 %s
170 %s
171 aria-label="%s"
172 >',
173 esc_attr( $module_id ),
174 esc_attr( $module_type ),
175 esc_attr( $module_palette ),
176 esc_attr( $module_id_class ),
177 esc_attr( $image_class ),
178 esc_attr( $custom_classes ),
179 $module_data,
180 $inline_style,
181 $aria_label,
182 );
183
184 return $html;
185 }
186
187 /**
188 * Get content wrapper
189 *
190 * @since 4.0
191 * @return string
192 */
193 protected function get_wrapper_content() {
194
195 $module_type = $this->module->module_type;
196
197 if ( Hustle_Module_Model::EMBEDDED_MODULE === $this->module->module_type ) {
198 $module_type = 'inline';
199 }
200
201 $html = sprintf(
202 '<div class="hustle-%s-content">',
203 esc_attr( $module_type )
204 );
205
206 return $html;
207 }
208
209 /**
210 * Get the right body depending if the module is "optin" or "informational".
211 *
212 * @since 4.0
213 * @return string
214 */
215 public function get_module_body() {
216
217 if ( $this->is_optin() ) {
218 $html = $this->get_optin_body();
219 } else {
220 $html = $this->get_informational_body();
221 }
222
223 return $html;
224 }
225
226 /**
227 * Get an overlay mask for pop-ups only.
228 *
229 * @since 4.0
230 * @return string
231 */
232 protected function get_overlay_mask() {
233
234 if ( Hustle_Module_Model::POPUP_MODULE !== $this->module->module_type ) {
235 $overlay = '';
236 } else {
237 $overlay = '<div class="hustle-popup-mask hustle-optin-mask" aria-hidden="true"></div>';
238 }
239
240 return $overlay;
241 }
242
243 /**
244 * Get close button for pop-ups and slide-ins only.
245 *
246 * @since 4.0
247 * @return string
248 */
249 private function get_close_button() {
250
251 $button = '<button class="hustle-button-icon hustle-button-close has-background">
252 <span class="hustle-icon-close" aria-hidden="true"></span>
253 <span class="hustle-screen-reader">Close this module</span>
254 </button>';
255
256 if ( Hustle_Module_Model::EMBEDDED_MODULE === $this->module->module_type ) {
257 $button = '';
258 }
259
260 $html = $button;
261
262 return $html;
263 }
264
265 /**
266 * Get feature image markup.
267 *
268 * @since 4.0
269 * @return string
270 */
271 private function get_feature_image() {
272
273 $html = '';
274 $design = $this->module->design;
275 $source = esc_url( $this->module->content->feature_image );
276 $position = '';
277 $mobile_hide = '';
278
279 if ( 'custom' !== $design->feature_image_horizontal_position || 'custom' !== $design->feature_image_vertical_position ) {
280
281 $x_axis = '';
282 $y_axis = '';
283
284 if ( 'custom' !== $design->feature_image_horizontal_position ) {
285 $x_axis = $design->feature_image_horizontal_position;
286 } else {
287 $x_axis = 'custom';
288 }
289
290 if ( 'custom' !== $design->feature_image_vertical_position ) {
291 $y_axis = $design->feature_image_vertical_position;
292 } else {
293 $y_axis = 'custom';
294 }
295
296 // Legacy class. We're not making use of it as of 4.3.0.
297 // Not removing it in case users are using it for custom css.
298 $position .= sprintf(
299 ' class="hustle-image-position--%s%s"',
300 esc_attr( $x_axis ),
301 esc_attr( $y_axis )
302 );
303 }
304
305 if ( '1' === $design->enable_mobile_settings && '1' === $design->feature_image_hide_on_mobile ) {
306 $mobile_hide = ' hustle-hide-until-sm';
307 }
308
309 $alt = $this->module->get_feature_image_alt();
310
311 $html .= sprintf(
312 '<div class="hustle-image hustle-image-fit--%s%s" aria-hidden="true">',
313 esc_attr( $design->feature_image_fit ),
314 esc_attr( $mobile_hide )
315 );
316 $html .= sprintf(
317 '<img src="%s" alt="%s"%s />',
318 esc_url( $source ),
319 esc_attr( $alt ),
320 $position
321 );
322 $html .= '</div>';
323
324 return $html;
325 }
326
327 /**
328 * Check whether the CTA should be shown.
329 *
330 * @since 4.3.0
331 *
332 * @param object $content The stored module's content meta settings.
333 * @return boolean
334 */
335 private function is_show_cta( $content ) {
336
337 // If CTA is disabled.
338 if ( '0' === $content->show_cta ) {
339 return false;
340 }
341
342 // Checks for when 1 and 2 CTAs are enabled.
343 if ( '1' === $content->show_cta ) {
344
345 // Make sure it has a label.
346 // And make sure it has an URL if its action isn't to close the module.
347 if ( '' === $content->cta_label ) {
348 return false;
349
350 } elseif ( 'close' !== $content->cta_target && '' === $content->cta_url ) {
351 return false;
352 }
353 } else {
354 // Make both buttons have a label.
355 if ( '' === $content->cta_label || '' === $content->cta_two_label ) {
356 return false;
357 }
358
359 // And make sure they have an URL if their action isn't to close the module.
360 if (
361 ( 'close' !== $content->cta_target && '' === $content->cta_url ) ||
362 ( 'close' !== $content->cta_two_target && '' === $content->cta_two_url )
363 ) {
364 return false;
365 }
366 }
367
368 return true;
369 }
370
371 /**
372 * Returns whether the primary CTA can be used as a whole-module trigger.
373 *
374 * Whole-module CTA can intentionally hide the visible button label, so we
375 * only require that the CTA action itself is actionable.
376 *
377 * @since 4.3.0
378 *
379 * @param object $content The stored module's content meta settings.
380 * @return boolean
381 */
382 private function has_whole_cta( $content ) {
383
384 if ( '1' !== $content->show_cta || empty( $content->cta_whole ) ) {
385 return false;
386 }
387
388 if ( 'close' === $content->cta_target ) {
389 return true;
390 }
391
392 return '' !== $content->cta_url;
393 }
394
395 /**
396 * Get call to action button.
397 *
398 * @since 4.0.0
399 * @return string
400 */
401 private function get_cta_button() {
402
403 $label = $this->module->content->cta_label;
404 $target = $this->module->content->cta_target;
405 $link = $this->module->content->cta_url;
406
407 $html = '<div class="hustle-cta-container">';
408 $html .= $this->get_cta_markup( $label, $target, $link );
409
410 if ( '2' === $this->module->content->show_cta ) {
411 $label_two = $this->module->content->cta_two_label;
412 $target_two = $this->module->content->cta_two_target;
413 $link_two = $this->module->content->cta_two_url;
414 // CTA #2.
415 $html .= $this->get_cta_markup(
416 $label_two,
417 $target_two,
418 $link_two,
419 'cta_2'
420 );
421 }
422 $html .= '</div>';
423
424 // Display CTA helper text if enabled and not empty.
425 if ( '1' === $this->module->content->cta_helper_show && '' !== $this->module->content->cta_helper_text ) {
426 $allowed_html = array(
427 'a' => array(
428 'href' => true,
429 'title' => true,
430 'target' => true,
431 'alt' => true,
432 ),
433 'b' => array(),
434 'strong' => array(),
435 'i' => array(),
436 'em' => array(),
437 'del' => array(),
438 );
439
440 $cta_helper = '<p class="hustle-cta-helper-text">' . wp_kses( $this->module->content->cta_helper_text, $allowed_html ) . '</p>';
441
442 /**
443 * Filter the whole markup for the CTA helper text
444 *
445 * @since 4.3.0
446 *
447 * @param $cta_helper CTA helper text markup to be shown.
448 */
449 $html .= apply_filters( 'hustle_get_cta_helper_text', $cta_helper );
450 }
451
452 /**
453 * Filter the markup for the CTA buttons.
454 *
455 * @since 4.3.0
456 *
457 * @param $html The markup.
458 * @param $this->module The current module.
459 */
460 return apply_filters( 'hustle_get_cta_buttons', $html, $this->module );
461 }
462
463 /**
464 * Get the markup of each CTA.
465 *
466 * @since 4.3.0
467 *
468 * @param string $label Label.
469 * @param string $target Target.
470 * @param string $url URL.
471 * @param string $cta_type CTA type.
472 * @return string
473 */
474 private function get_cta_markup( $label, $target, $url, $cta_type = 'cta' ) {
475
476 $extra_class = 'cta_2' === $cta_type ? 'hustle-last-button' : '';
477 $class = 'hustle-cta-close hustle-button-close ' . $extra_class;
478 $data = '';
479 $label = $this->input_sanitize( $label );
480
481 if ( 'close' !== $target ) {
482 $data = sprintf( 'href="%s" target="_%s"', esc_url( $url ), esc_attr( $target ) );
483 $class = $extra_class;
484 } else {
485 $data = 'href="#"';
486 }
487
488 /**
489 * Filter the extra classes for the CTA
490 *
491 * @since 4.3.0
492 *
493 * @param $class The class to be added to the button.
494 * @param $is_second Whether it's the CTA #2.
495 * @param $target The target.
496 */
497 $class = apply_filters( 'hustle_cta_extra_classes', $class, $cta_type, $target );
498 $html = '';
499 $html .= sprintf( '<a class="hustle-button hustle-button-cta %1$s" %2$s %3$s>', esc_attr( $class ), $data, 'data-cta-type="' . esc_attr( $cta_type ) . '"' );
500 $html .= stripcslashes( $label );
501 $html .= '</a>';
502
503 /**
504 * Filter the markup each CTA.
505 *
506 * @since 4.3.0
507 *
508 * @param $html The markup.
509 * @param $label The label.
510 * @param $target The target.
511 * @param $url You guess.
512 * @param $class Extra classes for the button.
513 */
514 return apply_filters( 'hustle_get_cta_html', $html, $label, $target, $url, $class );
515 }
516
517 /**
518 * Adds an off-screen CTA target for whole-module CTA when the visible button
519 * is intentionally hidden by leaving its label empty.
520 *
521 * @since 4.3.0
522 * @return string
523 */
524 private function maybe_get_hidden_whole_cta_button() {
525 $content = $this->module->content;
526
527 if ( ! $this->has_whole_cta( $content ) || $this->is_show_cta( $content ) ) {
528 return '';
529 }
530
531 $target = $content->cta_target;
532 $url = $content->cta_url;
533 $class = '';
534 $data = '';
535
536 if ( 'close' !== $target ) {
537 $data = sprintf( 'href="%s" target="_%s"', esc_url( $url ), esc_attr( $target ) );
538 } else {
539 $class = 'hustle-cta-close hustle-button-close';
540 $data = 'href="#"';
541 }
542
543 $class = apply_filters( 'hustle_cta_extra_classes', $class, 'cta', $target );
544 $class = trim( $class . ' hustle-hidden-whole-module-cta' );
545
546 $html = sprintf(
547 '<a class="hustle-button hustle-button-cta %1$s" %2$s %3$s aria-hidden="true" tabindex="-1" style="display:none;"></a>',
548 esc_attr( $class ),
549 $data,
550 'data-cta-type="cta"'
551 );
552
553 return apply_filters( 'hustle_get_cta_html', $html, '', $target, $url, $class );
554 }
555
556 // ====================================
557 // Informational only markup.
558 // ====================================
559
560 /**
561 * Get the body of Informational modules.
562 *
563 * @since 4.0
564 *
565 * @return string
566 */
567 private function get_informational_body() {
568
569 $layout = $this->module->design->style;
570 $content = $this->module->content;
571
572 $module_layout = 'default';
573
574 if ( 'simple' === $layout ) {
575 $module_layout = 'compact';
576 }
577
578 if ( 'cabriolet' === $layout ) {
579 $module_layout = 'stacked';
580 }
581
582 $html = sprintf(
583 '<div class="hustle-info hustle-info--%s">',
584 esc_attr( $module_layout )
585 );
586
587 $html .= '<div class="hustle-main-wrapper">';
588
589 $html .= '<div class="hustle-layout'
590 . ( $this->has_whole_cta( $content ) ? ' hustle-whole-module-cta' : '' ) . '">';
591
592 $html .= ( 'cabriolet' !== $layout ) ? $this->get_close_button() : '';
593
594 $html .= $this->get_informational_body_content();
595 $html .= $this->maybe_get_hidden_whole_cta_button();
596
597 $html .= '</div>';
598
599 $html .= '</div>';
600
601 // NSA Link.
602 $html .= $this->get_optin_nsa_link( false );
603
604 $html .= '</div>';
605
606 return $html;
607 }
608
609 /**
610 * Get the right optin body according to the design.
611 *
612 * @since 4.0
613 * @return string
614 */
615 private function get_informational_body_content() {
616
617 switch ( $this->module->design->style ) {
618 case 'minimal':
619 return $this->get_informational_design_default();
620
621 case 'simple':
622 return $this->get_informational_design_compact();
623
624 default: // 'cabriolet'.
625 return $this->get_informational_design_stacked();
626
627 }
628 }
629
630 /**
631 * Get default (minimal) layout markup for informational module.
632 *
633 * @since 4.0
634 * @return string
635 */
636 private function get_informational_design_default() {
637
638 $html = '';
639
640 $content = $this->module->content;
641
642 // Header.
643 if ( '' !== $content->title || '' !== $content->sub_title ) {
644
645 $html .= '<div class="hustle-layout-header">';
646
647 if ( '' !== $content->title ) {
648 $html .= '<h3 class="hustle-title">';
649 $html .= $this->input_sanitize( $content->title );
650 $html .= '</h3>';
651 }
652
653 if ( '' !== $content->sub_title ) {
654 $html .= '<h4 class="hustle-subtitle">';
655 $html .= $this->input_sanitize( $content->sub_title );
656 $html .= '</h4>';
657 }
658
659 $html .= '</div>';
660 }
661
662 // Content.
663 if ( '' !== $content->main_content || '' !== $content->feature_image ) {
664
665 $html .= '<div class="hustle-layout-content">';
666
667 if ( '' !== $content->feature_image && 'left' === $this->module->design->feature_image_position ) {
668 $html .= $this->get_feature_image();
669 }
670
671 if ( '' !== $content->main_content ) {
672 $html .= '<div class="hustle-content">';
673 $html .= '<div class="hustle-content-wrap">';
674 $html .= '<div class="hustle-group-content">';
675 $html .= $this->get_module_main_content( $content );
676 $html .= '</div>';
677 $html .= '</div>';
678 $html .= '</div>';
679 }
680
681 if ( '' !== $content->feature_image && 'right' === $this->module->design->feature_image_position ) {
682 $html .= $this->get_feature_image();
683 }
684
685 $html .= '</div>';
686
687 }
688
689 // Footer.
690 if ( $this->is_show_cta( $content ) ) {
691
692 $html .= '<div class="hustle-layout-footer">';
693 $html .= $this->get_cta_button();
694 $html .= '</div>';
695
696 }
697
698 return $html;
699 }
700
701 /**
702 * Get compact (simple) layout markup for informational module.
703 *
704 * @since 4.0
705 * @return string
706 */
707 private function get_informational_design_compact() {
708
709 $html = '';
710
711 $content = $this->module->content;
712
713 // Image (Left).
714 if ( '' !== $content->feature_image && 'left' === $this->module->design->feature_image_position ) {
715 $html .= $this->get_feature_image();
716 }
717
718 // Content.
719 if (
720 '' !== $content->title ||
721 '' !== $content->sub_title ||
722 '' !== $content->main_content ||
723 $this->is_show_cta( $content )
724 ) {
725
726 $html .= '<div class="hustle-content">';
727
728 $html .= '<div class="hustle-content-wrap">';
729
730 if ( '' !== $content->title || '' !== $content->sub_title ) {
731
732 $html .= '<div class="hustle-group-title">';
733
734 if ( '' !== $content->title ) {
735 $html .= '<h3 class="hustle-title">';
736 $html .= $this->input_sanitize( $content->title );
737 $html .= '</h3>';
738 }
739
740 if ( '' !== $content->sub_title ) {
741 $html .= '<h4 class="hustle-subtitle">';
742 $html .= $this->input_sanitize( $content->sub_title );
743 $html .= '</h4>';
744 }
745
746 $html .= '</div>';
747 }
748
749 if ( '' !== $content->main_content ) {
750
751 $html .= '<div class="hustle-group-content">';
752 $html .= $this->get_module_main_content( $content );
753 $html .= '</div>';
754
755 }
756
757 if ( $this->is_show_cta( $content ) ) {
758
759 $html .= $this->get_cta_button();
760
761 }
762
763 $html .= '</div>';
764
765 $html .= '</div>';
766
767 }
768
769 // Image (Right).
770 if ( '' !== $content->feature_image && 'right' === $this->module->design->feature_image_position ) {
771 $html .= $this->get_feature_image();
772 }
773
774 return $html;
775 }
776
777 /**
778 * Get stacked (cabriolet) layout markup for informational module.
779 *
780 * @since 4.0
781 * @return string
782 */
783 private function get_informational_design_stacked() {
784
785 $html = '';
786
787 $content = $this->module->content;
788
789 $html .= $this->get_close_button();
790
791 // Header.
792 $html .= '<div class="hustle-layout-header">';
793
794 if ( '' !== $content->title ) {
795 $html .= '<h3 class="hustle-title">';
796 $html .= $this->input_sanitize( $content->title );
797 $html .= '</h3>';
798 }
799
800 if ( '' !== $content->sub_title ) {
801 $html .= '<h4 class="hustle-subtitle">';
802 $html .= $this->input_sanitize( $content->sub_title );
803 $html .= '</h4>';
804 }
805 $html .= '</div>';
806
807 // Body.
808 if (
809 '' !== $content->main_content ||
810 '' !== $content->feature_image ||
811 $this->is_show_cta( $content )
812 ) {
813
814 $html .= '<div class="hustle-layout-body">';
815
816 // Image (Left).
817 if ( '' !== $content->feature_image && 'left' === $this->module->design->feature_image_position ) {
818 $html .= $this->get_feature_image();
819 }
820
821 if (
822 '' !== $content->main_content ||
823 $this->is_show_cta( $content )
824 ) {
825
826 $html .= '<div class="hustle-content">';
827
828 $html .= '<div class="hustle-content-wrap">';
829
830 if ( '' !== $content->main_content ) {
831
832 $html .= '<div class="hustle-group-content">';
833 $html .= $this->get_module_main_content( $content );
834 $html .= '</div>';
835
836 }
837
838 if ( $this->is_show_cta( $content ) ) {
839 $html .= $this->get_cta_button();
840 }
841
842 $html .= '</div>';
843
844 $html .= '</div>';
845
846 }
847
848 // Image (Right).
849 if ( '' !== $content->feature_image && 'right' === $this->module->design->feature_image_position ) {
850 $html .= $this->get_feature_image();
851 }
852
853 $html .= '</div>';
854
855 }
856
857 return $html;
858 }
859
860 // ====================================
861 // Opt-in only markup.
862 // ====================================
863
864 /**
865 * Get the body of Opt-In modules.
866 *
867 * @since 4.0
868 *
869 * @return string
870 */
871 public function get_optin_body() {
872
873 $layout = $this->module->design->form_layout;
874 $content = $this->module->content;
875
876 $module_layout = 'default';
877
878 if ( 'two' === $layout ) {
879 $module_layout = 'compact';
880 }
881
882 if ( 'three' === $layout ) {
883 $module_layout = 'focus-optin';
884 }
885
886 if ( 'four' === $layout ) {
887 $module_layout = 'focus-content';
888 }
889
890 $html = sprintf(
891 '<div class="hustle-optin hustle-optin--%s">',
892 $module_layout
893 );
894
895 if ( 'two' === $layout ) {
896 $html .= '<div class="hustle-optin-content">';
897 }
898
899 $html .= $this->maybe_get_success_message();
900
901 $html .= '<div class="hustle-layout'
902 . ( $this->has_whole_cta( $content ) ? ' hustle-whole-module-cta' : '' ) . '">';
903
904 $html .= '<div class="hustle-main-wrapper">';
905
906 $html .= $this->get_close_button();
907
908 $html .= $this->get_optin_body_content();
909 $html .= $this->maybe_get_hidden_whole_cta_button();
910
911 $html .= '</div>';
912
913 $html .= $this->get_optin_nsa_link();
914
915 $html .= '</div>';
916
917 if ( 'two' === $layout ) {
918 $html .= '</div>';
919 }
920
921 $html .= '</div>';
922
923 return $html;
924 }
925
926 /**
927 * Get opt-in success message.
928 *
929 * @since 4.0
930 * @return string
931 */
932 private function maybe_get_success_message() {
933
934 $html = '';
935 $emails = $this->module->emails;
936 $success_option = $emails->after_successful_submission;
937 $success_message = $emails->success_message;
938 $auto_close = '1' === $emails->auto_close_success_message ? Hustle_Time_Helper::to_microseconds( $emails->auto_close_time, $emails->auto_close_unit ) : 'false';
939
940 if ( 'show_success' === $success_option || ( 'redirect' === $success_option && '' === $emails->redirect_url )
941 || 'newtab_thankyou' === $emails->redirect_tab ) {
942
943 $html .= sprintf(
944 '<div class="hustle-success" data-close-delay="%s" style="display: none;">',
945 esc_attr( $auto_close )
946 );
947
948 $html .= '<span class="hustle-icon-check" aria-hidden="true"></span>';
949
950 if ( '' !== $success_message ) {
951
952 $html .= '<div class="hustle-success-content">';
953
954 if ( is_admin() ) {
955 $html .= do_shortcode( $success_message );
956 }
957
958 $html .= '</div>';
959
960 }
961
962 $html .= '</div>';
963
964 }
965
966 return $html;
967 }
968
969 /**
970 * Get the right optin body according to the design.
971 *
972 * @since 4.0
973 * @return string
974 */
975 private function get_optin_body_content() {
976
977 switch ( $this->module->design->form_layout ) {
978 case 'one':
979 return $this->get_optin_design_default();
980
981 case 'two':
982 return $this->get_optin_design_compact();
983
984 case 'three':
985 return $this->get_optin_design_focus_optin();
986
987 default: // four.
988 return $this->get_optin_design_focus_content();
989 }
990 }
991
992 /**
993 * Get the markup according to design "one".
994 *
995 * @since 4.0
996 * @return string
997 */
998 private function get_optin_design_default() {
999
1000 $html = '';
1001 $content = $this->module->content;
1002 $design = $this->module->design;
1003
1004 $html .= '<div class="hustle-layout-body">';
1005
1006 if (
1007 '' !== $content->title ||
1008 '' !== $content->sub_title ||
1009 '' !== $content->feature_image ||
1010 '' !== $content->main_content ||
1011 $this->is_show_cta( $content )
1012 ) {
1013
1014 $html .= sprintf( '<div class="hustle-layout-content hustle-layout-position--%s">', esc_attr( $design->feature_image_position ) );
1015
1016 if ( '' !== $content->feature_image && ( 'left' === $design->feature_image_position || 'above' === $design->feature_image_position ) ) {
1017 $html .= $this->get_feature_image();
1018 }
1019
1020 if (
1021 '' !== $content->title ||
1022 '' !== $content->sub_title ||
1023 '' !== $content->main_content ||
1024 $this->is_show_cta( $content )
1025 ) {
1026
1027 $html .= '<div class="hustle-content">';
1028
1029 $html .= '<div class="hustle-content-wrap">';
1030
1031 if ( '' !== $content->title || '' !== $content->sub_title ) {
1032
1033 $html .= '<div class="hustle-group-title">';
1034
1035 if ( '' !== $content->title ) {
1036 $html .= '<h3 class="hustle-title">';
1037 $html .= $this->input_sanitize( $content->title );
1038 $html .= '</h3>';
1039 }
1040
1041 if ( '' !== $content->sub_title ) {
1042 $html .= '<h4 class="hustle-subtitle">';
1043 $html .= $this->input_sanitize( $content->sub_title );
1044 $html .= '</h4>';
1045 }
1046
1047 $html .= '</div>';
1048 }
1049
1050 if ( '' !== $content->main_content ) {
1051 $html .= '<div class="hustle-group-content">';
1052 $html .= $this->get_module_main_content( $content );
1053 $html .= '</div>';
1054 }
1055
1056 if ( $this->is_show_cta( $content ) ) {
1057
1058 $html .= $this->get_cta_button();
1059
1060 }
1061
1062 $html .= '</div>';
1063
1064 $html .= '</div>';
1065 }
1066
1067 if ( '' !== $content->feature_image && ( 'right' === $design->feature_image_position || 'below' === $design->feature_image_position ) ) {
1068 $html .= $this->get_feature_image();
1069 }
1070
1071 $html .= '</div>';
1072
1073 }
1074
1075 $html .= $this->get_form();
1076
1077 $html .= '</div>';
1078
1079 return $html;
1080 }
1081
1082 /**
1083 * Get the markup according to design "two".
1084 *
1085 * @since 4.0
1086 * @return string
1087 */
1088 private function get_optin_design_compact() {
1089
1090 $html = '';
1091 $content = $this->module->content;
1092
1093 $html .= '<div class="hustle-layout-body">';
1094
1095 if ( '' !== $content->feature_image && 'left' === $this->module->design->feature_image_position ) {
1096 $html .= $this->get_feature_image();
1097 }
1098
1099 $html .= '<div class="hustle-layout-content">';
1100
1101 if (
1102 '' !== $content->title ||
1103 '' !== $content->sub_title ||
1104 '' !== $content->main_content ||
1105 $this->is_show_cta( $content )
1106 ) {
1107
1108 $html .= '<div class="hustle-content">';
1109
1110 $html .= '<div class="hustle-content-wrap">';
1111
1112 if ( '' !== $content->title || '' !== $content->sub_title ) {
1113
1114 $html .= '<div class="hustle-group-title">';
1115
1116 if ( '' !== $content->title ) {
1117 $html .= '<h3 class="hustle-title">';
1118 $html .= $this->input_sanitize( $content->title );
1119 $html .= '</h3>';
1120 }
1121
1122 if ( '' !== $content->sub_title ) {
1123 $html .= '<h4 class="hustle-subtitle">';
1124 $html .= $this->input_sanitize( $content->sub_title );
1125 $html .= '</h4>';
1126 }
1127
1128 $html .= '</div>';
1129
1130 }
1131
1132 if ( '' !== $content->main_content ) {
1133 $html .= '<div class="hustle-group-content">';
1134 $html .= $this->get_module_main_content( $content );
1135 $html .= '</div>';
1136 }
1137
1138 if ( $this->is_show_cta( $content ) ) {
1139
1140 $html .= $this->get_cta_button();
1141
1142 }
1143
1144 $html .= '</div>';
1145
1146 $html .= '</div>';
1147
1148 }
1149
1150 $html .= $this->get_form();
1151
1152 $html .= '</div>';
1153
1154 if ( '' !== $content->feature_image && 'right' === $this->module->design->feature_image_position ) {
1155 $html .= $this->get_feature_image();
1156 }
1157
1158 $html .= '</div>';
1159
1160 return $html;
1161 }
1162
1163 /**
1164 * Get the markup according to design "three".
1165 *
1166 * @since 4.0
1167 * @return string
1168 */
1169 private function get_optin_design_focus_optin() {
1170
1171 $html = '';
1172 $content = $this->module->content;
1173
1174 $html .= '<div class="hustle-layout-body">';
1175
1176 if ( 'right' === $this->module->design->feature_image_position ) {
1177 $html .= $this->get_form();
1178 }
1179
1180 if (
1181 '' !== $content->title ||
1182 '' !== $content->sub_title ||
1183 '' !== $content->feature_image ||
1184 '' !== $content->main_content ||
1185 $this->is_show_cta( $content )
1186 ) {
1187
1188 $html .= '<div class="hustle-layout-content">';
1189
1190 if ( '' !== $content->feature_image ) {
1191 $html .= $this->get_feature_image();
1192 }
1193
1194 if (
1195 '' !== $content->title ||
1196 '' !== $content->sub_title ||
1197 '' !== $content->main_content ||
1198 $this->is_show_cta( $content )
1199 ) {
1200
1201 $html .= '<div class="hustle-content">';
1202
1203 $html .= '<div class="hustle-content-wrap">';
1204
1205 if ( '' !== $content->title || '' !== $content->sub_title ) {
1206
1207 $html .= '<div class="hustle-group-title">';
1208
1209 if ( '' !== $content->title ) {
1210 $html .= '<h3 class="hustle-title">';
1211 $html .= $this->input_sanitize( $content->title );
1212 $html .= '</h3>';
1213 }
1214
1215 if ( '' !== $content->sub_title ) {
1216 $html .= '<h4 class="hustle-subtitle">';
1217 $html .= $this->input_sanitize( $content->sub_title );
1218 $html .= '</h4>';
1219 }
1220
1221 $html .= '</div>';
1222
1223 }
1224
1225 if ( '' !== $content->main_content ) {
1226 $html .= '<div class="hustle-group-content">';
1227 $html .= $this->get_module_main_content( $content );
1228 $html .= '</div>';
1229 }
1230
1231 if ( $this->is_show_cta( $content ) ) {
1232
1233 $html .= $this->get_cta_button();
1234
1235 }
1236
1237 $html .= '</div>';
1238
1239 $html .= '</div>';
1240
1241 }
1242
1243 $html .= '</div>';
1244
1245 }
1246
1247 if ( 'left' === $this->module->design->feature_image_position ) {
1248 $html .= $this->get_form();
1249 }
1250
1251 $html .= '</div>';
1252
1253 return $html;
1254 }
1255
1256 /**
1257 * Get the markup according to design "four".
1258 *
1259 * @since 4.0
1260 * @return string
1261 */
1262 private function get_optin_design_focus_content() {
1263
1264 $html = '';
1265 $content = $this->module->content;
1266
1267 $html .= '<div class="hustle-layout-body">';
1268
1269 if ( 'left' === $this->module->design->feature_image_position ) {
1270
1271 $html .= '<div class="hustle-layout-sidebar">';
1272
1273 if ( '' !== $content->feature_image ) {
1274 $html .= $this->get_feature_image();
1275 }
1276
1277 $html .= $this->get_form();
1278
1279 $html .= '</div>';
1280
1281 }
1282
1283 if (
1284 '' !== $content->title ||
1285 '' !== $content->sub_title ||
1286 '' !== $content->main_content ||
1287 $this->is_show_cta( $content )
1288 ) {
1289
1290 $html .= '<div class="hustle-layout-content">';
1291
1292 $html .= '<div class="hustle-content">';
1293
1294 $html .= '<div class="hustle-content-wrap">';
1295
1296 if ( '' !== $content->title || '' !== $content->sub_title ) {
1297
1298 $html .= '<div class="hustle-group-title">';
1299
1300 if ( '' !== $content->title ) {
1301 $html .= '<h3 class="hustle-title">';
1302 $html .= $this->input_sanitize( $content->title );
1303 $html .= '</h3>';
1304 }
1305
1306 if ( '' !== $content->sub_title ) {
1307 $html .= '<h4 class="hustle-subtitle">';
1308 $html .= $this->input_sanitize( $content->sub_title );
1309 $html .= '</h4>';
1310 }
1311
1312 $html .= '</div>';
1313
1314 }
1315
1316 if ( '' !== $content->main_content ) {
1317 $html .= '<div class="hustle-group-content">';
1318 $html .= $this->get_module_main_content( $content );
1319 $html .= '</div>';
1320 }
1321
1322 if ( $this->is_show_cta( $content ) ) {
1323
1324 $html .= $this->get_cta_button();
1325
1326 }
1327
1328 $html .= '</div>';
1329
1330 $html .= '</div>';
1331
1332 $html .= '</div>';
1333
1334 }
1335
1336 if ( 'right' === $this->module->design->feature_image_position ) {
1337
1338 $html .= '<div class="hustle-layout-sidebar">';
1339
1340 if ( '' !== $content->feature_image ) {
1341 $html .= $this->get_feature_image();
1342 }
1343
1344 $html .= $this->get_form();
1345
1346 $html .= '</div>';
1347
1348 }
1349
1350 $html .= '</div>';
1351
1352 return $html;
1353 }
1354
1355 /**
1356 * Get module fields
1357 *
1358 * @return array
1359 */
1360 private function form_elements() {
1361 /**
1362 * Edit module fields
1363 *
1364 * @since 4.1.1
1365 * @param string $form_elements Current module fields.
1366 */
1367 $fields = apply_filters( 'hustle_form_elements', $this->module->emails->form_elements );
1368
1369 return $fields;
1370 }
1371
1372 /**
1373 * Get the opt-in form markup.
1374 *
1375 * @since 4.0
1376 * @param bool $show_recaptcha Show recaptcha.
1377 * @return string
1378 */
1379 private function get_form( $show_recaptcha = true ) {
1380
1381 $html = '';
1382 $fields = $this->form_elements();
1383 $design = $this->module->design;
1384
1385 $distribution = $design->optin_form_layout;
1386
1387 if ( ! $this->is_admin ) {
1388 $tag = 'form';
1389 $extra_data = ' novalidate="novalidate"';
1390 } else {
1391 $tag = 'div';
1392 $extra_data = '';
1393 }
1394
1395 $html .= sprintf(
1396 '<%s class="hustle-layout-form"%s>',
1397 $tag,
1398 $extra_data
1399 );
1400
1401 // Form fields.
1402 $html .= sprintf(
1403 '<div class="hustle-form%s">',
1404 'inline' === $distribution ? ' hustle-form-inline' : ''
1405 );
1406
1407 $html .= $this->get_form_fields( $fields );
1408
1409 $html .= $this->get_custom_fields();
1410
1411 $html .= '</div>';
1412
1413 // Common hidden fields with useful data.
1414 $html .= $this->get_common_hidden_fields();
1415
1416 // GDPR checkbox.
1417 $html .= $this->get_field_gdpr( $fields );
1418
1419 // reCaptchaget_recaptcha_container.
1420 if ( $show_recaptcha ) {
1421 $html .= $this->get_recaptcha_container( $fields );
1422 $html .= $this->get_turnstile_container( $fields );
1423 }
1424
1425 // Error message.
1426 $html .= $this->get_form_error( $fields );
1427
1428 $html .= sprintf( '</%s>', $tag );
1429
1430 return $html;
1431 }
1432
1433 /**
1434 * Get opt-in form fields markup.
1435 *
1436 * @since 4.0
1437 * @param array $fields Fields.
1438 * @return string
1439 */
1440 private function get_form_fields( $fields ) {
1441 $html = '';
1442 // Keeping the `hustle-proximity-%s` class just for users. Don't use it.
1443 $html .= sprintf(
1444 '<div class="hustle-form-fields hustle-proximity-%s">',
1445 ( '0' === $this->module->design->customize_form_fields_proximity ? 'joined' : 'separated' )
1446 );
1447
1448 $hidden_fields_markup = '';
1449 if ( is_array( $fields ) ) {
1450
1451 foreach ( $fields as $name => $field ) {
1452 if ( in_array( $field['type'], array( 'submit', 'gdpr', 'recaptcha', 'turnstile' ), true ) ) {
1453 continue;
1454 }
1455
1456 if ( 'hidden' !== $field['type'] ) {
1457 $html .= $this->get_form_input( $field );
1458 } else {
1459 $hidden_fields_markup .= $this->get_form_hidden_input( $field );
1460 }
1461 }
1462 }
1463 $html .= $this->get_form_submit( $fields );
1464 $html .= $hidden_fields_markup;
1465 $html .= '</div>';
1466 return $html;
1467 }
1468
1469 /**
1470 * Get opt-in form input field markup.
1471 *
1472 * @since 4.0
1473 * @param array $field Field.
1474 * @return string
1475 */
1476 private function get_form_input( $field ) {
1477 $type = isset( $field['type'] ) ? $field['type'] : 'text';
1478 $name = isset( $field['name'] ) ? $field['name'] : 'first_name';
1479 $label = ( '' !== $field['placeholder'] ) ? $this->input_sanitize( $field['placeholder'] ) : $this->input_sanitize( $field['label'] );
1480 $sr_label = ( '' !== $field['label'] ) ? $this->input_sanitize( $field['label'] ) : '';
1481 $required = isset( $field['required'] ) && 'true' === $field['required'] ? true : false;
1482 $to_validate = isset( $field['validate'] ) && 'true' === $field['validate'] ? true : false;
1483
1484 $module_id = $this->module->module_id;
1485 $icon = $type;
1486 $value = '';
1487 $field_icon = '';
1488 $class_icon = '';
1489 $class_input = '';
1490 $class_status = $required ? ' hustle-field-required' : '';
1491 $data_attributes = sprintf( 'data-validate="%s" ', esc_attr( $to_validate ) );
1492
1493 if ( $required && ! empty( $field['required_error_message'] ) ) {
1494 $data_attributes .= sprintf( 'data-required-error="%s" ', esc_attr( wp_strip_all_tags( html_entity_decode( $field['required_error_message'] ) ) ) );
1495 }
1496 if ( $to_validate && ! empty( $field['validation_message'] ) ) {
1497 $data_attributes .= sprintf( 'data-validation-error="%s" ', esc_attr( wp_strip_all_tags( html_entity_decode( $field['validation_message'] ) ) ) );
1498 }
1499
1500 switch ( $type ) {
1501
1502 case 'email':
1503 $type = 'email';
1504 break;
1505
1506 case 'phone':
1507 $type = 'text';
1508 $data_attributes .= 'data-type="phone"';
1509 break;
1510
1511 case 'url':
1512 $icon = 'website';
1513 break;
1514
1515 case 'website':
1516 $type = 'url';
1517 break;
1518
1519 case 'timepicker':
1520 $class_input = 'hustle-time';
1521
1522 if ( '24' === $field['time_format'] ) {
1523 $time_format = 'HH:mm';
1524 $time_hours = ! empty( $field['time_hours'] ) ? $field['time_hours'] : '';
1525 $time_minutes = ! empty( $field['time_minutes'] ) ? $field['time_minutes'] : '';
1526 $time_structure = $time_hours . ':' . $time_minutes;
1527 $time_default = ( '' !== $time_hours && '' !== $time_minutes ) ? $time_structure : '';
1528 $value = $time_default;
1529 } else {
1530 $time_format = 'hh:mm p';
1531 $time_hours = ! empty( $field['time_hours'] ) ? $field['time_hours'] : '';
1532 $time_minutes = ! empty( $field['time_minutes'] ) ? $field['time_minutes'] : '';
1533 $time_period = ! empty( $field['time_period'] ) ? $field['time_period'] : 'am';
1534 $time_structure = $time_hours . ':' . $time_minutes . ' ' . $time_period;
1535 $time_default = ( '' !== $time_hours && '' !== $time_minutes && '' !== $time_period ) ? $time_structure : '';
1536 $value = $time_default;
1537 }
1538
1539 $data_attributes .= 'data-time-format="' . esc_attr( $time_format ) . '" data-time-default="' . esc_attr( $time_default ) . '" data-time-interval="1" data-time-dropdown="true"';
1540 break;
1541
1542 case 'datepicker':
1543 $date_format = $field['date_format'];
1544
1545 // These formats come from 4.0, so we keep the same display as in there, even though it was a bug.
1546 if ( in_array( $field['date_format'], array( 'm/d/Y', 'Y/m/d', 'd/m/Y' ), true ) ) {
1547 $date_format = 'MM d, yy';
1548 }
1549
1550 $class_input = 'hustle-date';
1551 $change_year = wp_json_encode( ! empty( $field['change_year'] ) );
1552 $change_month = wp_json_encode( ! empty( $field['change_month'] ) );
1553 $min_year_range = ! empty( $field['min_year_range'] ) ? $field['min_year_range'] : 'c-10';
1554 $max_year_range = ! empty( $field['max_year_range'] ) ? $field['max_year_range'] : 'c+10';
1555 $year_range = $min_year_range . ':' . $max_year_range;
1556 $data_attributes .= 'data-min-date="null" data-rtl-support="false" data-change-year="' . esc_attr( $change_year ) . '" data-change-month="' . esc_attr( $change_month ) . '" data-year-range="' . esc_attr( $year_range ) . '" data-format="' . esc_attr( $date_format ) . '"';
1557 break;
1558
1559 default:
1560 break;
1561 }
1562
1563 if ( 'none' !== $this->module->design->form_fields_icon ) {
1564 $field_icon = sprintf( '<span class="hustle-icon-%s"></span>', esc_attr( $icon ) );
1565 $class_icon = ' hustle-field-icon--' . $this->module->design->form_fields_icon;
1566 }
1567
1568 $classes = array(
1569 sprintf(
1570 'hustle-field%s%s',
1571 esc_attr( $class_icon ),
1572 esc_attr( $class_status )
1573 ),
1574 );
1575
1576 if ( ! empty( $value ) ) {
1577 $classes[] = 'hustle-field-filled';
1578 }
1579
1580 if ( isset( $field['css_classes'] ) ) {
1581 $classes[] = $field['css_classes'];
1582 }
1583
1584 $html = sprintf(
1585 '<div class="%s">',
1586 esc_attr( implode( ' ', $classes ) )
1587 );
1588
1589 if ( '' !== $sr_label ) {
1590
1591 $html .= sprintf(
1592 '<label for="hustle-field-%s-module-%s" id="hustle-field-%s-module-%s-label" class="hustle-screen-reader">%s</label>',
1593 esc_attr( $name ),
1594 esc_attr( $module_id ),
1595 esc_attr( $name ),
1596 esc_attr( $module_id ),
1597 $sr_label
1598 );
1599
1600 }
1601
1602 $html .= sprintf(
1603 '<input id="hustle-field-%s-module-%s" type="%s" class="hustle-input %s" name="%s" value="%s" aria-labelledby="hustle-field-%s-module-%s-label" %s/>', // TODO: add autocomplete here or to form.
1604 esc_attr( $name ),
1605 esc_attr( $module_id ),
1606 esc_attr( $type ),
1607 esc_attr( $class_input ),
1608 esc_attr( $name ),
1609 esc_attr( $value ),
1610 esc_attr( $name ),
1611 esc_attr( $module_id ),
1612 $data_attributes
1613 );
1614
1615 $html .= '<span class="hustle-input-label" aria-hidden="true" style="flex-flow: row nowrap;">';
1616 $html .= $field_icon;
1617 $html .= sprintf( '<span>%s</span>', $label );
1618 $html .= '</span>';
1619
1620 $html .= '</div>';
1621
1622 return $html;
1623 }
1624
1625 /**
1626 * Get hidden value.
1627 *
1628 * @param array $field_data Current field data.
1629 * @return string
1630 */
1631 public static function get_hidden_value( $field_data ) {
1632 $value = '';
1633
1634 switch ( $field_data['default_value'] ) {
1635
1636 case 'user_ip':
1637 $value = Opt_In_Geo::get_user_ip();
1638 break;
1639 case 'date_mdy':
1640 $value = date_i18n( 'm/d/Y', Hustle_Time_Helper::get_local_timestamp(), true );
1641 break;
1642 case 'date_dmy':
1643 $value = date_i18n( 'd/m/Y', Hustle_Time_Helper::get_local_timestamp(), true );
1644 break;
1645 case 'embed_id':
1646 $value = Opt_In_Utils::get_post_data( 'ID' );
1647 break;
1648 case 'embed_title':
1649 $value = Opt_In_Utils::get_post_data( 'post_title' );
1650 break;
1651 case 'embed_url':
1652 $value = Opt_In_Utils::get_current_url();
1653 break;
1654 case 'user_agent':
1655 $value = filter_input( INPUT_SERVER, 'HTTP_USER_AGENT', FILTER_SANITIZE_SPECIAL_CHARS );
1656 break;
1657 case 'refer_url':
1658 $value = filter_input( INPUT_SERVER, 'HTTP_REFERER', FILTER_SANITIZE_SPECIAL_CHARS );
1659 if ( ! $value ) {
1660 $value = Opt_In_Utils::get_current_url();
1661 }
1662 break;
1663 case 'user_id':
1664 $value = Opt_In_Utils::get_user_data( 'ID' );
1665 break;
1666 case 'user_name':
1667 $value = Opt_In_Utils::get_user_data( 'display_name' );
1668 break;
1669 case 'user_email':
1670 $value = Opt_In_Utils::get_user_data( 'user_email' );
1671 break;
1672 case 'user_login':
1673 $value = Opt_In_Utils::get_user_data( 'user_login' );
1674 break;
1675 case 'custom_value':
1676 $value = $field_data['custom_value'];
1677 break;
1678 case 'query_parameter':
1679 $value = $field_data['query_parameter'] ? (string) filter_input( INPUT_GET, $field_data['query_parameter'], FILTER_SANITIZE_SPECIAL_CHARS ) : '';
1680 break;
1681
1682 default:
1683 break;
1684 }
1685
1686 return $value;
1687 }
1688
1689 /**
1690 * Get the markup for hidden fields.
1691 *
1692 * @since 4.0.4
1693 * @param array $field Current field data.
1694 * @return string
1695 */
1696 private function get_form_hidden_input( $field ) {
1697 $value = self::get_hidden_value( $field );
1698
1699 /**
1700 * Edit the value of the hidden field.
1701 *
1702 * @since 4.0.4
1703 * @param string $value Current value.
1704 * @param array $field Current field data.
1705 * @param Hustle_Module_Model $this->module Instance of the current module.
1706 */
1707 $value = apply_filters( 'hustle_field_hidden_field_value', $value, $field, $this->module );
1708
1709 $html = sprintf(
1710 '<input type="hidden" name="%s" value="%s"/>',
1711 esc_attr( $field['name'] ),
1712 esc_attr( $value )
1713 );
1714
1715 return $html;
1716 }
1717
1718 /**
1719 * Get opt-in form submit button markup.
1720 *
1721 * @since 4.0
1722 * @param array $fields Fields.
1723 * @return string
1724 */
1725 private function get_form_submit( $fields ) {
1726
1727 $html = '';
1728
1729 $label = __( 'Submit', 'hustle' );
1730 $loading = __( 'Form is being submitted, please wait a bit.', 'hustle' );
1731
1732 $classes = isset( $fields['submit']['css_classes'] ) ? $fields['submit']['css_classes'] : '';
1733
1734 if ( isset( $fields['submit'] ) && isset( $fields['submit']['label'] ) ) {
1735 $label = wp_strip_all_tags( html_entity_decode( $fields['submit']['label'] ) );
1736 }
1737
1738 $html .= sprintf( '<button class="hustle-button hustle-button-submit %s" aria-live="polite" data-loading-text="%s">', esc_attr( $classes ), esc_attr( $loading ) );
1739
1740 $html .= sprintf( '<span class="hustle-button-text">%s</span>', esc_html( $label ) );
1741
1742 $html .= '<span class="hustle-icon-loader hustle-loading-icon" aria-hidden="true"></span>';
1743
1744 $html .= '</button>';
1745
1746 return $html;
1747 }
1748
1749 /**
1750 * Get opt-in custom fields markup.
1751 * These custom fields are added by provider's, for example: Mailchimp groups.
1752 *
1753 * @since 4.0
1754 * @return string
1755 * @throws Exception Addon field isn't a string.
1756 */
1757 private function get_custom_fields() {
1758
1759 $html = '';
1760
1761 $connected_addons = Hustle_Provider_Utils::get_addons_instance_connected_with_module( $this->module->module_id );
1762
1763 foreach ( $connected_addons as $connected_addon ) {
1764
1765 try {
1766
1767 $form_hooks = $connected_addon->get_addon_form_hooks( $this->module->module_id );
1768
1769 if ( $form_hooks instanceof Hustle_Provider_Form_Hooks_Abstract ) {
1770 $addon_fields = $form_hooks->add_front_form_fields( $this->module );
1771
1772 // Log errors.
1773 if ( ! is_string( $addon_fields ) ) {
1774 throw new Exception( 'The returned markup should be a string.' );
1775 }
1776
1777 $html .= $addon_fields;
1778 }
1779 } catch ( Exception $e ) {
1780 Hustle_Provider_Utils::maybe_log( $connected_addon->get_slug(), 'failed to add custom front form fields.', $e->getMessage() );
1781 }
1782 }
1783
1784 return $html;
1785 }
1786
1787 /**
1788 * Get common hidden fields with data about the displayed module.
1789 *
1790 * @since 4.0
1791 * @return string
1792 */
1793 private function get_common_hidden_fields() {
1794
1795 $html = '<input type="hidden" name="hustle_module_id" value="' . esc_attr( $this->module->module_id ) . '">';
1796
1797 $html .= '<input type="hidden" name="post_id" value="' . esc_attr( $this->get_post_id() ) . '">';
1798
1799 if ( ! empty( $this->sub_type ) ) {
1800 $html .= '<input type="hidden" name="hustle_sub_type" value="' . esc_attr( $this->sub_type ) . '">';
1801 }
1802
1803 return $html;
1804 }
1805
1806 /**
1807 * Get the GDPR checkbox field markup.
1808 *
1809 * @since 4.0
1810 * @param array $fields Fields.
1811 * @return string
1812 */
1813 private function get_field_gdpr( $fields ) {
1814
1815 $html = '';
1816 $module_id = $this->module->module_id;
1817 $render_id = self::$render_ids[ $module_id ];
1818 $classes = isset( $fields['gdpr']['css_classes'] ) ? $fields['gdpr']['css_classes'] : '';
1819
1820 if ( isset( $fields['gdpr'] ) ) {
1821
1822 $html .= sprintf(
1823 '<label for="hustle-gdpr-module-%d-%d" class="hustle-checkbox hustle-gdpr %s">',
1824 esc_attr( $module_id ),
1825 esc_attr( $render_id ),
1826 esc_attr( $classes )
1827 );
1828
1829 $data_attributes = ! empty( $fields['gdpr']['required_error_message'] ) ?
1830 sprintf( 'data-required-error="%s" ', esc_attr( wp_strip_all_tags( html_entity_decode( $fields['gdpr']['required_error_message'] ) ) ) ) : '';
1831
1832 $html .= sprintf(
1833 '<input type="checkbox" name="gdpr" id="hustle-gdpr-module-%d-%d" %s />',
1834 esc_attr( $module_id ),
1835 esc_attr( $render_id ),
1836 $data_attributes
1837 );
1838
1839 $html .= '<span aria-hidden="true"></span>';
1840
1841 $html .= sprintf(
1842 '<span>%s</span>',
1843 $this->input_sanitize( $fields['gdpr']['gdpr_message'] )
1844 );
1845
1846 $html .= '</label>';
1847
1848 }
1849
1850 return $html;
1851 }
1852
1853 /**
1854 * Get the filtered and parsed main content for the module.
1855 *
1856 * @since 4.0
1857 *
1858 * @param object $content Content.
1859 * @return string
1860 */
1861 private function get_module_main_content( $content ) {
1862
1863 $allowed_html = wp_kses_allowed_html( 'post' );
1864
1865 // iframe.
1866 $allowed_html['iframe'] = array(
1867 'src' => array(),
1868 'height' => array(),
1869 'width' => array(),
1870 'frameborder' => array(),
1871 'allowfullscreen' => array(),
1872 );
1873 // Form.
1874 $allowed_html['form'] = array(
1875 'class' => array(),
1876 'action' => true,
1877 'accept' => true,
1878 'accept-charset' => true,
1879 'enctype' => true,
1880 'method' => true,
1881 'name' => true,
1882 'target' => true,
1883 'role' => array(),
1884 );
1885 // Inputs.
1886 $allowed_html['input'] = array(
1887 'class' => array(),
1888 'id' => array(),
1889 'name' => array(),
1890 'value' => array(),
1891 'type' => array(),
1892 'placeholder' => array(),
1893 );
1894 // Select.
1895 $allowed_html['select'] = array(
1896 'class' => array(),
1897 'id' => array(),
1898 'name' => array(),
1899 'value' => array(),
1900 'type' => array(),
1901 );
1902 // Select options.
1903 $allowed_html['option'] = array(
1904 'selected' => array(),
1905 );
1906 // Style.
1907 $allowed_html['style'] = array(
1908 'types' => array(),
1909 );
1910
1911 // i for fontawesome.
1912 $allowed_html['i'] = array(
1913 'class' => array(),
1914 );
1915 // Keep allowing scripts because users are using it.
1916 $allowed_html['script'] = array(
1917 'async' => array(),
1918 'crossorigin' => array(),
1919 'integrity' => array(),
1920 'referrerpolicy' => array(),
1921 'nomodule' => array(),
1922 'src' => array(),
1923 'type' => array(),
1924 'defer' => array(),
1925 'charset' => array(),
1926 );
1927
1928 /**
1929 * Allows editing the allowed html tags for the modules' main content.
1930 *
1931 * @since 4.0.0.1
1932 */
1933 $allowed_html = apply_filters( 'hustle_module_main_content_allowed_html', $allowed_html, $this->module );
1934 $content = wpautop( wp_kses( $content->main_content, $allowed_html ) );
1935
1936 /**
1937 * Allows editing the escaped main content before doing the shortcodes.
1938 *
1939 * @since 4.0.0.1
1940 */
1941 $content = apply_filters( 'hustle_module_main_content', $content, $this->module );
1942
1943 // Process the [embed] shortcode.
1944 if ( has_shortcode( $content, 'embed' ) ) {
1945 $wp_embed = new WP_Embed();
1946 $content = $wp_embed->run_shortcode( $content );
1947 }
1948
1949 return do_shortcode( $content );
1950 }
1951
1952 /**
1953 * Get the filtered and parsed content for the module.
1954 *
1955 * @since 4.2.1
1956 *
1957 * @param object $content Contet.
1958 * @return string
1959 */
1960 public function input_sanitize( $content ) {
1961 $allowed_html = wp_kses_allowed_html( 'post' );
1962
1963 /**
1964 * Custom filtering for every other field than main content.
1965 * By default it uses wp_kses post rules.
1966 *
1967 * @since 4.2.1
1968 */
1969 $allowed_html = apply_filters( 'hustle_module_input_content_allowed_html', $allowed_html, $this->module );
1970
1971 $content = wp_kses( $content, $allowed_html );
1972 return $content;
1973 }
1974
1975 /**
1976 * Get the opt-in form error message markup.
1977 *
1978 * @since 4.0
1979 * @param array $fields Fields.
1980 * @return string
1981 */
1982 private function get_form_error( $fields ) {
1983
1984 if ( isset( $fields['submit'] ) && ! empty( $fields['submit']['error_message'] ) ) {
1985 $default_error = esc_attr( wp_strip_all_tags( html_entity_decode( $fields['submit']['error_message'] ) ) );
1986 } else {
1987 $default_error = __( 'There was an error submitting the form', 'hustle' );
1988 }
1989
1990 $html = sprintf(
1991 '<div class="hustle-error-message" style="display: none;" data-default-error="%s">',
1992 esc_attr( $default_error )
1993 );
1994
1995 $html .= '</div>';
1996
1997 return $html;
1998 }
1999
2000 /**
2001 * Get opt-in never see link markup.
2002 *
2003 * @since 4.0
2004 * @param bool $wrapper Wrapper.
2005 * @return string
2006 */
2007 private function get_optin_nsa_link( $wrapper = true ) {
2008
2009 $html = '';
2010
2011 if ( Hustle_Module_Model::EMBEDDED_MODULE !== $this->module->module_type ) {
2012
2013 $content = $this->module->content;
2014 $message = ( '' !== $content->never_see_link_text ) ? $content->never_see_link_text : esc_html__( 'Never see this message again', 'hustle' );
2015
2016 if ( (int) $content->show_never_see_link ) {
2017 $html .= ( true === $wrapper ) ? '<div class="hustle-layout-footer">' : '';
2018 $html .= '<p class="hustle-nsa-link">';
2019 $html .= '<a href="#">' . $this->input_sanitize( $message ) . '</a>';
2020 $html .= '</p>';
2021 $html .= ( true === $wrapper ) ? '</div>' : '';
2022 }
2023 }
2024 return $html;
2025 }
2026
2027 /**
2028 * Get recaptcha container if configured.
2029 *
2030 * @since 4.0
2031 * @param array $fields Fields.
2032 * @return string
2033 */
2034 private function get_recaptcha_container( $fields ) {
2035
2036 $html = '';
2037 $fields = $this->module->emails->form_elements;
2038
2039 // Check if this module has a recaptcha field, and that its creds were added.
2040 if ( $this->is_recaptcha_active( $fields ) ) {
2041
2042 $recaptcha = $fields['recaptcha'];
2043
2044 $recaptcha_version = empty( $recaptcha['version'] ) ? 'v2_checkbox' : $recaptcha['version'];
2045 $site_key_key = $recaptcha_version . '_site_key';
2046
2047 if ( 'v2_checkbox' === $recaptcha_version ) {
2048 $size = $recaptcha['recaptcha_type'];
2049 $badge = '';
2050 $show_badge = true;
2051
2052 } else {
2053 $size = 'invisible';
2054 $show_badge = '1' === $recaptcha[ $recaptcha_version . '_show_badge' ];
2055 $badge = 'inline';
2056 }
2057
2058 $recaptcha_classes = isset( $recaptcha['css_classes'] ) ? $recaptcha['css_classes'] : '';
2059
2060 if ( ! $show_badge ) {
2061 $recaptcha_classes .= ' hustle-recaptcha-nobadge';
2062 }
2063
2064 $recaptcha_theme = ( 'v2_invisible' === $recaptcha['version'] ) ? $recaptcha['v2_invisible_theme'] : $recaptcha['recaptcha_theme'];
2065
2066 $extra_data = sprintf(
2067 'data-size="%s" data-theme="%s" data-badge="%3$s"',
2068 esc_attr( $size ),
2069 esc_attr( $recaptcha_theme ),
2070 esc_attr( $badge )
2071 );
2072
2073 $recaptcha_settings = Hustle_Settings_Admin::get_recaptcha_settings();
2074 $render_id = self::$render_ids[ $this->module->module_id ];
2075 $html .= sprintf(
2076 '<div id="hustle-modal-recaptcha-%1$d-%2$d" class="hustle-recaptcha %3$s" data-required-error="%4$s" data-sitekey="%5$s" data-version=%6$s %7$s></div>',
2077 esc_attr( $this->module->id ),
2078 esc_attr( $render_id ),
2079 esc_attr( $recaptcha_classes ),
2080 esc_attr( wp_strip_all_tags( html_entity_decode( $fields['recaptcha']['validation_message'] ) ) ),
2081 esc_attr( $recaptcha_settings[ $site_key_key ] ),
2082 esc_attr( $recaptcha_version ),
2083 $extra_data
2084 );
2085
2086 // Display custom text instead of badge if hidden.
2087 if ( ! $show_badge ) {
2088
2089 $html .= sprintf(
2090 '<div class="hustle-recaptcha-copy">%s</div>',
2091 $this->input_sanitize( $recaptcha[ $recaptcha_version . '_badge_replacement' ] )
2092 );
2093 }
2094
2095 // The input that will hold the recaptcha's response for backend validation on form submit.
2096 $html .= '<input type="hidden" name="recaptcha-response" class="recaptcha-response-input" value="">';
2097
2098 /**
2099 * Filter the markup for the recaptcha container
2100 *
2101 * @since 4.1.1
2102 *
2103 * @param object $this->module Current module. Instance of Hustle_Module_Model.
2104 * @param array $recaptcha Module's recaptcha field settings.
2105 * @param array $recaptcha_settings Global stored recaptcha credentials.
2106 * @param string $render_id The render ID of the currently rendered module instance.
2107 */
2108 $html = apply_filters( 'hustle_get_module_recaptcha_container', $html, $this->module, $recaptcha, $recaptcha_settings, $render_id );
2109 }
2110
2111 return $html;
2112 }
2113
2114 /**
2115 * Get Cloudflare Turnstile container if configured.
2116 *
2117 * @since 4.0.0
2118 * @param array $fields Fields.
2119 * @return string
2120 */
2121 private function get_turnstile_container( $fields ) {
2122
2123 $html = '';
2124 $fields = $this->module->emails->form_elements;
2125
2126 if ( $this->is_turnstile_active( $fields ) ) {
2127
2128 $turnstile = $fields['turnstile'];
2129 $turnstile_settings = Hustle_Settings_Admin::get_turnstile_settings();
2130 $render_id = self::$render_ids[ $this->module->module_id ];
2131 $css_classes = isset( $turnstile['css_classes'] ) ? $turnstile['css_classes'] : '';
2132 $theme = isset( $turnstile['turnstile_theme'] ) ? $turnstile['turnstile_theme'] : 'auto';
2133 $size = isset( $turnstile['turnstile_size'] ) ? $turnstile['turnstile_size'] : 'normal';
2134 $language = isset( $turnstile['language'] ) ? $turnstile['language'] : $turnstile_settings['language'];
2135
2136 $html .= sprintf(
2137 '<div id="hustle-modal-turnstile-%1$d-%2$d" style="margin-top: 10px;" class="hustle-turnstile %3$s" data-required-error="%4$s" data-sitekey="%5$s" data-theme="%6$s" data-size="%7$s" data-language="%8$s"></div>',
2138 esc_attr( $this->module->id ),
2139 esc_attr( $render_id ),
2140 esc_attr( $css_classes ),
2141 esc_attr( wp_strip_all_tags( html_entity_decode( $turnstile['validation_message'] ) ) ),
2142 esc_attr( $turnstile_settings['turnstile_api_key'] ),
2143 esc_attr( $theme ),
2144 esc_attr( $size ),
2145 esc_attr( $language )
2146 );
2147 }
2148
2149 return $html;
2150 }
2151
2152 /**
2153 * Whether the current module's Turnstile can be displayed.
2154 * Check whether this module has a turnstile field,
2155 * and if the corresponding credentials were already stored.
2156 *
2157 * @since 4.0.0
2158 * @param array $fields This module's fields.
2159 * @return bool
2160 */
2161 private function is_turnstile_active( $fields = array() ) {
2162
2163 if ( empty( $fields ) ) {
2164 $fields = $this->module->emails->form_elements;
2165 }
2166
2167 if ( isset( $fields['turnstile'] ) ) {
2168 $turnstile_settings = Hustle_Settings_Admin::get_turnstile_settings();
2169
2170 if ( ! empty( $turnstile_settings['turnstile_api_key'] ) && ! empty( $turnstile_settings['turnstile_client_secret'] ) ) {
2171 return true;
2172 }
2173 }
2174
2175 return false;
2176 }
2177
2178 /**
2179 * Whether the current module's recaptcha can be displayed
2180 * Check whether this module has a recaptcha field,
2181 * and if the corresponding credentials were already stored.
2182 *
2183 * @since 4.1.1
2184 * @param array $fields This module's fields.
2185 */
2186 private function is_recaptcha_active( $fields = array() ) {
2187
2188 if ( empty( $fields ) ) {
2189 $fields = $this->module->emails->form_elements;
2190 }
2191
2192 // The module does have recaptcha.
2193 if ( isset( $fields['recaptcha'] ) ) {
2194
2195 $recaptcha = $fields['recaptcha'];
2196
2197 $recaptcha_version = empty( $recaptcha['version'] ) ? 'v2_checkbox' : $recaptcha['version'];
2198 $site_key_key = $recaptcha_version . '_site_key';
2199 $secret_key_key = $recaptcha_version . '_secret_key';
2200
2201 $recaptcha_settings = Hustle_Settings_Admin::get_recaptcha_settings();
2202
2203 // Make sure the creds for the selected recaptcha type has been added.
2204 if ( ! empty( $recaptcha_settings[ $site_key_key ] ) && ! empty( $recaptcha_settings[ $secret_key_key ] ) ) {
2205 return true;
2206 }
2207 }
2208
2209 return false;
2210 }
2211
2212 /**
2213 * Handle AJAX display
2214 *
2215 * @since 4.0
2216 * @param Hustle_Module_Model $module Module.
2217 * @param array $data Data.
2218 * @param bool $is_preview Is preview.
2219 * @return string
2220 */
2221 public function ajax_display( Hustle_Module_Model $module, $data = array(), $is_preview = true ) {
2222
2223 self::$is_preview = $is_preview;
2224
2225 if ( ! empty( $data ) ) {
2226 $this->module = $module->load_preview( $data );
2227 } else {
2228 $this->module = $module->load();
2229 }
2230
2231 $response = array(
2232 'html' => '',
2233 'style' => array(),
2234 'script' => array(),
2235 'module' => $this->module,
2236 );
2237
2238 $subtype = Hustle_Module_Model::EMBEDDED_MODULE !== $module->module_type ? null : 'shortcode';
2239 $response['html'] = $this->get_module( $subtype, 'hustle-preview' );
2240
2241 $styles = Hustle_Module_Front::print_front_fonts( $module->get_google_fonts(), true );
2242
2243 // Add the recaptcha script inline for previews.
2244 if ( $is_preview && Hustle_Model::OPTIN_MODE === $this->module->module_mode ) {
2245 $fields = $this->module->emails->form_elements;
2246
2247 // Load the recaptcha script if the module has it, and if the credentials are stored.
2248 if ( $this->is_recaptcha_active( $fields ) ) {
2249
2250 $recaptcha = $fields['recaptcha'];
2251 $source = Hustle_Module_Front::add_recaptcha_script( $recaptcha['recaptcha_language'], true, true );
2252
2253 // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
2254 $response['script'] = '<script src="' . esc_url( $source ) . '" async defer></script>';
2255 }
2256
2257 // Load the Turnstile script if the module has it, and if the credentials are stored.
2258 if ( $this->is_turnstile_active( $fields ) ) {
2259 // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
2260 $response['script'] = ( isset( $response['script'] ) ? $response['script'] : '' ) . '<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>';
2261 }
2262 }
2263
2264 // This might be used later for ajax loading.
2265 ob_start();
2266 $this->print_styles();
2267 $styles .= ob_get_clean();
2268 $response['style'] = $styles;
2269
2270 return $response;
2271 }
2272 }
2273