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