PluginProbe ʕ •ᴥ•ʔ
Hustle – Email Marketing, Lead Generation, Optins, Popups / 7.8.9
Hustle – Email Marketing, Lead Generation, Optins, Popups v7.8.9
7.8.13 7.8.13.1 trunk 3.0 3.1 3.1.1 3.1.2 3.1.3 3.1.4 4.3.2 4.4.4 4.4.5 4.4.5.1 4.4.5.4 4.6 4.6.1.1 4.6.1.4 4.7.0.2 4.7.0.3 4.7.0.7 4.7.0.9 4.7.1.0 4.7.1.1 4.8.0.0 5.0.0 5.0.1 5.0.1.1 5.0.1.2 5.1 5.1.1 5.1.2 5.1.3 5.1.3.1 5.1.3.2 5.1.4 5.1.5 6.0 6.0.1 6.0.2 6.0.3 6.0.4.2 6.0.5 6.0.6.1 6.0.7 6.0.8.1 6.0.9 7.0.0.1 7.0.2 7.0.3 7.0.4 7.1.0 7.1.1 7.2.0 7.2.1 7.3.0 7.3.1 7.3.3 7.3.5 7.3.6 7.3.7 7.4.0 7.4.1 7.4.11 7.4.13 7.4.13.1 7.4.2 7.4.3 7.4.4 7.4.5 7.4.5.1 7.4.5.2 7.4.6 7.4.7 7.5.0 7.6.0 7.6.1 7.6.3 7.6.4 7.6.6 7.7.0 7.7.1 7.8.0 7.8.1 7.8.10 7.8.10.1 7.8.10.2 7.8.11 7.8.12 7.8.12.1 7.8.2 7.8.3 7.8.4 7.8.5 7.8.6 7.8.7 7.8.8 7.8.9 7.8.9.1 7.8.9.2 7.8.9.3
wordpress-popup / inc / front / hustle-module-renderer.php
wordpress-popup / inc / front Last commit date
non-sshare-styles 9 months ago class-hustle-decorator-non-sshare.php 3 years ago class-hustle-decorator-sshare.php 9 months ago class-hustle-decorator_abstract.php 3 years ago class-hustle-module-preview.php 1 year ago hustle-module-front-ajax.php 1 year ago hustle-module-front.php 2 years ago hustle-module-renderer.php 9 months ago hustle-renderer-abstract.php 3 years ago hustle-renderer-sshare.php 3 years ago
hustle-module-renderer.php
2140 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 array $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
355 // Make both buttons have a label.
356 // And make sure they have an URL if their action isn't to close the module.
357 if ( '' === $content->cta_label || '' === $content->cta_two_label ) {
358 return false;
359
360 } elseif (
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 * Get call to action button.
373 *
374 * @since 4.0.0
375 * @return string
376 */
377 private function get_cta_button() {
378
379 $label = $this->module->content->cta_label;
380 $target = $this->module->content->cta_target;
381 $link = $this->module->content->cta_url;
382
383 $html = '<div class="hustle-cta-container">';
384 $html .= $this->get_cta_markup( $label, $target, $link );
385
386 if ( '2' === $this->module->content->show_cta ) {
387 $label_two = $this->module->content->cta_two_label;
388 $target_two = $this->module->content->cta_two_target;
389 $link_two = $this->module->content->cta_two_url;
390 // CTA #2.
391 $html .= $this->get_cta_markup(
392 $label_two,
393 $target_two,
394 $link_two,
395 'cta_2'
396 );
397 }
398 $html .= '</div>';
399
400 // Display CTA helper text if enabled and not empty.
401 if ( '1' === $this->module->content->cta_helper_show && '' !== $this->module->content->cta_helper_text ) {
402 $allowed_html = array(
403 'a' => array(
404 'href' => true,
405 'title' => true,
406 'target' => true,
407 'alt' => true,
408 ),
409 'b' => array(),
410 'strong' => array(),
411 'i' => array(),
412 'em' => array(),
413 'del' => array(),
414 );
415
416 $cta_helper = '<p class="hustle-cta-helper-text">' . wp_kses( $this->module->content->cta_helper_text, $allowed_html ) . '</p>';
417
418 /**
419 * Filter the whole markup for the CTA helper text
420 *
421 * @since 4.3.0
422 *
423 * @param $cta_helper CTA helper text markup to be shown.
424 */
425 $html .= apply_filters( 'hustle_get_cta_helper_text', $cta_helper );
426 }
427
428 /**
429 * Filter the markup for the CTA buttons.
430 *
431 * @since 4.3.0
432 *
433 * @param $html The markup.
434 * @param $this->module The current module.
435 */
436 return apply_filters( 'hustle_get_cta_buttons', $html, $this->module );
437 }
438
439 /**
440 * Get the markup of each CTA.
441 *
442 * @since 4.3.0
443 *
444 * @param string $label Label.
445 * @param string $target Target.
446 * @param string $url URL.
447 * @param string $cta_type CTA type.
448 * @return string
449 */
450 private function get_cta_markup( $label, $target, $url, $cta_type = 'cta' ) {
451
452 $extra_class = 'cta_2' === $cta_type ? 'hustle-last-button' : '';
453 $class = 'hustle-cta-close hustle-button-close ' . $extra_class;
454 $data = '';
455 $label = $this->input_sanitize( $label );
456
457 if ( 'close' !== $target ) {
458 $data = sprintf( 'href="%s" target="_%s"', esc_url( $url ), esc_attr( $target ) );
459 $class = $extra_class;
460 } else {
461 $data = 'href="#"';
462 }
463
464 /**
465 * Filter the extra classes for the CTA
466 *
467 * @since 4.3.0
468 *
469 * @param $class The class to be added to the button.
470 * @param $is_second Whether it's the CTA #2.
471 * @param $target The target.
472 */
473 $class = apply_filters( 'hustle_cta_extra_classes', $class, $cta_type, $target );
474 $html = '';
475 $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 ) . '"' );
476 $html .= stripcslashes( $label );
477 $html .= '</a>';
478
479 /**
480 * Filter the markup each CTA.
481 *
482 * @since 4.3.0
483 *
484 * @param $html The markup.
485 * @param $label The label.
486 * @param $target The target.
487 * @param $url You guess.
488 * @param $class Extra classes for the button.
489 */
490 return apply_filters( 'hustle_get_cta_buttons', $html, $label, $target, $url, $class );
491 }
492
493 // ====================================
494 // Informational only markup.
495 // ====================================
496
497 /**
498 * Get the body of Informational modules.
499 *
500 * @since 4.0
501 *
502 * @return string
503 */
504 private function get_informational_body() {
505
506 $layout = $this->module->design->style;
507 $content = $this->module->content;
508
509 $module_layout = 'default';
510
511 if ( 'simple' === $layout ) {
512 $module_layout = 'compact';
513 }
514
515 if ( 'cabriolet' === $layout ) {
516 $module_layout = 'stacked';
517 }
518
519 $html = sprintf(
520 '<div class="hustle-info hustle-info--%s">',
521 esc_attr( $module_layout )
522 );
523
524 $html .= '<div class="hustle-main-wrapper">';
525
526 $html .= '<div class="hustle-layout'
527 . ( ! empty( $content->show_cta ) && '1' === $content->show_cta && ! empty( $content->cta_whole )
528 ? ' hustle-whole-module-cta' : '' ) . '">';
529
530 $html .= ( 'cabriolet' !== $layout ) ? $this->get_close_button() : '';
531
532 $html .= $this->get_informational_body_content();
533
534 $html .= '</div>';
535
536 $html .= '</div>';
537
538 // NSA Link.
539 $html .= $this->get_optin_nsa_link( false );
540
541 $html .= '</div>';
542
543 return $html;
544 }
545
546 /**
547 * Get the right optin body according to the design.
548 *
549 * @since 4.0
550 * @return string
551 */
552 private function get_informational_body_content() {
553
554 switch ( $this->module->design->style ) {
555 case 'minimal':
556 return $this->get_informational_design_default();
557
558 case 'simple':
559 return $this->get_informational_design_compact();
560
561 default: // 'cabriolet'.
562 return $this->get_informational_design_stacked();
563
564 }
565 }
566
567 /**
568 * Get default (minimal) layout markup for informational module.
569 *
570 * @since 4.0
571 * @return string
572 */
573 private function get_informational_design_default() {
574
575 $html = '';
576
577 $content = $this->module->content;
578
579 // Header.
580 if ( '' !== $content->title || '' !== $content->sub_title ) {
581
582 $html .= '<div class="hustle-layout-header">';
583
584 if ( '' !== $content->title ) {
585 $html .= '<h3 class="hustle-title">';
586 $html .= $this->input_sanitize( $content->title );
587 $html .= '</h3>';
588 }
589
590 if ( '' !== $content->sub_title ) {
591 $html .= '<h4 class="hustle-subtitle">';
592 $html .= $this->input_sanitize( $content->sub_title );
593 $html .= '</h4>';
594 }
595
596 $html .= '</div>';
597 }
598
599 // Content.
600 if ( '' !== $content->main_content || '' !== $content->feature_image ) {
601
602 $html .= '<div class="hustle-layout-content">';
603
604 if ( '' !== $content->feature_image && 'left' === $this->module->design->feature_image_position ) {
605 $html .= $this->get_feature_image();
606 }
607
608 if ( '' !== $content->main_content ) {
609 $html .= '<div class="hustle-content">';
610 $html .= '<div class="hustle-content-wrap">';
611 $html .= '<div class="hustle-group-content">';
612 $html .= $this->get_module_main_content( $content );
613 $html .= '</div>';
614 $html .= '</div>';
615 $html .= '</div>';
616 }
617
618 if ( '' !== $content->feature_image && 'right' === $this->module->design->feature_image_position ) {
619 $html .= $this->get_feature_image();
620 }
621
622 $html .= '</div>';
623
624 }
625
626 // Footer.
627 if ( $this->is_show_cta( $content ) ) {
628
629 $html .= '<div class="hustle-layout-footer">';
630 $html .= $this->get_cta_button();
631 $html .= '</div>';
632
633 }
634
635 return $html;
636 }
637
638 /**
639 * Get compact (simple) layout markup for informational module.
640 *
641 * @since 4.0
642 * @return string
643 */
644 private function get_informational_design_compact() {
645
646 $html = '';
647
648 $content = $this->module->content;
649
650 // Image (Left).
651 if ( '' !== $content->feature_image && 'left' === $this->module->design->feature_image_position ) {
652 $html .= $this->get_feature_image();
653 }
654
655 // Content.
656 if (
657 '' !== $content->title ||
658 '' !== $content->sub_title ||
659 '' !== $content->main_content ||
660 $this->is_show_cta( $content )
661 ) {
662
663 $html .= '<div class="hustle-content">';
664
665 $html .= '<div class="hustle-content-wrap">';
666
667 if ( '' !== $content->title || '' !== $content->sub_title ) {
668
669 $html .= '<div class="hustle-group-title">';
670
671 if ( '' !== $content->title ) {
672 $html .= '<h3 class="hustle-title">';
673 $html .= $this->input_sanitize( $content->title );
674 $html .= '</h3>';
675 }
676
677 if ( '' !== $content->sub_title ) {
678 $html .= '<h4 class="hustle-subtitle">';
679 $html .= $this->input_sanitize( $content->sub_title );
680 $html .= '</h4>';
681 }
682
683 $html .= '</div>';
684 }
685
686 if ( '' !== $content->main_content ) {
687
688 $html .= '<div class="hustle-group-content">';
689 $html .= $this->get_module_main_content( $content );
690 $html .= '</div>';
691
692 }
693
694 if ( $this->is_show_cta( $content ) ) {
695
696 $html .= $this->get_cta_button();
697
698 }
699
700 $html .= '</div>';
701
702 $html .= '</div>';
703
704 }
705
706 // Image (Right).
707 if ( '' !== $content->feature_image && 'right' === $this->module->design->feature_image_position ) {
708 $html .= $this->get_feature_image();
709 }
710
711 return $html;
712 }
713
714 /**
715 * Get stacked (cabriolet) layout markup for informational module.
716 *
717 * @since 4.0
718 * @return string
719 */
720 private function get_informational_design_stacked() {
721
722 $html = '';
723
724 $content = $this->module->content;
725
726 $html .= $this->get_close_button();
727
728 // Header.
729 $html .= '<div class="hustle-layout-header">';
730
731 if ( '' !== $content->title ) {
732 $html .= '<h3 class="hustle-title">';
733 $html .= $this->input_sanitize( $content->title );
734 $html .= '</h3>';
735 }
736
737 if ( '' !== $content->sub_title ) {
738 $html .= '<h4 class="hustle-subtitle">';
739 $html .= $this->input_sanitize( $content->sub_title );
740 $html .= '</h4>';
741 }
742 $html .= '</div>';
743
744 // Body.
745 if (
746 '' !== $content->main_content ||
747 '' !== $content->feature_image ||
748 $this->is_show_cta( $content )
749 ) {
750
751 $html .= '<div class="hustle-layout-body">';
752
753 // Image (Left).
754 if ( '' !== $content->feature_image && 'left' === $this->module->design->feature_image_position ) {
755 $html .= $this->get_feature_image();
756 }
757
758 if (
759 '' !== $content->main_content ||
760 $this->is_show_cta( $content )
761 ) {
762
763 $html .= '<div class="hustle-content">';
764
765 $html .= '<div class="hustle-content-wrap">';
766
767 if ( '' !== $content->main_content ) {
768
769 $html .= '<div class="hustle-group-content">';
770 $html .= $this->get_module_main_content( $content );
771 $html .= '</div>';
772
773 }
774
775 if ( $this->is_show_cta( $content ) ) {
776 $html .= $this->get_cta_button();
777 }
778
779 $html .= '</div>';
780
781 $html .= '</div>';
782
783 }
784
785 // Image (Right).
786 if ( '' !== $content->feature_image && 'right' === $this->module->design->feature_image_position ) {
787 $html .= $this->get_feature_image();
788 }
789
790 $html .= '</div>';
791
792 }
793
794 return $html;
795 }
796
797 // ====================================
798 // Opt-in only markup.
799 // ====================================
800
801 /**
802 * Get the body of Opt-In modules.
803 *
804 * @since 4.0
805 *
806 * @return string
807 */
808 public function get_optin_body() {
809
810 $layout = $this->module->design->form_layout;
811 $content = $this->module->content;
812
813 $module_layout = 'default';
814
815 if ( 'two' === $layout ) {
816 $module_layout = 'compact';
817 }
818
819 if ( 'three' === $layout ) {
820 $module_layout = 'focus-optin';
821 }
822
823 if ( 'four' === $layout ) {
824 $module_layout = 'focus-content';
825 }
826
827 $html = sprintf(
828 '<div class="hustle-optin hustle-optin--%s">',
829 $module_layout
830 );
831
832 if ( 'two' === $layout ) {
833 $html .= '<div class="hustle-optin-content">';
834 }
835
836 $html .= $this->maybe_get_success_message();
837
838 $html .= '<div class="hustle-layout'
839 . ( ! empty( $content->show_cta ) && '1' === $content->show_cta && ! empty( $content->cta_whole )
840 ? ' hustle-whole-module-cta' : '' ) . '">';
841
842 $html .= '<div class="hustle-main-wrapper">';
843
844 $html .= $this->get_close_button();
845
846 $html .= $this->get_optin_body_content();
847
848 $html .= '</div>';
849
850 $html .= $this->get_optin_nsa_link();
851
852 $html .= '</div>';
853
854 if ( 'two' === $layout ) {
855 $html .= '</div>';
856 }
857
858 $html .= '</div>';
859
860 return $html;
861 }
862
863 /**
864 * Get opt-in success message.
865 *
866 * @since 4.0
867 * @return string
868 */
869 private function maybe_get_success_message() {
870
871 $html = '';
872 $emails = $this->module->emails;
873 $success_option = $emails->after_successful_submission;
874 $success_message = $emails->success_message;
875 $auto_close = '1' === $emails->auto_close_success_message ? Hustle_Time_Helper::to_microseconds( $emails->auto_close_time, $emails->auto_close_unit ) : 'false';
876
877 if ( 'show_success' === $success_option || ( 'redirect' === $success_option && '' === $emails->redirect_url )
878 || 'newtab_thankyou' === $emails->redirect_tab ) {
879
880 $html .= sprintf(
881 '<div class="hustle-success" data-close-delay="%s" style="display: none;">',
882 esc_attr( $auto_close )
883 );
884
885 $html .= '<span class="hustle-icon-check" aria-hidden="true"></span>';
886
887 if ( '' !== $success_message ) {
888
889 $html .= '<div class="hustle-success-content">';
890
891 if ( is_admin() ) {
892 $html .= do_shortcode( $success_message );
893 }
894
895 $html .= '</div>';
896
897 }
898
899 $html .= '</div>';
900
901 }
902
903 return $html;
904 }
905
906 /**
907 * Get the right optin body according to the design.
908 *
909 * @since 4.0
910 * @return string
911 */
912 private function get_optin_body_content() {
913
914 switch ( $this->module->design->form_layout ) {
915 case 'one':
916 return $this->get_optin_design_default();
917
918 case 'two':
919 return $this->get_optin_design_compact();
920
921 case 'three':
922 return $this->get_optin_design_focus_optin();
923
924 default: // four.
925 return $this->get_optin_design_focus_content();
926 }
927 }
928
929 /**
930 * Get the markup according to design "one".
931 *
932 * @since 4.0
933 * @return string
934 */
935 private function get_optin_design_default() {
936
937 $html = '';
938 $content = $this->module->content;
939 $design = $this->module->design;
940
941 $html .= '<div class="hustle-layout-body">';
942
943 if (
944 '' !== $content->title ||
945 '' !== $content->sub_title ||
946 '' !== $content->feature_image ||
947 '' !== $content->main_content ||
948 $this->is_show_cta( $content )
949 ) {
950
951 $html .= sprintf( '<div class="hustle-layout-content hustle-layout-position--%s">', esc_attr( $design->feature_image_position ) );
952
953 if ( '' !== $content->feature_image && ( 'left' === $design->feature_image_position || 'above' === $design->feature_image_position ) ) {
954 $html .= $this->get_feature_image();
955 }
956
957 if (
958 '' !== $content->title ||
959 '' !== $content->sub_title ||
960 '' !== $content->main_content ||
961 $this->is_show_cta( $content )
962 ) {
963
964 $html .= '<div class="hustle-content">';
965
966 $html .= '<div class="hustle-content-wrap">';
967
968 if ( '' !== $content->title || '' !== $content->sub_title ) {
969
970 $html .= '<div class="hustle-group-title">';
971
972 if ( '' !== $content->title ) {
973 $html .= '<h3 class="hustle-title">';
974 $html .= $this->input_sanitize( $content->title );
975 $html .= '</h3>';
976 }
977
978 if ( '' !== $content->sub_title ) {
979 $html .= '<h4 class="hustle-subtitle">';
980 $html .= $this->input_sanitize( $content->sub_title );
981 $html .= '</h4>';
982 }
983
984 $html .= '</div>';
985 }
986
987 if ( '' !== $content->main_content ) {
988 $html .= '<div class="hustle-group-content">';
989 $html .= $this->get_module_main_content( $content );
990 $html .= '</div>';
991 }
992
993 if ( $this->is_show_cta( $content ) ) {
994
995 $html .= $this->get_cta_button();
996
997 }
998
999 $html .= '</div>';
1000
1001 $html .= '</div>';
1002 }
1003
1004 if ( '' !== $content->feature_image && ( 'right' === $design->feature_image_position || 'below' === $design->feature_image_position ) ) {
1005 $html .= $this->get_feature_image();
1006 }
1007
1008 $html .= '</div>';
1009
1010 }
1011
1012 $html .= $this->get_form();
1013
1014 $html .= '</div>';
1015
1016 return $html;
1017 }
1018
1019 /**
1020 * Get the markup according to design "two".
1021 *
1022 * @since 4.0
1023 * @return string
1024 */
1025 private function get_optin_design_compact() {
1026
1027 $html = '';
1028 $content = $this->module->content;
1029
1030 $html .= '<div class="hustle-layout-body">';
1031
1032 if ( '' !== $content->feature_image && 'left' === $this->module->design->feature_image_position ) {
1033 $html .= $this->get_feature_image();
1034 }
1035
1036 $html .= '<div class="hustle-layout-content">';
1037
1038 if (
1039 '' !== $content->title ||
1040 '' !== $content->sub_title ||
1041 '' !== $content->main_content ||
1042 $this->is_show_cta( $content )
1043 ) {
1044
1045 $html .= '<div class="hustle-content">';
1046
1047 $html .= '<div class="hustle-content-wrap">';
1048
1049 if ( '' !== $content->title || '' !== $content->sub_title ) {
1050
1051 $html .= '<div class="hustle-group-title">';
1052
1053 if ( '' !== $content->title ) {
1054 $html .= '<h3 class="hustle-title">';
1055 $html .= $this->input_sanitize( $content->title );
1056 $html .= '</h3>';
1057 }
1058
1059 if ( '' !== $content->sub_title ) {
1060 $html .= '<h4 class="hustle-subtitle">';
1061 $html .= $this->input_sanitize( $content->sub_title );
1062 $html .= '</h4>';
1063 }
1064
1065 $html .= '</div>';
1066
1067 }
1068
1069 if ( '' !== $content->main_content ) {
1070 $html .= '<div class="hustle-group-content">';
1071 $html .= $this->get_module_main_content( $content );
1072 $html .= '</div>';
1073 }
1074
1075 if ( $this->is_show_cta( $content ) ) {
1076
1077 $html .= $this->get_cta_button();
1078
1079 }
1080
1081 $html .= '</div>';
1082
1083 $html .= '</div>';
1084
1085 }
1086
1087 $html .= $this->get_form();
1088
1089 $html .= '</div>';
1090
1091 if ( '' !== $content->feature_image && 'right' === $this->module->design->feature_image_position ) {
1092 $html .= $this->get_feature_image();
1093 }
1094
1095 $html .= '</div>';
1096
1097 return $html;
1098 }
1099
1100 /**
1101 * Get the markup according to design "three".
1102 *
1103 * @since 4.0
1104 * @return string
1105 */
1106 private function get_optin_design_focus_optin() {
1107
1108 $html = '';
1109 $content = $this->module->content;
1110
1111 $html .= '<div class="hustle-layout-body">';
1112
1113 if ( 'right' === $this->module->design->feature_image_position ) {
1114 $html .= $this->get_form();
1115 }
1116
1117 if (
1118 '' !== $content->title ||
1119 '' !== $content->sub_title ||
1120 '' !== $content->feature_image ||
1121 '' !== $content->main_content ||
1122 $this->is_show_cta( $content )
1123 ) {
1124
1125 $html .= '<div class="hustle-layout-content">';
1126
1127 if ( '' !== $content->feature_image ) {
1128 $html .= $this->get_feature_image();
1129 }
1130
1131 if (
1132 '' !== $content->title ||
1133 '' !== $content->sub_title ||
1134 '' !== $content->main_content ||
1135 $this->is_show_cta( $content )
1136 ) {
1137
1138 $html .= '<div class="hustle-content">';
1139
1140 $html .= '<div class="hustle-content-wrap">';
1141
1142 if ( '' !== $content->title || '' !== $content->sub_title ) {
1143
1144 $html .= '<div class="hustle-group-title">';
1145
1146 if ( '' !== $content->title ) {
1147 $html .= '<h3 class="hustle-title">';
1148 $html .= $this->input_sanitize( $content->title );
1149 $html .= '</h3>';
1150 }
1151
1152 if ( '' !== $content->sub_title ) {
1153 $html .= '<h4 class="hustle-subtitle">';
1154 $html .= $this->input_sanitize( $content->sub_title );
1155 $html .= '</h4>';
1156 }
1157
1158 $html .= '</div>';
1159
1160 }
1161
1162 if ( '' !== $content->main_content ) {
1163 $html .= '<div class="hustle-group-content">';
1164 $html .= $this->get_module_main_content( $content );
1165 $html .= '</div>';
1166 }
1167
1168 if ( $this->is_show_cta( $content ) ) {
1169
1170 $html .= $this->get_cta_button();
1171
1172 }
1173
1174 $html .= '</div>';
1175
1176 $html .= '</div>';
1177
1178 }
1179
1180 $html .= '</div>';
1181
1182 }
1183
1184 if ( 'left' === $this->module->design->feature_image_position ) {
1185 $html .= $this->get_form();
1186 }
1187
1188 $html .= '</div>';
1189
1190 return $html;
1191 }
1192
1193 /**
1194 * Get the markup according to design "four".
1195 *
1196 * @since 4.0
1197 * @return string
1198 */
1199 private function get_optin_design_focus_content() {
1200
1201 $html = '';
1202 $content = $this->module->content;
1203
1204 $html .= '<div class="hustle-layout-body">';
1205
1206 if ( 'left' === $this->module->design->feature_image_position ) {
1207
1208 $html .= '<div class="hustle-layout-sidebar">';
1209
1210 if ( '' !== $content->feature_image ) {
1211 $html .= $this->get_feature_image();
1212 }
1213
1214 $html .= $this->get_form();
1215
1216 $html .= '</div>';
1217
1218 }
1219
1220 if (
1221 '' !== $content->title ||
1222 '' !== $content->sub_title ||
1223 '' !== $content->main_content ||
1224 $this->is_show_cta( $content )
1225 ) {
1226
1227 $html .= '<div class="hustle-layout-content">';
1228
1229 $html .= '<div class="hustle-content">';
1230
1231 $html .= '<div class="hustle-content-wrap">';
1232
1233 if ( '' !== $content->title || '' !== $content->sub_title ) {
1234
1235 $html .= '<div class="hustle-group-title">';
1236
1237 if ( '' !== $content->title ) {
1238 $html .= '<h3 class="hustle-title">';
1239 $html .= $this->input_sanitize( $content->title );
1240 $html .= '</h3>';
1241 }
1242
1243 if ( '' !== $content->sub_title ) {
1244 $html .= '<h4 class="hustle-subtitle">';
1245 $html .= $this->input_sanitize( $content->sub_title );
1246 $html .= '</h4>';
1247 }
1248
1249 $html .= '</div>';
1250
1251 }
1252
1253 if ( '' !== $content->main_content ) {
1254 $html .= '<div class="hustle-group-content">';
1255 $html .= $this->get_module_main_content( $content );
1256 $html .= '</div>';
1257 }
1258
1259 if ( $this->is_show_cta( $content ) ) {
1260
1261 $html .= $this->get_cta_button();
1262
1263 }
1264
1265 $html .= '</div>';
1266
1267 $html .= '</div>';
1268
1269 $html .= '</div>';
1270
1271 }
1272
1273 if ( 'right' === $this->module->design->feature_image_position ) {
1274
1275 $html .= '<div class="hustle-layout-sidebar">';
1276
1277 if ( '' !== $content->feature_image ) {
1278 $html .= $this->get_feature_image();
1279 }
1280
1281 $html .= $this->get_form();
1282
1283 $html .= '</div>';
1284
1285 }
1286
1287 $html .= '</div>';
1288
1289 return $html;
1290 }
1291
1292 /**
1293 * Get module fields
1294 *
1295 * @return array
1296 */
1297 private function form_elements() {
1298 /**
1299 * Edit module fields
1300 *
1301 * @since 4.1.1
1302 * @param string $form_elements Current module fields.
1303 */
1304 $fields = apply_filters( 'hustle_form_elements', $this->module->emails->form_elements );
1305
1306 return $fields;
1307 }
1308
1309 /**
1310 * Get the opt-in form markup.
1311 *
1312 * @since 4.0
1313 * @param bool $show_recaptcha Show recaptcha.
1314 * @return string
1315 */
1316 private function get_form( $show_recaptcha = true ) {
1317
1318 $html = '';
1319 $fields = $this->form_elements();
1320 $design = $this->module->design;
1321
1322 $distribution = $design->optin_form_layout;
1323
1324 if ( ! $this->is_admin ) {
1325 $tag = 'form';
1326 $extra_data = ' novalidate="novalidate"';
1327 } else {
1328 $tag = 'div';
1329 $extra_data = '';
1330 }
1331
1332 $html .= sprintf(
1333 '<%s class="hustle-layout-form"%s>',
1334 $tag,
1335 $extra_data
1336 );
1337
1338 // Form fields.
1339 $html .= sprintf(
1340 '<div class="hustle-form%s">',
1341 'inline' === $distribution ? ' hustle-form-inline' : ''
1342 );
1343
1344 $html .= $this->get_form_fields( $fields );
1345
1346 $html .= $this->get_custom_fields();
1347
1348 $html .= '</div>';
1349
1350 // Common hidden fields with useful data.
1351 $html .= $this->get_common_hidden_fields();
1352
1353 // GDPR checkbox.
1354 $html .= $this->get_field_gdpr( $fields );
1355
1356 // reCaptchaget_recaptcha_container.
1357 if ( $show_recaptcha ) {
1358 $html .= $this->get_recaptcha_container( $fields );
1359 }
1360
1361 // Error message.
1362 $html .= $this->get_form_error( $fields );
1363
1364 $html .= sprintf( '</%s>', $tag );
1365
1366 return $html;
1367 }
1368
1369 /**
1370 * Get opt-in form fields markup.
1371 *
1372 * @since 4.0
1373 * @param array $fields Fields.
1374 * @return string
1375 */
1376 private function get_form_fields( $fields ) {
1377 $html = '';
1378 // Keeping the `hustle-proximity-%s` class just for users. Don't use it.
1379 $html .= sprintf(
1380 '<div class="hustle-form-fields hustle-proximity-%s">',
1381 ( '0' === $this->module->design->customize_form_fields_proximity ? 'joined' : 'separated' )
1382 );
1383
1384 $hidden_fields_markup = '';
1385 if ( is_array( $fields ) ) {
1386
1387 foreach ( $fields as $name => $field ) {
1388 if ( in_array( $field['type'], array( 'submit', 'gdpr', 'recaptcha' ), true ) ) {
1389 continue;
1390 }
1391
1392 if ( 'hidden' !== $field['type'] ) {
1393 $html .= $this->get_form_input( $field );
1394 } else {
1395 $hidden_fields_markup .= $this->get_form_hidden_input( $field );
1396 }
1397 }
1398 }
1399 $html .= $this->get_form_submit( $fields );
1400 $html .= $hidden_fields_markup;
1401 $html .= '</div>';
1402 return $html;
1403 }
1404
1405 /**
1406 * Get opt-in form input field markup.
1407 *
1408 * @since 4.0
1409 * @param array $field Field.
1410 * @return string
1411 */
1412 private function get_form_input( $field ) {
1413 $type = isset( $field['type'] ) ? $field['type'] : 'text';
1414 $name = isset( $field['name'] ) ? $field['name'] : 'first_name';
1415 $label = ( '' !== $field['placeholder'] ) ? $this->input_sanitize( $field['placeholder'] ) : $this->input_sanitize( $field['label'] );
1416 $sr_label = ( '' !== $field['label'] ) ? $this->input_sanitize( $field['label'] ) : '';
1417 $required = isset( $field['required'] ) && 'true' === $field['required'] ? true : false;
1418 $to_validate = isset( $field['validate'] ) && 'true' === $field['validate'] ? true : false;
1419
1420 $module_id = $this->module->module_id;
1421 $icon = $type;
1422 $value = '';
1423 $field_icon = '';
1424 $class_icon = '';
1425 $class_input = '';
1426 $class_status = $required ? ' hustle-field-required' : '';
1427 $data_attributes = sprintf( 'data-validate="%s" ', esc_attr( $to_validate ) );
1428
1429 if ( $required && ! empty( $field['required_error_message'] ) ) {
1430 $data_attributes .= sprintf( 'data-required-error="%s" ', esc_attr( wp_strip_all_tags( html_entity_decode( $field['required_error_message'] ) ) ) );
1431 }
1432 if ( $to_validate && ! empty( $field['validation_message'] ) ) {
1433 $data_attributes .= sprintf( 'data-validation-error="%s" ', esc_attr( wp_strip_all_tags( html_entity_decode( $field['validation_message'] ) ) ) );
1434 }
1435
1436 switch ( $type ) {
1437
1438 case 'email':
1439 $type = 'email';
1440 break;
1441
1442 case 'phone':
1443 $type = 'text';
1444 $data_attributes .= 'data-type="phone"';
1445 break;
1446
1447 case 'url':
1448 $icon = 'website';
1449 break;
1450
1451 case 'website':
1452 $type = 'url';
1453 break;
1454
1455 case 'timepicker':
1456 $class_input = 'hustle-time';
1457
1458 if ( '24' === $field['time_format'] ) {
1459 $time_format = 'HH:mm';
1460 $time_hours = ! empty( $field['time_hours'] ) ? $field['time_hours'] : '';
1461 $time_minutes = ! empty( $field['time_minutes'] ) ? $field['time_minutes'] : '';
1462 $time_structure = $time_hours . ':' . $time_minutes;
1463 $time_default = ( '' !== $time_hours && '' !== $time_minutes ) ? $time_structure : '';
1464 $value = $time_default;
1465 } else {
1466 $time_format = 'hh:mm p';
1467 $time_hours = ! empty( $field['time_hours'] ) ? $field['time_hours'] : '';
1468 $time_minutes = ! empty( $field['time_minutes'] ) ? $field['time_minutes'] : '';
1469 $time_period = ! empty( $field['time_period'] ) ? $field['time_period'] : 'am';
1470 $time_structure = $time_hours . ':' . $time_minutes . ' ' . $time_period;
1471 $time_default = ( '' !== $time_hours && '' !== $time_minutes && '' !== $time_period ) ? $time_structure : '';
1472 $value = $time_default;
1473 }
1474
1475 $data_attributes .= 'data-time-format="' . esc_attr( $time_format ) . '" data-time-default="' . esc_attr( $time_default ) . '" data-time-interval="1" data-time-dropdown="true"';
1476 break;
1477
1478 case 'datepicker':
1479 $date_format = $field['date_format'];
1480
1481 // These formats come from 4.0, so we keep the same display as in there, even though it was a bug.
1482 if ( in_array( $field['date_format'], array( 'm/d/Y', 'Y/m/d', 'd/m/Y' ), true ) ) {
1483 $date_format = 'MM d, yy';
1484 }
1485
1486 $class_input = 'hustle-date';
1487 $change_year = wp_json_encode( ! empty( $field['change_year'] ) );
1488 $change_month = wp_json_encode( ! empty( $field['change_month'] ) );
1489 $min_year_range = ! empty( $field['min_year_range'] ) ? $field['min_year_range'] : 'c-10';
1490 $max_year_range = ! empty( $field['max_year_range'] ) ? $field['max_year_range'] : 'c+10';
1491 $year_range = $min_year_range . ':' . $max_year_range;
1492 $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 ) . '"';
1493 break;
1494
1495 default:
1496 break;
1497 }
1498
1499 if ( 'none' !== $this->module->design->form_fields_icon ) {
1500 $field_icon = sprintf( '<span class="hustle-icon-%s"></span>', esc_attr( $icon ) );
1501 $class_icon = ' hustle-field-icon--' . $this->module->design->form_fields_icon;
1502 }
1503
1504 $classes = array(
1505 sprintf(
1506 'hustle-field%s%s',
1507 esc_attr( $class_icon ),
1508 esc_attr( $class_status )
1509 ),
1510 );
1511
1512 if ( ! empty( $value ) ) {
1513 $classes[] = 'hustle-field-filled';
1514 }
1515
1516 if ( isset( $field['css_classes'] ) ) {
1517 $classes[] = $field['css_classes'];
1518 }
1519
1520 $html = sprintf(
1521 '<div class="%s">',
1522 esc_attr( implode( ' ', $classes ) )
1523 );
1524
1525 if ( '' !== $sr_label ) {
1526
1527 $html .= sprintf(
1528 '<label for="hustle-field-%s-module-%s" id="hustle-field-%s-module-%s-label" class="hustle-screen-reader">%s</label>',
1529 esc_attr( $name ),
1530 esc_attr( $module_id ),
1531 esc_attr( $name ),
1532 esc_attr( $module_id ),
1533 $sr_label
1534 );
1535
1536 }
1537
1538 $html .= sprintf(
1539 '<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.
1540 esc_attr( $name ),
1541 esc_attr( $module_id ),
1542 esc_attr( $type ),
1543 esc_attr( $class_input ),
1544 esc_attr( $name ),
1545 esc_attr( $value ),
1546 esc_attr( $name ),
1547 esc_attr( $module_id ),
1548 $data_attributes
1549 );
1550
1551 $html .= '<span class="hustle-input-label" aria-hidden="true" style="flex-flow: row nowrap;">';
1552 $html .= $field_icon;
1553 $html .= sprintf( '<span>%s</span>', $label );
1554 $html .= '</span>';
1555
1556 $html .= '</div>';
1557
1558 return $html;
1559 }
1560
1561 /**
1562 * Get hidden value.
1563 *
1564 * @param array $field_data Current field data.
1565 * @return string
1566 */
1567 public static function get_hidden_value( $field_data ) {
1568 $value = '';
1569
1570 switch ( $field_data['default_value'] ) {
1571
1572 case 'user_ip':
1573 $value = Opt_In_Geo::get_user_ip();
1574 break;
1575 case 'date_mdy':
1576 $value = date_i18n( 'm/d/Y', Hustle_Time_Helper::get_local_timestamp(), true );
1577 break;
1578 case 'date_dmy':
1579 $value = date_i18n( 'd/m/Y', Hustle_Time_Helper::get_local_timestamp(), true );
1580 break;
1581 case 'embed_id':
1582 $value = Opt_In_Utils::get_post_data( 'ID' );
1583 break;
1584 case 'embed_title':
1585 $value = Opt_In_Utils::get_post_data( 'post_title' );
1586 break;
1587 case 'embed_url':
1588 $value = Opt_In_Utils::get_current_url();
1589 break;
1590 case 'user_agent':
1591 $value = filter_input( INPUT_SERVER, 'HTTP_USER_AGENT', FILTER_SANITIZE_SPECIAL_CHARS );
1592 break;
1593 case 'refer_url':
1594 $value = filter_input( INPUT_SERVER, 'HTTP_REFERER', FILTER_SANITIZE_SPECIAL_CHARS );
1595 if ( ! $value ) {
1596 $value = Opt_In_Utils::get_current_url();
1597 }
1598 break;
1599 case 'user_id':
1600 $value = Opt_In_Utils::get_user_data( 'ID' );
1601 break;
1602 case 'user_name':
1603 $value = Opt_In_Utils::get_user_data( 'display_name' );
1604 break;
1605 case 'user_email':
1606 $value = Opt_In_Utils::get_user_data( 'user_email' );
1607 break;
1608 case 'user_login':
1609 $value = Opt_In_Utils::get_user_data( 'user_login' );
1610 break;
1611 case 'custom_value':
1612 $value = $field_data['custom_value'];
1613 break;
1614 case 'query_parameter':
1615 $value = $field_data['query_parameter'] ? (string) filter_input( INPUT_GET, $field_data['query_parameter'], FILTER_SANITIZE_SPECIAL_CHARS ) : '';
1616 break;
1617
1618 default:
1619 break;
1620 }
1621
1622 return $value;
1623 }
1624
1625 /**
1626 * Get the markup for hidden fields.
1627 *
1628 * @since 4.0.4
1629 * @param array $field Current field data.
1630 * @return string
1631 */
1632 private function get_form_hidden_input( $field ) {
1633 $value = self::get_hidden_value( $field );
1634
1635 /**
1636 * Edit the value of the hidden field.
1637 *
1638 * @since 4.0.4
1639 * @param string $value Current value.
1640 * @param array $field Current field data.
1641 * @param Hustle_Module_Model $this->module Instance of the current module.
1642 */
1643 $value = apply_filters( 'hustle_field_hidden_field_value', $value, $field, $this->module );
1644
1645 $html = sprintf(
1646 '<input type="hidden" name="%s" value="%s"/>',
1647 esc_attr( $field['name'] ),
1648 esc_attr( $value )
1649 );
1650
1651 return $html;
1652 }
1653
1654 /**
1655 * Get opt-in form submit button markup.
1656 *
1657 * @since 4.0
1658 * @param array $fields Fields.
1659 * @return string
1660 */
1661 private function get_form_submit( $fields ) {
1662
1663 $html = '';
1664
1665 $label = __( 'Submit', 'hustle' );
1666 $loading = __( 'Form is being submitted, please wait a bit.', 'hustle' );
1667
1668 $classes = isset( $fields['submit']['css_classes'] ) ? $fields['submit']['css_classes'] : '';
1669
1670 if ( isset( $fields['submit'] ) && isset( $fields['submit']['label'] ) ) {
1671 $label = wp_strip_all_tags( html_entity_decode( $fields['submit']['label'] ) );
1672 }
1673
1674 $html .= sprintf( '<button class="hustle-button hustle-button-submit %s" aria-live="polite" data-loading-text="%s">', esc_attr( $classes ), esc_attr( $loading ) );
1675
1676 $html .= sprintf( '<span class="hustle-button-text">%s</span>', esc_html( $label ) );
1677
1678 $html .= '<span class="hustle-icon-loader hustle-loading-icon" aria-hidden="true"></span>';
1679
1680 $html .= '</button>';
1681
1682 return $html;
1683 }
1684
1685 /**
1686 * Get opt-in custom fields markup.
1687 * These custom fields are added by provider's, for example: Mailchimp groups.
1688 *
1689 * @since 4.0
1690 * @return string
1691 * @throws Excpetion Addon field isn't a string.
1692 */
1693 private function get_custom_fields() {
1694
1695 $html = '';
1696
1697 $connected_addons = Hustle_Provider_Utils::get_addons_instance_connected_with_module( $this->module->module_id );
1698
1699 foreach ( $connected_addons as $connected_addon ) {
1700
1701 try {
1702
1703 $form_hooks = $connected_addon->get_addon_form_hooks( $this->module->module_id );
1704
1705 if ( $form_hooks instanceof Hustle_Provider_Form_Hooks_Abstract ) {
1706 $addon_fields = $form_hooks->add_front_form_fields( $this->module );
1707
1708 // Log errors.
1709 if ( ! is_string( $addon_fields ) ) {
1710 throw new Excpetion( 'The returned markup should be a string.' );
1711 }
1712
1713 $html .= $addon_fields;
1714 }
1715 } catch ( Exception $e ) {
1716 Hustle_Utils::maybe_log( $connected_addon->get_slug(), 'failed to add custom front form fields.', $e->getMessage() );
1717 }
1718 }
1719
1720 return $html;
1721 }
1722
1723 /**
1724 * Get common hidden fields with data about the displayed module.
1725 *
1726 * @since 4.0
1727 * @return string
1728 */
1729 private function get_common_hidden_fields() {
1730
1731 $html = '<input type="hidden" name="hustle_module_id" value="' . esc_attr( $this->module->module_id ) . '">';
1732
1733 $html .= '<input type="hidden" name="post_id" value="' . esc_attr( $this->get_post_id() ) . '">';
1734
1735 if ( ! empty( $this->sub_type ) ) {
1736 $html .= '<input type="hidden" name="hustle_sub_type" value="' . esc_attr( $this->sub_type ) . '">';
1737 }
1738
1739 return $html;
1740 }
1741
1742 /**
1743 * Get the GDPR checkbox field markup.
1744 *
1745 * @since 4.0
1746 * @param array $fields Fields.
1747 * @return string
1748 */
1749 private function get_field_gdpr( $fields ) {
1750
1751 $html = '';
1752 $module_id = $this->module->module_id;
1753 $render_id = self::$render_ids[ $module_id ];
1754 $classes = isset( $fields['gdpr']['css_classes'] ) ? $fields['gdpr']['css_classes'] : '';
1755
1756 if ( isset( $fields['gdpr'] ) ) {
1757
1758 $html .= sprintf(
1759 '<label for="hustle-gdpr-module-%d-%d" class="hustle-checkbox hustle-gdpr %s">',
1760 esc_attr( $module_id ),
1761 esc_attr( $render_id ),
1762 esc_attr( $classes )
1763 );
1764
1765 $data_attributes = ! empty( $fields['gdpr']['required_error_message'] ) ?
1766 sprintf( 'data-required-error="%s" ', esc_attr( wp_strip_all_tags( html_entity_decode( $fields['gdpr']['required_error_message'] ) ) ) ) : '';
1767
1768 $html .= sprintf(
1769 '<input type="checkbox" name="gdpr" id="hustle-gdpr-module-%d-%d" %s />',
1770 esc_attr( $module_id ),
1771 esc_attr( $render_id ),
1772 $data_attributes
1773 );
1774
1775 $html .= '<span aria-hidden="true"></span>';
1776
1777 $html .= sprintf(
1778 '<span>%s</span>',
1779 $this->input_sanitize( $fields['gdpr']['gdpr_message'] )
1780 );
1781
1782 $html .= '</label>';
1783
1784 }
1785
1786 return $html;
1787 }
1788
1789 /**
1790 * Get the filtered and parsed main content for the module.
1791 *
1792 * @since 4.0
1793 *
1794 * @param object $content Content.
1795 * @return string
1796 */
1797 private function get_module_main_content( $content ) {
1798
1799 $allowed_html = wp_kses_allowed_html( 'post' );
1800
1801 // iframe.
1802 $allowed_html['iframe'] = array(
1803 'src' => array(),
1804 'height' => array(),
1805 'width' => array(),
1806 'frameborder' => array(),
1807 'allowfullscreen' => array(),
1808 );
1809 // Form.
1810 $allowed_html['form'] = array(
1811 'class' => array(),
1812 'action' => true,
1813 'accept' => true,
1814 'accept-charset' => true,
1815 'enctype' => true,
1816 'method' => true,
1817 'name' => true,
1818 'target' => true,
1819 'role' => array(),
1820 );
1821 // Inputs.
1822 $allowed_html['input'] = array(
1823 'class' => array(),
1824 'id' => array(),
1825 'name' => array(),
1826 'value' => array(),
1827 'type' => array(),
1828 'placeholder' => array(),
1829 );
1830 // Select.
1831 $allowed_html['select'] = array(
1832 'class' => array(),
1833 'id' => array(),
1834 'name' => array(),
1835 'value' => array(),
1836 'type' => array(),
1837 );
1838 // Select options.
1839 $allowed_html['option'] = array(
1840 'selected' => array(),
1841 );
1842 // Style.
1843 $allowed_html['style'] = array(
1844 'types' => array(),
1845 );
1846
1847 // i for fontawesome.
1848 $allowed_html['i'] = array(
1849 'class' => array(),
1850 );
1851 // Keep allowing scripts because users are using it.
1852 $allowed_html['script'] = array(
1853 'async' => array(),
1854 'crossorigin' => array(),
1855 'integrity' => array(),
1856 'referrerpolicy' => array(),
1857 'nomodule' => array(),
1858 'src' => array(),
1859 'type' => array(),
1860 'defer' => array(),
1861 'charset' => array(),
1862 );
1863
1864 /**
1865 * Allows editing the allowed html tags for the modules' main content.
1866 *
1867 * @since 4.0.0.1
1868 */
1869 $allowed_html = apply_filters( 'hustle_module_main_content_allowed_html', $allowed_html, $this->module );
1870 $content = wpautop( wp_kses( $content->main_content, $allowed_html ) );
1871
1872 /**
1873 * Allows editing the escaped main content before doing the shortcodes.
1874 *
1875 * @since 4.0.0.1
1876 */
1877 $content = apply_filters( 'hustle_module_main_content', $content, $this->module );
1878
1879 // Process the [embed] shortcode.
1880 if ( has_shortcode( $content, 'embed' ) ) {
1881 $wp_embed = new WP_Embed();
1882 $content = $wp_embed->run_shortcode( $content );
1883 }
1884
1885 return do_shortcode( $content );
1886 }
1887
1888 /**
1889 * Get the filtered and parsed content for the module.
1890 *
1891 * @since 4.2.1
1892 *
1893 * @param object $content Contet.
1894 * @return string
1895 */
1896 public function input_sanitize( $content ) {
1897 $allowed_html = wp_kses_allowed_html( 'post' );
1898
1899 /**
1900 * Custom filtering for every other field than main content.
1901 * By default it uses wp_kses post rules.
1902 *
1903 * @since 4.2.1
1904 */
1905 $allowed_html = apply_filters( 'hustle_module_input_content_allowed_html', $allowed_html, $this->module );
1906
1907 $content = wp_kses( $content, $allowed_html );
1908 return $content;
1909 }
1910
1911 /**
1912 * Get the opt-in form error message markup.
1913 *
1914 * @since 4.0
1915 * @param array $fields Fields.
1916 * @return string
1917 */
1918 private function get_form_error( $fields ) {
1919
1920 if ( isset( $fields['submit'] ) && ! empty( $fields['submit']['error_message'] ) ) {
1921 $default_error = esc_attr( wp_strip_all_tags( html_entity_decode( $fields['submit']['error_message'] ) ) );
1922 } else {
1923 $default_error = __( 'There was an error submitting the form', 'hustle' );
1924 }
1925
1926 $html = sprintf(
1927 '<div class="hustle-error-message" style="display: none;" data-default-error="%s">',
1928 esc_attr( $default_error )
1929 );
1930
1931 $html .= '</div>';
1932
1933 return $html;
1934 }
1935
1936 /**
1937 * Get opt-in never see link markup.
1938 *
1939 * @since 4.0
1940 * @param bool $wrapper Wrapper.
1941 * @return string
1942 */
1943 private function get_optin_nsa_link( $wrapper = true ) {
1944
1945 $html = '';
1946
1947 if ( Hustle_Module_Model::EMBEDDED_MODULE !== $this->module->module_type ) {
1948
1949 $content = $this->module->content;
1950 $message = ( '' !== $content->never_see_link_text ) ? $content->never_see_link_text : esc_html__( 'Never see this message again', 'hustle' );
1951
1952 if ( (int) $content->show_never_see_link ) {
1953 $html .= ( true === $wrapper ) ? '<div class="hustle-layout-footer">' : '';
1954 $html .= '<p class="hustle-nsa-link">';
1955 $html .= '<a href="#">' . $this->input_sanitize( $message ) . '</a>';
1956 $html .= '</p>';
1957 $html .= ( true === $wrapper ) ? '</div>' : '';
1958 }
1959 }
1960 return $html;
1961 }
1962
1963 /**
1964 * Get recaptcha container if configured.
1965 *
1966 * @since 4.0
1967 * @param array $fields Fields.
1968 * @return string
1969 */
1970 private function get_recaptcha_container( $fields ) {
1971
1972 $html = '';
1973 $fields = $this->module->emails->form_elements;
1974
1975 // Check if this module has a recaptcha field, and that its creds were added.
1976 if ( $this->is_recaptcha_active( $fields ) ) {
1977
1978 $recaptcha = $fields['recaptcha'];
1979
1980 $recaptcha_version = empty( $recaptcha['version'] ) ? 'v2_checkbox' : $recaptcha['version'];
1981 $site_key_key = $recaptcha_version . '_site_key';
1982
1983 if ( 'v2_checkbox' === $recaptcha_version ) {
1984 $size = $recaptcha['recaptcha_type'];
1985 $badge = '';
1986 $show_badge = true;
1987
1988 } else {
1989 $size = 'invisible';
1990 $show_badge = '1' === $recaptcha[ $recaptcha_version . '_show_badge' ];
1991 $badge = 'inline';
1992 }
1993
1994 $recaptcha_classes = isset( $recaptcha['css_classes'] ) ? $recaptcha['css_classes'] : '';
1995
1996 if ( ! $show_badge ) {
1997 $recaptcha_classes .= ' hustle-recaptcha-nobadge';
1998 }
1999
2000 $recaptcha_theme = ( 'v2_invisible' === $recaptcha['version'] ) ? $recaptcha['v2_invisible_theme'] : $recaptcha['recaptcha_theme'];
2001
2002 $extra_data = sprintf(
2003 'data-size="%s" data-theme="%s" data-badge="%3$s"',
2004 esc_attr( $size ),
2005 esc_attr( $recaptcha_theme ),
2006 esc_attr( $badge )
2007 );
2008
2009 $recaptcha_settings = Hustle_Settings_Admin::get_recaptcha_settings();
2010 $render_id = self::$render_ids[ $this->module->module_id ];
2011 $html .= sprintf(
2012 '<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>',
2013 esc_attr( $this->module->id ),
2014 esc_attr( $render_id ),
2015 esc_attr( $recaptcha_classes ),
2016 esc_attr( wp_strip_all_tags( html_entity_decode( $fields['recaptcha']['validation_message'] ) ) ),
2017 esc_attr( $recaptcha_settings[ $site_key_key ] ),
2018 esc_attr( $recaptcha_version ),
2019 $extra_data
2020 );
2021
2022 // Display custom text instead of badge if hidden.
2023 if ( ! $show_badge ) {
2024
2025 $html .= sprintf(
2026 '<div class="hustle-recaptcha-copy">%s</div>',
2027 $this->input_sanitize( $recaptcha[ $recaptcha_version . '_badge_replacement' ] )
2028 );
2029 }
2030
2031 // The input that will hold the recaptcha's response for backend validation on form submit.
2032 $html .= '<input type="hidden" name="recaptcha-response" class="recaptcha-response-input" value="">';
2033
2034 /**
2035 * Filter the markup for the recaptcha container
2036 *
2037 * @since 4.1.1
2038 *
2039 * @param object $this->module Current module. Instance of Hustle_Module_Model.
2040 * @param array $recaptcha Module's recaptcha field settings.
2041 * @param array $recaptcha_settings Global stored recaptcha credentials.
2042 * @param string $render_id The render ID of the currently rendered module instance.
2043 */
2044 $html = apply_filters( 'hustle_get_module_recaptcha_container', $html, $this->module, $recaptcha, $recaptcha_settings, $render_id );
2045 }
2046
2047 return $html;
2048
2049 }
2050
2051 /**
2052 * Whether the current module's recaptcha can be displayed
2053 * Check whether this module has a recaptcha field,
2054 * and if the corresponding credentials were already stored.
2055 *
2056 * @since 4.1.1
2057 * @param array $fields This module's fields.
2058 */
2059 private function is_recaptcha_active( $fields = array() ) {
2060
2061 if ( empty( $fields ) ) {
2062 $fields = $this->module->emails->form_elements;
2063 }
2064
2065 // The module does have recaptcha.
2066 if ( isset( $fields['recaptcha'] ) ) {
2067
2068 $recaptcha = $fields['recaptcha'];
2069
2070 $recaptcha_version = empty( $recaptcha['version'] ) ? 'v2_checkbox' : $recaptcha['version'];
2071 $site_key_key = $recaptcha_version . '_site_key';
2072 $secret_key_key = $recaptcha_version . '_secret_key';
2073
2074 $recaptcha_settings = Hustle_Settings_Admin::get_recaptcha_settings();
2075
2076 // Make sure the creds for the selected recaptcha type has been added.
2077 if ( ! empty( $recaptcha_settings[ $site_key_key ] ) && ! empty( $recaptcha_settings[ $secret_key_key ] ) ) {
2078 return true;
2079 }
2080 }
2081
2082 return false;
2083 }
2084
2085 /**
2086 * Handle AJAX display
2087 *
2088 * @since 4.0
2089 * @param Hustle_Module_Model $module Module.
2090 * @param array $data Data.
2091 * @param bool $is_preview Is preview.
2092 * @return string
2093 */
2094 public function ajax_display( Hustle_Module_Model $module, $data = array(), $is_preview = true ) {
2095
2096 self::$is_preview = $is_preview;
2097
2098 if ( ! empty( $data ) ) {
2099 $this->module = $module->load_preview( $data );
2100 } else {
2101 $this->module = $module->load();
2102 }
2103
2104 $response = array(
2105 'html' => '',
2106 'style' => array(),
2107 'script' => array(),
2108 'module' => $this->module,
2109 );
2110
2111 $subtype = Hustle_Module_Model::EMBEDDED_MODULE !== $module->module_type ? null : 'shortcode';
2112 $response['html'] = $this->get_module( $subtype, 'hustle-preview' );
2113
2114 $styles = Hustle_Module_Front::print_front_fonts( $module->get_google_fonts(), true );
2115
2116 // Add the recaptcha script inline for previews.
2117 if ( $is_preview && Hustle_Model::OPTIN_MODE === $this->module->module_mode ) {
2118 $fields = $this->module->emails->form_elements;
2119
2120 // Load the recaptcha script if the module has it, and if the credentials are stored.
2121 if ( $this->is_recaptcha_active( $fields ) ) {
2122
2123 $recaptcha = $fields['recaptcha'];
2124 $source = Hustle_Module_Front::add_recaptcha_script( $recaptcha['recaptcha_language'], true, true );
2125
2126 // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
2127 $response['script'] = '<script src="' . esc_url( $source ) . '" async defer></script>';
2128 }
2129 }
2130
2131 // This might be used later for ajax loading.
2132 ob_start();
2133 $this->print_styles();
2134 $styles .= ob_get_clean();
2135 $response['style'] = $styles;
2136
2137 return $response;
2138 }
2139 }
2140