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 / hustle-module-model.php
wordpress-popup / inc Last commit date
display-conditions 3 years ago front 9 months ago helpers 9 months ago metas 3 years ago multisite 3 years ago palettes 3 years ago provider 3 years ago providers 9 months ago templates 3 years ago update 3 years ago class-hustle-admin-page-abstract.php 9 months ago class-hustle-black-friday-campaign.php 7 months ago class-hustle-condition-factory.php 6 years ago class-hustle-cross-sell.php 9 months ago class-hustle-dashboard-admin.php 3 years ago class-hustle-data.php 3 years ago class-hustle-db.php 2 years ago class-hustle-installer.php 4 years ago class-hustle-meta.php 3 years ago class-hustle-module-admin.php 9 months ago class-hustle-module-collection.php 3 years ago class-hustle-module-page-abstract.php 2 years ago class-hustle-notifications.php 3 years ago class-hustle-settings-admin.php 3 years ago class-hustle-tutorials-page.php 4 years ago class-hustle-wp-dashboard-page.php 3 years ago hustle-deletion.php 3 years ago hustle-embedded-admin.php 3 years ago hustle-entries-admin.php 3 years ago hustle-entry-model.php 3 years ago hustle-general-data-protection.php 3 years ago hustle-init.php 7 months ago hustle-mail.php 3 years ago hustle-migration.php 3 years ago hustle-model.php 3 years ago hustle-module-model.php 9 months ago hustle-module-widget-legacy.php 3 years ago hustle-module-widget.php 3 years ago hustle-modules-common-admin-ajax.php 9 months ago hustle-popup-admin.php 3 years ago hustle-providers-admin.php 3 years ago hustle-providers.php 3 years ago hustle-settings-admin-ajax.php 3 years ago hustle-settings-page.php 3 years ago hustle-slidein-admin.php 3 years ago hustle-sshare-admin.php 3 years ago hustle-sshare-model.php 3 years ago hustle-tracking-model.php 3 years ago opt-in-geo.php 9 months ago opt-in-utils.php 3 years ago opt-in-wpmudev-api.php 3 years ago
hustle-module-model.php
945 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Hustle_Module_Model
4 *
5 * @package Hustle
6 */
7
8 /**
9 * Class Hustle_Module_Model
10 */
11 class Hustle_Module_Model extends Hustle_Model {
12
13 /**
14 * Get the sub-types for embedded modules.
15 *
16 * @since the beggining of time
17 * @since 4.0 "after_content" changed to "inline"
18 * @param bool $with_titles With titles.
19 *
20 * @return array
21 */
22 public static function get_embedded_types( $with_titles = false ) {
23 if ( ! $with_titles ) {
24 return array( 'inline', 'widget', 'shortcode' );
25 } else {
26 return array(
27 'inline' => __( 'Inline', 'hustle' ),
28 'widget' => __( 'Widget', 'hustle' ),
29 'shortcode' => __( 'Shortcode', 'hustle' ),
30 );
31 }
32 }
33
34 /**
35 * Get the sub-types for this module.
36 *
37 * @since 4.0
38 * @param bool $with_titles With titles.
39 *
40 * @return array
41 */
42 public function get_sub_types( $with_titles = false ) {
43 if ( self::EMBEDDED_MODULE === $this->module_type ) {
44 return self::get_embedded_types( $with_titles );
45 }
46
47 return array();
48 }
49
50 /**
51 * Gets the instance of the decorator class for this module type.
52 *
53 * @since 4.3.0
54 *
55 * @return Hustle_Decorator_Non_Sshare
56 */
57 public function get_decorator_instance() {
58 return new Hustle_Decorator_Non_Sshare( $this );
59 }
60
61 /**
62 * Content Model based upon module type.
63 *
64 * @return Class
65 */
66 public function get_content() {
67 $data = $this->get_settings_meta( self::KEY_CONTENT );
68
69 if ( ! Opt_In_Utils::is_free() ) {
70 if ( ! empty( $data['background_image'] ) ) {
71 $data['background_image'] = self::replace_free_to_pro_folder( $data['background_image'] );
72 }
73 if ( ! empty( $data['feature_image'] ) ) {
74 $data['feature_image'] = self::replace_free_to_pro_folder( $data['feature_image'] );
75 }
76 }
77
78 return new Hustle_Meta_Base_Content( $data, $this );
79 }
80
81 /**
82 * It applies for users which was upgraded from free to pro version
83 *
84 * @param string $subject String which includes an old path.
85 * @return string
86 */
87 private static function replace_free_to_pro_folder( $subject ) {
88 $free_plugin_name = 'wordpress-popup/';
89 $pro_plugin_name = 'hustle/';
90 return str_replace( '/plugins/' . $free_plugin_name, '/plugins/' . $pro_plugin_name, $subject );
91 }
92
93 /**
94 * Get the content of the data stored under 'emails' meta.
95 *
96 * @since 4.0
97 *
98 * @return Hustle_Meta_Base_Emails
99 */
100 public function get_emails() {
101 $data = $this->get_settings_meta( self::KEY_EMAILS );
102
103 return new Hustle_Meta_Base_Emails( $data, $this );
104 }
105
106 /**
107 * Get the module's settings for the given provider.
108 *
109 * @since 4.0
110 *
111 * @param string $slug Slug.
112 * @param bool $get_cached Get cached.
113 * @return array
114 */
115 public function get_provider_settings( $slug, $get_cached = true ) {
116 return $this->get_settings_meta( $slug . self::KEY_PROVIDER, array(), $get_cached );
117 }
118
119 /**
120 * Save the module's settings for the given provider.
121 *
122 * @since 4.0
123 *
124 * @param string $slug Slug.
125 * @param array $data Data.
126 * @return array
127 */
128 public function set_provider_settings( $slug, $data ) {
129 return $this->update_meta( $slug . self::KEY_PROVIDER, $data );
130 }
131
132 /**
133 * Get the all-integrations module's settings.
134 * This is not each provider's settings. Instead, these are per module settings
135 * that are applied to all the active providers of this module.
136 *
137 * @since 4.0
138 *
139 * @return array
140 */
141 public function get_integrations_settings() {
142 $stored = $this->get_settings_meta( self::KEY_INTEGRATIONS_SETTINGS );
143 return new Hustle_Meta_Base_Integrations( $stored, $this );
144 }
145
146 /**
147 * Get design
148 *
149 * @return \Hustle_Meta_Base_Design
150 */
151 public function get_design() {
152 $stored = $this->get_settings_meta( self::KEY_DESIGN );
153 return new Hustle_Meta_Base_Design( $stored, $this );
154 }
155
156 /**
157 * Get the stored settings for the "Display" tab.
158 * Used for Embedded.
159 *
160 * @since 4.0
161 *
162 * @return Hustle_Meta_Base_Display
163 */
164 public function get_display() {
165 return new Hustle_Meta_Base_Display( $this->get_settings_meta( self::KEY_DISPLAY_OPTIONS ), $this );
166 }
167
168 /**
169 * Used when populating data with "get".
170 */
171 public function get_settings() {
172 $saved = $this->get_settings_meta( self::KEY_SETTINGS );
173
174 // We made this an array in 4.1.0. Some sites didn't run the migration for some reason.
175 // This prevents the old string from triggering php errors and warnings.
176 // It'd be even better to find out why that migration didn't run in some sites.
177 if ( ! empty( $saved['triggers']['trigger'] ) && ! is_array( $saved['triggers']['trigger'] ) ) {
178 $saved['triggers']['trigger'] = array( $saved['triggers']['trigger'] );
179 }
180
181 if ( self::POPUP_MODULE === $this->module_type ) {
182 return new Hustle_Popup_Settings( $saved, $this );
183
184 } elseif ( self::EMBEDDED_MODULE === $this->module_type ) {
185 return new Hustle_Meta_Base_Settings( $saved, $this );
186
187 } elseif ( self::SLIDEIN_MODULE === $this->module_type ) {
188 return new Hustle_Slidein_Settings( $saved, $this );
189 }
190
191 return false;
192 }
193
194 /**
195 * Get the stored schedule flags
196 *
197 * @since 4.2.0
198 * @return array
199 */
200 public function get_schedule_flags() {
201 $default = array(
202 'is_currently_scheduled' => '1',
203 'check_schedule_at' => 1,
204 );
205
206 return $this->get_settings_meta( 'schedule_flags', $default );
207 }
208
209 /**
210 * Set the schedule flags.
211 *
212 * @since 4.2.0
213 * @param array $flags Flags.
214 * @return void
215 */
216 public function set_schedule_flags( $flags ) {
217 $this->update_meta( 'schedule_flags', $flags );
218 }
219
220 /**
221 * Get the module's data. Used to display it.
222 *
223 * @since 3.0.7
224 *
225 * @return array
226 */
227 public function get_module_data_to_display() {
228 $settings = array( 'settings' => $this->get_settings()->to_array() );
229 $data = array_merge( $settings, $this->get_data() );
230
231 return $data;
232 }
233
234 /**
235 * Get the form fields of this module, if any.
236 *
237 * @since 4.0
238 *
239 * @return null|array
240 */
241 public function get_form_fields() {
242
243 if ( 'social_sharing' === $this->module_type || 'informational' === $this->module_mode ) {
244 return null;
245 }
246
247 $emails_data = empty( $this->emails ) ? $this->get_emails()->to_array() : (array) $this->emails;
248 /**
249 * Edit module fields
250 *
251 * @since 4.1.1
252 * @param string $form_elements Current module fields.
253 */
254 $form_fields = apply_filters( 'hustle_form_elements', $emails_data['form_elements'] );
255
256 return $form_fields;
257
258 }
259
260 /**
261 * Create a new module of the provided mode and type.
262 *
263 * @since 4.0
264 *
265 * @param array $data Must contain the Module's 'mode', 'name' and 'type.
266 * @return int|false Module ID if successfully saved. False otherwise.
267 */
268 public function create_new( $data ) {
269 $module_populated = $this->populate_module_from_data( $data );
270 if ( ! $module_populated ) {
271 return false;
272 }
273
274 // Save to modules table.
275 $this->save();
276
277 $data = $this->sanitize_module( $data );
278
279 // Save the new module's meta.
280 $this->store_new_module_meta( $data );
281
282 $this->activate_providers( $data );
283
284 return $this->id;
285 }
286
287 /**
288 * Populates the current model with the given data.
289 *
290 * @since 4.3.4
291 *
292 * @param array $data Data to populate the module with.
293 * @return bool
294 */
295 private function populate_module_from_data( $data ) {
296 // Verify it's a valid module type.
297 if ( ! in_array( $data['module_type'], array( self::POPUP_MODULE, self::SLIDEIN_MODULE, self::EMBEDDED_MODULE ), true ) ) {
298 return false;
299 }
300
301 // Abort if the mode isn't set.
302 if ( ! in_array( $data['module_mode'], array( 'optin', 'informational' ), true ) ) {
303 return false;
304 }
305
306 $this->module_name = sanitize_text_field( $data['module_name'] );
307 $this->module_type = $data['module_type'];
308 $this->active = 0;
309 $this->module_mode = $data['module_mode'];
310
311 return true;
312 }
313
314 /**
315 * Store the metas in the db when creating a new module.
316 *
317 * @since 4.0.0
318 *
319 * @param array $data Module's data to store.
320 */
321 private function store_new_module_meta( $data ) {
322 $def_content = apply_filters( 'hustle_module_get_' . self::KEY_CONTENT . '_defaults', $this->get_content()->to_array(), $this, $data );
323 $content = empty( $data['content'] ) ? $def_content : array_merge( $def_content, $data['content'] );
324
325 $def_emails = apply_filters( 'hustle_module_get_' . self::KEY_EMAILS . '_defaults', $this->get_emails()->to_array(), $this, $data );
326 $emails = empty( $data['emails'] ) ? $def_emails : array_merge( $def_emails, $data['emails'] );
327
328 $def_design = apply_filters( 'hustle_module_get_' . self::KEY_DESIGN . '_defaults', $this->get_design()->to_array(), $this, $data );
329 $design = empty( $data['design'] ) ? $def_design : array_merge( $def_design, $data['design'] );
330 if ( ! empty( $data['base_template'] ) ) {
331 $design['base_template'] = $data['base_template'];
332 }
333
334 $def_integrations_settings = apply_filters( 'hustle_module_get_' . self::KEY_INTEGRATIONS_SETTINGS . '_defaults', $this->get_integrations_settings()->to_array(), $this, $data );
335 $integrations_settings = empty( $data['integrations_settings'] ) ? $def_integrations_settings : array_merge( $def_integrations_settings, $data['integrations_settings'] );
336
337 $def_settings = apply_filters( 'hustle_module_get_' . self::KEY_SETTINGS . '_defaults', $this->get_settings()->to_array(), $this, $data );
338 $settings = empty( $data['settings'] ) ? $def_settings : array_merge( $def_settings, $data['settings'] );
339
340 $def_visibility = apply_filters( 'hustle_module_get_' . self::KEY_VISIBILITY . '_defaults', $this->get_visibility()->to_array(), $this, $data );
341 $visibility = empty( $data['visibility'] ) ? $def_visibility : array_merge( $def_visibility, $data['visibility'] );
342
343 $this->update_meta( self::KEY_CONTENT, $content );
344 $this->update_meta( self::KEY_EMAILS, $emails );
345 $this->update_meta( self::KEY_INTEGRATIONS_SETTINGS, $integrations_settings );
346 $this->update_meta( self::KEY_DESIGN, $design );
347 $this->update_meta( self::KEY_SETTINGS, $settings );
348 $this->update_meta( self::KEY_VISIBILITY, $visibility );
349
350 // Embedded only. Display options.
351 if ( self::EMBEDDED_MODULE === $this->module_type ) {
352 $def_display = apply_filters( 'hustle_module_get_' . self::KEY_DISPLAY_OPTIONS . '_defaults', $this->get_display()->to_array(), $this, $data );
353 $display = empty( $data['display'] ) ? $def_display : array_merge( $def_display, $data['display'] );
354
355 $this->update_meta( self::KEY_DISPLAY_OPTIONS, $display );
356 }
357
358 $this->enable_type_track_mode( $this->module_type, true );
359 }
360
361 /**
362 * Populates an instance of a module for a template preview.
363 * This is only used for displaying a preview. We're using a saved
364 * module nor creating a new one, thus we don't have an ID.
365 *
366 * @since 4.3.4
367 *
368 * @param array $data Module data to be populated.
369 */
370 public function populate_module_for_template( $data ) {
371 $this->module_id = 0;
372
373 $this->populate_module_from_data( $data );
374 }
375
376 /**
377 * Creates and store the nonce used to validate email unsubscriptions.
378 *
379 * @since 3.0.5
380 * @param string $email Email to be unsubscribed.
381 * @param array $lists_id IDs of the modules to which it will be unsubscribed.
382 * @return boolean
383 */
384 public function create_unsubscribe_nonce( $email, array $lists_id ) {
385 // Since we're supporting php 5.2, random_bytes or other strong rng are not available. So using this instead.
386 $nonce = hash_hmac( 'md5', $email, wp_rand() . time() );
387
388 $data = get_option( self::KEY_UNSUBSCRIBE_NONCES, array() );
389
390 // If the email already created a nonce and didn't use it, replace its data.
391 $data[ $email ] = array(
392 'nonce' => $nonce,
393 'lists_id' => $lists_id,
394 'date_created' => time(),
395 );
396
397 $updated = update_option( self::KEY_UNSUBSCRIBE_NONCES, $data );
398 if ( $updated ) {
399 return $nonce;
400 } else {
401 return false;
402 }
403 }
404
405 /**
406 * Does the actual email unsubscription.
407 *
408 * @since 3.0.5
409 * @param string $email Email to be unsubscribed.
410 * @param string $nonce Nonce associated with the email for the unsubscription.
411 * @return boolean
412 */
413 public function unsubscribe_email( $email, $nonce ) {
414 $data = get_option( self::KEY_UNSUBSCRIBE_NONCES, false );
415 if ( ! $data ) {
416 return false;
417 }
418 if ( ! isset( $data[ $email ] ) || ! isset( $data[ $email ]['nonce'] ) || ! isset( $data[ $email ]['lists_id'] ) ) {
419 return false;
420 }
421 $email_data = $data[ $email ];
422 if ( ! hash_equals( (string) $email_data['nonce'], $nonce ) ) {
423 return false;
424 }
425 // Nonce expired. Remove it. Currently giving 1 day of life span.
426 if ( ( time() - (int) $email_data['date_created'] ) > DAY_IN_SECONDS ) {
427 unset( $data[ $email ] );
428 update_option( self::KEY_UNSUBSCRIBE_NONCES, $data );
429 return false;
430 }
431
432 // Proceed to unsubscribe.
433 foreach ( $email_data['lists_id'] as $id ) {
434 $unsubscribed = $this->remove_local_subscription_by_email_and_module_id( $email, $id );
435 }
436
437 // The email was unsubscribed and the nonce was used. Remove it from the saved list.
438 unset( $data[ $email ] );
439 update_option( self::KEY_UNSUBSCRIBE_NONCES, $data );
440
441 return true;
442
443 }
444
445 /**
446 * Updates the metas specific for Non Social Sharing modules.
447 *
448 * @since 4.3.0
449 * @param array $data Data to save.
450 * @return void
451 */
452 protected function update_module_metas( $data ) {
453 // Meta used in all module types.
454 if ( isset( $data['content'] ) ) {
455 if ( ! empty( $data['content']['feature_image'] ) ) {
456 $data['content']['feature_image_alt'] = $this->update_feature_image_alt( $data['content']['feature_image'], false );
457 }
458 $this->update_meta( self::KEY_CONTENT, $data['content'] );
459 }
460 // Meta used in all module types.
461 if ( isset( $data['visibility'] ) ) {
462 $this->update_meta( self::KEY_VISIBILITY, $data['visibility'] );
463 }
464
465 // Design tab.
466 if ( isset( $data['design'] ) ) {
467 $saved_design = $this->get_design()->to_array();
468 $new_design = array_merge( $saved_design, $data['design'] );
469
470 $this->update_meta( self::KEY_DESIGN, $new_design );
471 }
472
473 // Emails tab.
474 if ( isset( $data['emails'] ) ) {
475 $this->update_meta( self::KEY_EMAILS, $data['emails'] );
476 }
477
478 // Settings tab.
479 if ( isset( $data['settings'] ) ) {
480 // Clear flags to skip cached schedule values.
481 $this->set_schedule_flags( array() );
482 $this->update_meta( self::KEY_SETTINGS, $data['settings'] );
483 }
484
485 // Integrations tab.
486 if ( isset( $data['integrations_settings'] ) ) {
487 $this->update_meta( self::KEY_INTEGRATIONS_SETTINGS, $data['integrations_settings'] );
488 }
489
490 // Embedded only meta.
491 if ( self::EMBEDDED_MODULE === $this->module_type && isset( $data['display'] ) ) {
492 $this->update_meta( self::KEY_DISPLAY_OPTIONS, $data['display'] );
493 }
494
495 // Activate integrations if provided.
496 if ( isset( $data['integrations'] ) ) {
497 $this->activate_providers( $data );
498 }
499
500 $this->maybe_update_custom_fields();
501 }
502
503 /**
504 * Sanitize/Replace the module's data.
505 *
506 * @param array $data Data to sanitize.
507 * @return array Sanitized data.
508 */
509 public function sanitize_module( $data ) {
510 $design_obj = $this->get_design();
511 $default_options = $design_obj->get_defaults();
512 $saved_options = $design_obj->to_array();
513 $new_options = ! empty( $data['design'] ) ? $data['design'] : array();
514 $typography_options = $design_obj->get_typography_defaults( 'desktop' );
515
516 // Check is `Border, Spacing and Shadow` enabled for desktop.
517 $spacing_on = $this->get_newest_value( 'customize_border_shadow_spacing', $new_options, $saved_options );
518 // Check is `Typography` enabled for desktop.
519 $typography_on = $this->get_newest_value( 'customize_typography', $new_options, $saved_options );
520
521 foreach ( $new_options as $option_name => $value ) {
522 if ( $spacing_on ) {
523 $data = $this->replace_empty_spacing_numbers( $data, $option_name, $value, $default_options );
524 }
525 if ( $typography_on ) {
526 $data = $this->replace_empty_typography_numbers( $data, $option_name, $value, $default_options, $typography_options );
527 }
528 }
529
530 if ( ! empty( $data['module']['module_name'] ) ) {
531 $data['module']['module_name'] = sanitize_text_field( $data['module']['module_name'] );
532 }
533
534 if ( ! empty( $data['settings'] ) && ! is_array( $data['settings'] ) ) {
535 $setting_json = true;
536 $data['settings'] = json_decode( $data['settings'], true );
537 }
538
539 array_walk_recursive(
540 $data,
541 function ( &$value, $key ) {
542 $consist_html = apply_filters(
543 'hustle_fields_with_html',
544 array(
545 'main_content',
546 'title',
547 'sub_title',
548 'email_body',
549 'success_message',
550 'emailmessage',
551 'email_message',
552 'gdpr_message',
553 'required_error_message',
554 'v3_recaptcha_badge_replacement',
555 )
556 );
557 if ( in_array( $key, array( 'refs', 'urls' ), true ) ) {
558 // Handle Visibility -> URL textarea.
559 $urls = preg_split( '/\r\n|\r|\n/', $value );
560 $urls = array_map(
561 function( $v ) {
562 return filter_var( wp_strip_all_tags( $v ), FILTER_SANITIZE_URL );
563 },
564 (array) $urls
565 );
566 $value = implode( "\n", $urls );
567 } elseif ( in_array( $key, $consist_html, true ) ) {
568 $value = wp_unslash( apply_filters( 'content_save_pre', wp_slash( $value ) ) );
569 if ( ! in_array( $key, array( 'main_content', 'emailmessage', 'email_message', 'success_message' ), true ) ) {
570 $value = wp_kses_post( $value );
571 }
572 } elseif ( ! is_int( $value ) ) {
573 $value = sanitize_text_field( $value );
574 }
575 }
576 );
577
578 if ( ! empty( $setting_json ) ) {
579 $data['settings'] = wp_json_encode( $data['settings'] );
580 }
581
582 if ( isset( $data['emails']['form_elements'] ) ) {
583 $data['emails']['form_elements'] = $this->sanitize_form_elements( $data['emails']['form_elements'] );
584 }
585
586 return $data;
587 }
588
589 /**
590 * Validates the module's data.
591 *
592 * @since 4.0.3
593 *
594 * @param array $data Data to validate.
595 * @return array
596 */
597 public function validate_module( $data ) {
598 $errors = array();
599
600 // Name validation.
601 if ( empty( $data['module']['module_name'] ) ) {
602 $errors['error']['name_error'] = __( 'This field is required', 'hustle' );
603
604 return $errors;
605 }
606
607 return true;
608 }
609
610 /**
611 * Get newest value.
612 *
613 * @param string $option_name Option name.
614 * @param array $new_options New options.
615 * @param array $saved_options Old options.
616 * @return bool
617 */
618 private function get_newest_value( $option_name, $new_options, $saved_options ) {
619 if ( isset( $new_options[ $option_name ] ) ) {
620 $value = $new_options[ $option_name ];
621 } elseif ( isset( $saved_options[ $option_name ] ) ) {
622 $value = $saved_options[ $option_name ];
623 } else {
624 $value = '';
625 }
626
627 return $value;
628 }
629
630 /**
631 * Check if it's spacing option and value isn't set
632 *
633 * @param string $key Option name.
634 * @param string $value Option value.
635 * @return bool
636 */
637 private function is_empty_spacing_number( $key, $value ) {
638 $needles = array( '_margin_', '_padding_', '_shadow_', '_radius_', '_border_' );
639 return $this->similar_in_array( $key, $needles ) && '' === $value;
640 }
641
642 /**
643 * Check if it's typography option and value isn't set
644 *
645 * @param string $key Option name.
646 * @param string $value Option value.
647 * @param array $typography_options Typography options.
648 * @return bool
649 */
650 private function is_empty_typography_number( $key, $value, $typography_options ) {
651 return '' === $value && isset( $typography_options[ $key ] )
652 && ( is_float( $typography_options[ $key ] ) || is_int( $typography_options[ $key ] ) );
653 }
654
655 /**
656 * If it's an empty number option from Destop section - replace it to relevant default value.
657 *
658 * @param string $data Data for sanitize.
659 * @param string $option_name Option name.
660 * @param string $option_value Option value.
661 * @param array $default_options Default options.
662 * @return string
663 */
664 private function replace_empty_spacing_numbers( $data, $option_name, $option_value, $default_options ) {
665 if ( $this->is_empty_spacing_number( $option_name, $option_value ) && '_mobile' !== substr( $option_name, -7 ) ) {
666 $data['design'][ $option_name ] = isset( $default_options[ $option_name ] ) ? $default_options[ $option_name ] : 0;
667 }
668
669 return $data;
670 }
671
672 /**
673 * If it's an empty number option from Typography section - replace it to relevant default value.
674 *
675 * @param string $data Data for sanitize.
676 * @param string $option_name Option name.
677 * @param string $option_value Option value.
678 * @param array $default_options Default options.
679 * @param array $typography_options Typography options.
680 * @return string
681 */
682 private function replace_empty_typography_numbers( $data, $option_name, $option_value, $default_options, $typography_options ) {
683 if ( $this->is_empty_typography_number( $option_name, $option_value, $typography_options ) && '_mobile' !== substr( $option_name, -7 ) ) {
684 $data['design'][ $option_name ] = isset( $default_options[ $option_name ] ) ? $default_options[ $option_name ] : 0;
685 }
686
687 return $data;
688 }
689
690 /**
691 * Checks if a value contains a part of any array item
692 *
693 * @param array $haystack The value.
694 * @param string $needles The array of pices.
695 * @return boolean
696 */
697 private function similar_in_array( $haystack, $needles ) {
698 foreach ( $needles as $needle ) {
699 if ( false !== strpos( $haystack, $needle ) ) {
700 return true;
701 }
702 }
703
704 return false;
705 }
706
707 /**
708 * Get renderer
709 *
710 * @return \Hustle_Module_Renderer
711 */
712 public function get_renderer() {
713 return new Hustle_Module_Renderer();
714 }
715
716 /**
717 * Sanitize the form fields name replacing spaces by underscores.
718 * This way the data is handled properly along hustle.
719 *
720 * @since 4.0
721 * @param string $name Name.
722 * @return string
723 */
724 private function sanitize_form_field_name( $name ) {
725 $sanitized_name = apply_filters( 'hustle_sanitize_form_field_name', str_replace( ' ', '_', trim( $name ) ), $name );
726
727 return sanitize_text_field( $sanitized_name );
728 }
729
730 /**
731 * Sanitize form elements
732 *
733 * @param array $form_elements Form elements.
734 * @return type
735 */
736 public function sanitize_form_elements( $form_elements ) {
737 // Sanitize GDPR message.
738 if ( isset( $form_elements['gdpr']['gdpr_message'] ) ) {
739 $allowed_html = array(
740 'a' => array(
741 'href' => true,
742 'title' => true,
743 'target' => true,
744 'alt' => true,
745 ),
746 'b' => array(),
747 'strong' => array(),
748 'i' => array(),
749 'em' => array(),
750 'del' => array(),
751 );
752 $form_elements['gdpr']['gdpr_message'] = wp_kses( wp_unslash( $form_elements['gdpr']['gdpr_message'] ), $allowed_html );
753 }
754
755 $sanitized_fields = array();
756
757 // Loop through each form field.
758 foreach ( $form_elements as $field_data ) {
759 $name = $this->sanitize_form_field_name( $field_data['name'] );
760
761 // After sanitize if name become empty then create array key from label.
762 if ( ! $name ) {
763 $name = $this->sanitize_form_field_name( $field_data['label'] );
764
765 // If still key is empty then go to next iteration.
766 if ( ! $name ) {
767 continue;
768 }
769 }
770
771 // Check field name already exists or not. If exists then create a new name.
772 $name = $this->get_unique_field_name( $sanitized_fields, $name );
773
774 // Sanitize necessary fields.
775 $field_data['name'] = $name;
776 $field_data['label'] = sanitize_text_field( $field_data['label'] );
777 $field_data['placeholder'] = sanitize_text_field( $field_data['placeholder'] );
778
779 // Add new item with field data.
780 $sanitized_fields[ $name ] = $field_data;
781 }
782
783 return $sanitized_fields;
784 }
785
786 /**
787 * Update Custom Fields for Sendgrid New Campaigns
788 */
789 private function maybe_update_custom_fields() {
790 $connected_addons = Hustle_Provider_Utils::get_addons_instance_connected_with_module( $this->module_id );
791
792 foreach ( $connected_addons as $addon ) {
793
794 // Change logic only for sendgrid for now.
795 if ( 'sendgrid' !== $addon->get_slug() ) {
796 continue;
797 }
798 $global_multi_id = $addon->selected_global_multi_id;
799 $new_campaigns = $addon->get_setting( 'new_campaigns', '', $global_multi_id );
800
801 // only if it's the New Sendgrid Campaigns.
802 if ( 'new_campaigns' !== $new_campaigns ) {
803 continue;
804 }
805 $emails = $this->get_emails()->to_array();
806 $custom_fields = array();
807
808 $api_key = $addon->get_setting( 'api_key', '', $global_multi_id );
809 $api = $addon::api( $api_key, $new_campaigns );
810
811 foreach ( $emails['form_elements'] as $element ) {
812 if ( empty( $element['type'] ) || in_array( $element['type'], array( 'submit', 'recaptcha' ), true ) ) {
813 continue;
814 }
815 $custom_fields[] = array(
816 'type' => 'text',
817 'name' => $element['name'],
818 );
819 }
820
821 if ( ! empty( $custom_fields ) ) {
822 $api->add_custom_fields( $custom_fields );
823 }
824 }
825 }
826
827 /**
828 * Gets the selected Google fonts for the active elements in the module.
829 * Used for non-ssharing modules only.
830 *
831 * @since 4.3.0
832 *
833 * @return array
834 */
835 public function get_google_fonts() {
836 $fonts = array();
837
838 if ( '1' === $this->design->use_vanilla ) {
839 return $fonts;
840 }
841
842 $elements = array(
843 'title' => '' !== $this->content->title,
844 'subtitle' => '' !== $this->content->sub_title,
845 'main_content_paragraph' => '' !== $this->content->main_content,
846 'main_content_heading_one' => '' !== $this->content->main_content,
847 'main_content_heading_two' => '' !== $this->content->main_content,
848 'main_content_heading_three' => '' !== $this->content->main_content,
849 'main_content_heading_four' => '' !== $this->content->main_content,
850 'main_content_heading_five' => '' !== $this->content->main_content,
851 'main_content_heading_six' => '' !== $this->content->main_content,
852 'cta' => '0' !== $this->content->show_cta,
853 'never_see_link' => '0' !== $this->content->show_never_see_link,
854 );
855
856 // Only list the font of the elements that are shown, and aren't using a 'custom' font.
857 foreach ( $elements as $element_name => $is_shown ) {
858 if ( ! $is_shown ) {
859 continue;
860 }
861
862 $font = $this->design->{ $element_name . '_font_family' };
863 if ( 'custom' !== $font ) {
864 $font_weight = $this->design->{ $element_name . '_font_weight' };
865 if ( ! isset( $fonts[ $font ] ) ) {
866 $fonts[ $font ] = array();
867 }
868 if ( ! in_array( $font_weight, $fonts[ $font ], true ) ) {
869 $fonts[ $font ][] = $font_weight;
870 }
871 }
872 }
873
874 // We're done here for informational modules.
875 if ( self::OPTIN_MODE !== $this->module_mode ) {
876 return $fonts;
877 }
878
879 $has_mailchimp = ! empty( $this->get_provider_settings( 'mailchimp' ) );
880
881 $form_fields = $this->get_form_fields();
882 $has_success_message = 'show_success' === $this->emails->after_successful_submission;
883
884 $elements_optin = array(
885 'form_extras' => $has_mailchimp,
886 'input' => true,
887 'select' => $has_mailchimp,
888 'checkbox' => $has_mailchimp,
889 'dropdown' => $has_mailchimp,
890 'gdpr' => ! empty( $form_fields['gdpr'] ),
891 'recaptcha' => ! empty( $form_fields['recaptcha'] ),
892 'submit_button' => true,
893 'success_message_paragraph' => $has_success_message,
894 'success_message_heading_one' => $has_success_message,
895 'success_message_heading_two' => $has_success_message,
896 'success_message_heading_three' => $has_success_message,
897 'success_message_heading_four' => $has_success_message,
898 'success_message_heading_five' => $has_success_message,
899 'success_message_heading_six' => $has_success_message,
900 'error_message' => true,
901 );
902
903 foreach ( $elements_optin as $element_name => $is_shown ) {
904 if ( ! $is_shown ) {
905 continue;
906 }
907
908 $font = $this->design->{ $element_name . '_font_family' };
909 if ( 'custom' !== $font ) {
910 $font_weight = $this->design->{ $element_name . '_font_weight' };
911 if ( ! isset( $fonts[ $font ] ) ) {
912 $fonts[ $font ] = array();
913 }
914 if ( ! in_array( $font_weight, $fonts[ $font ], true ) ) {
915 $fonts[ $font ][] = $font_weight;
916 }
917 }
918 }
919 return $fonts;
920 }
921
922 /**
923 * Find the unique name of field without overriding others.
924 *
925 * @since 4.3.3
926 *
927 * @param array $sanitized_fields Array for fields that are previously sanitized.
928 * @param string $field_name field name which will be compare.
929 *
930 * @return array
931 */
932 private function get_unique_field_name( $sanitized_fields, $field_name ) {
933 $new_name = $field_name;
934 $i = 0;
935
936 while ( array_key_exists( $new_name, $sanitized_fields ) ) {
937 $i++;
938 $new_name = $field_name . '-' . $i;
939 }
940
941 return $new_name;
942 }
943
944 }
945