PluginProbe ʕ •ᴥ•ʔ
Hustle – Email Marketing, Lead Generation, Optins, Popups / 7.8.3
Hustle – Email Marketing, Lead Generation, Optins, Popups v7.8.3
7.8.14.2 7.8.14 7.8.14.1 7.8.13 7.8.13.1 trunk 3.0 3.1 3.1.1 3.1.2 3.1.3 3.1.4 4.3.2 4.4.4 4.4.5 4.4.5.1 4.4.5.4 4.6 4.6.1.1 4.6.1.4 4.7.0.2 4.7.0.3 4.7.0.7 4.7.0.9 4.7.1.0 4.7.1.1 4.8.0.0 5.0.0 5.0.1 5.0.1.1 5.0.1.2 5.1 5.1.1 5.1.2 5.1.3 5.1.3.1 5.1.3.2 5.1.4 5.1.5 6.0 6.0.1 6.0.2 6.0.3 6.0.4.2 6.0.5 6.0.6.1 6.0.7 6.0.8.1 6.0.9 7.0.0.1 7.0.2 7.0.3 7.0.4 7.1.0 7.1.1 7.2.0 7.2.1 7.3.0 7.3.1 7.3.3 7.3.5 7.3.6 7.3.7 7.4.0 7.4.1 7.4.11 7.4.13 7.4.13.1 7.4.2 7.4.3 7.4.4 7.4.5 7.4.5.1 7.4.5.2 7.4.6 7.4.7 7.5.0 7.6.0 7.6.1 7.6.3 7.6.4 7.6.6 7.7.0 7.7.1 7.8.0 7.8.1 7.8.10 7.8.10.1 7.8.10.2 7.8.11 7.8.12 7.8.12.1 7.8.2 7.8.3 7.8.4 7.8.5 7.8.6 7.8.7 7.8.8 7.8.9 7.8.9.1 7.8.9.2 7.8.9.3
wordpress-popup / inc / hustle-module-model.php
wordpress-popup / inc Last commit date
display-conditions 2 years ago front 2 years ago helpers 2 years ago metas 2 years ago multisite 2 years ago palettes 2 years ago provider 2 years ago providers 2 years ago templates 2 years ago update 2 years ago class-hustle-admin-page-abstract.php 2 years ago class-hustle-condition-factory.php 2 years ago class-hustle-dashboard-admin.php 2 years ago class-hustle-data.php 2 years ago class-hustle-db.php 2 years ago class-hustle-installer.php 2 years ago class-hustle-meta.php 2 years ago class-hustle-module-admin.php 2 years ago class-hustle-module-collection.php 2 years ago class-hustle-module-page-abstract.php 2 years ago class-hustle-notifications.php 2 years ago class-hustle-settings-admin.php 2 years ago class-hustle-tutorials-page.php 2 years ago class-hustle-upsell-page.php 2 years ago class-hustle-wp-dashboard-page.php 2 years ago hustle-deletion.php 2 years ago hustle-embedded-admin.php 2 years ago hustle-entries-admin.php 2 years ago hustle-entry-model.php 2 years ago hustle-general-data-protection.php 2 years ago hustle-init.php 2 years ago hustle-mail.php 2 years ago hustle-migration.php 2 years ago hustle-model.php 2 years ago hustle-module-model.php 2 years ago hustle-module-widget-legacy.php 2 years ago hustle-module-widget.php 2 years ago hustle-modules-common-admin-ajax.php 2 years ago hustle-popup-admin.php 2 years ago hustle-providers-admin.php 2 years ago hustle-providers.php 2 years ago hustle-settings-admin-ajax.php 2 years ago hustle-settings-page.php 2 years ago hustle-slidein-admin.php 2 years ago hustle-sshare-admin.php 2 years ago hustle-sshare-model.php 2 years ago hustle-tracking-model.php 2 years ago opt-in-geo.php 2 years ago opt-in-utils.php 2 years ago opt-in-wpmudev-api.php 2 years ago
hustle-module-model.php
944 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 )
555 );
556 if ( in_array( $key, array( 'refs', 'urls' ), true ) ) {
557 // Handle Visibility -> URL textarea.
558 $urls = preg_split( '/\r\n|\r|\n/', $value );
559 $urls = array_map(
560 function( $v ) {
561 return filter_var( wp_strip_all_tags( $v ), FILTER_SANITIZE_URL );
562 },
563 (array) $urls
564 );
565 $value = implode( "\n", $urls );
566 } elseif ( in_array( $key, $consist_html, true ) ) {
567 $value = wp_unslash( apply_filters( 'content_save_pre', wp_slash( $value ) ) );
568 if ( ! in_array( $key, array( 'main_content', 'emailmessage', 'email_message', 'success_message' ), true ) ) {
569 $value = wp_kses_post( $value );
570 }
571 } elseif ( ! is_int( $value ) ) {
572 $value = sanitize_text_field( $value );
573 }
574 }
575 );
576
577 if ( ! empty( $setting_json ) ) {
578 $data['settings'] = wp_json_encode( $data['settings'] );
579 }
580
581 if ( isset( $data['emails']['form_elements'] ) ) {
582 $data['emails']['form_elements'] = $this->sanitize_form_elements( $data['emails']['form_elements'] );
583 }
584
585 return $data;
586 }
587
588 /**
589 * Validates the module's data.
590 *
591 * @since 4.0.3
592 *
593 * @param array $data Data to validate.
594 * @return array
595 */
596 public function validate_module( $data ) {
597 $errors = array();
598
599 // Name validation.
600 if ( empty( $data['module']['module_name'] ) ) {
601 $errors['error']['name_error'] = __( 'This field is required', 'hustle' );
602
603 return $errors;
604 }
605
606 return true;
607 }
608
609 /**
610 * Get newest value.
611 *
612 * @param string $option_name Option name.
613 * @param array $new_options New options.
614 * @param array $saved_options Old options.
615 * @return bool
616 */
617 private function get_newest_value( $option_name, $new_options, $saved_options ) {
618 if ( isset( $new_options[ $option_name ] ) ) {
619 $value = $new_options[ $option_name ];
620 } elseif ( isset( $saved_options[ $option_name ] ) ) {
621 $value = $saved_options[ $option_name ];
622 } else {
623 $value = '';
624 }
625
626 return $value;
627 }
628
629 /**
630 * Check if it's spacing option and value isn't set
631 *
632 * @param string $key Option name.
633 * @param string $value Option value.
634 * @return bool
635 */
636 private function is_empty_spacing_number( $key, $value ) {
637 $needles = array( '_margin_', '_padding_', '_shadow_', '_radius_', '_border_' );
638 return $this->similar_in_array( $key, $needles ) && '' === $value;
639 }
640
641 /**
642 * Check if it's typography option and value isn't set
643 *
644 * @param string $key Option name.
645 * @param string $value Option value.
646 * @param array $typography_options Typography options.
647 * @return bool
648 */
649 private function is_empty_typography_number( $key, $value, $typography_options ) {
650 return '' === $value && isset( $typography_options[ $key ] )
651 && ( is_float( $typography_options[ $key ] ) || is_int( $typography_options[ $key ] ) );
652 }
653
654 /**
655 * If it's an empty number option from Destop section - replace it to relevant default value.
656 *
657 * @param string $data Data for sanitize.
658 * @param string $option_name Option name.
659 * @param string $option_value Option value.
660 * @param array $default_options Default options.
661 * @return string
662 */
663 private function replace_empty_spacing_numbers( $data, $option_name, $option_value, $default_options ) {
664 if ( $this->is_empty_spacing_number( $option_name, $option_value ) && '_mobile' !== substr( $option_name, -7 ) ) {
665 $data['design'][ $option_name ] = isset( $default_options[ $option_name ] ) ? $default_options[ $option_name ] : 0;
666 }
667
668 return $data;
669 }
670
671 /**
672 * If it's an empty number option from Typography section - replace it to relevant default value.
673 *
674 * @param string $data Data for sanitize.
675 * @param string $option_name Option name.
676 * @param string $option_value Option value.
677 * @param array $default_options Default options.
678 * @param array $typography_options Typography options.
679 * @return string
680 */
681 private function replace_empty_typography_numbers( $data, $option_name, $option_value, $default_options, $typography_options ) {
682 if ( $this->is_empty_typography_number( $option_name, $option_value, $typography_options ) && '_mobile' !== substr( $option_name, -7 ) ) {
683 $data['design'][ $option_name ] = isset( $default_options[ $option_name ] ) ? $default_options[ $option_name ] : 0;
684 }
685
686 return $data;
687 }
688
689 /**
690 * Checks if a value contains a part of any array item
691 *
692 * @param array $haystack The value.
693 * @param string $needles The array of pices.
694 * @return boolean
695 */
696 private function similar_in_array( $haystack, $needles ) {
697 foreach ( $needles as $needle ) {
698 if ( false !== strpos( $haystack, $needle ) ) {
699 return true;
700 }
701 }
702
703 return false;
704 }
705
706 /**
707 * Get renderer
708 *
709 * @return \Hustle_Module_Renderer
710 */
711 public function get_renderer() {
712 return new Hustle_Module_Renderer();
713 }
714
715 /**
716 * Sanitize the form fields name replacing spaces by underscores.
717 * This way the data is handled properly along hustle.
718 *
719 * @since 4.0
720 * @param string $name Name.
721 * @return string
722 */
723 private function sanitize_form_field_name( $name ) {
724 $sanitized_name = apply_filters( 'hustle_sanitize_form_field_name', str_replace( ' ', '_', trim( $name ) ), $name );
725
726 return sanitize_text_field( $sanitized_name );
727 }
728
729 /**
730 * Sanitize form elements
731 *
732 * @param array $form_elements Form elements.
733 * @return type
734 */
735 public function sanitize_form_elements( $form_elements ) {
736 // Sanitize GDPR message.
737 if ( isset( $form_elements['gdpr']['gdpr_message'] ) ) {
738 $allowed_html = array(
739 'a' => array(
740 'href' => true,
741 'title' => true,
742 'target' => true,
743 'alt' => true,
744 ),
745 'b' => array(),
746 'strong' => array(),
747 'i' => array(),
748 'em' => array(),
749 'del' => array(),
750 );
751 $form_elements['gdpr']['gdpr_message'] = wp_kses( wp_unslash( $form_elements['gdpr']['gdpr_message'] ), $allowed_html );
752 }
753
754 $sanitized_fields = array();
755
756 // Loop through each form field.
757 foreach ( $form_elements as $field_data ) {
758 $name = $this->sanitize_form_field_name( $field_data['name'] );
759
760 // After sanitize if name become empty then create array key from label.
761 if ( ! $name ) {
762 $name = $this->sanitize_form_field_name( $field_data['label'] );
763
764 // If still key is empty then go to next iteration.
765 if ( ! $name ) {
766 continue;
767 }
768 }
769
770 // Check field name already exists or not. If exists then create a new name.
771 $name = $this->get_unique_field_name( $sanitized_fields, $name );
772
773 // Sanitize necessary fields.
774 $field_data['name'] = $name;
775 $field_data['label'] = sanitize_text_field( $field_data['label'] );
776 $field_data['placeholder'] = sanitize_text_field( $field_data['placeholder'] );
777
778 // Add new item with field data.
779 $sanitized_fields[ $name ] = $field_data;
780 }
781
782 return $sanitized_fields;
783 }
784
785 /**
786 * Update Custom Fields for Sendgrid New Campaigns
787 */
788 private function maybe_update_custom_fields() {
789 $connected_addons = Hustle_Provider_Utils::get_addons_instance_connected_with_module( $this->module_id );
790
791 foreach ( $connected_addons as $addon ) {
792
793 // Change logic only for sendgrid for now.
794 if ( 'sendgrid' !== $addon->get_slug() ) {
795 continue;
796 }
797 $global_multi_id = $addon->selected_global_multi_id;
798 $new_campaigns = $addon->get_setting( 'new_campaigns', '', $global_multi_id );
799
800 // only if it's the New Sendgrid Campaigns.
801 if ( 'new_campaigns' !== $new_campaigns ) {
802 continue;
803 }
804 $emails = $this->get_emails()->to_array();
805 $custom_fields = array();
806
807 $api_key = $addon->get_setting( 'api_key', '', $global_multi_id );
808 $api = $addon::api( $api_key, $new_campaigns );
809
810 foreach ( $emails['form_elements'] as $element ) {
811 if ( empty( $element['type'] ) || in_array( $element['type'], array( 'submit', 'recaptcha' ), true ) ) {
812 continue;
813 }
814 $custom_fields[] = array(
815 'type' => 'text',
816 'name' => $element['name'],
817 );
818 }
819
820 if ( ! empty( $custom_fields ) ) {
821 $api->add_custom_fields( $custom_fields );
822 }
823 }
824 }
825
826 /**
827 * Gets the selected Google fonts for the active elements in the module.
828 * Used for non-ssharing modules only.
829 *
830 * @since 4.3.0
831 *
832 * @return array
833 */
834 public function get_google_fonts() {
835 $fonts = array();
836
837 if ( '1' === $this->design->use_vanilla ) {
838 return $fonts;
839 }
840
841 $elements = array(
842 'title' => '' !== $this->content->title,
843 'subtitle' => '' !== $this->content->sub_title,
844 'main_content_paragraph' => '' !== $this->content->main_content,
845 'main_content_heading_one' => '' !== $this->content->main_content,
846 'main_content_heading_two' => '' !== $this->content->main_content,
847 'main_content_heading_three' => '' !== $this->content->main_content,
848 'main_content_heading_four' => '' !== $this->content->main_content,
849 'main_content_heading_five' => '' !== $this->content->main_content,
850 'main_content_heading_six' => '' !== $this->content->main_content,
851 'cta' => '0' !== $this->content->show_cta,
852 'never_see_link' => '0' !== $this->content->show_never_see_link,
853 );
854
855 // Only list the font of the elements that are shown, and aren't using a 'custom' font.
856 foreach ( $elements as $element_name => $is_shown ) {
857 if ( ! $is_shown ) {
858 continue;
859 }
860
861 $font = $this->design->{ $element_name . '_font_family' };
862 if ( 'custom' !== $font ) {
863 $font_weight = $this->design->{ $element_name . '_font_weight' };
864 if ( ! isset( $fonts[ $font ] ) ) {
865 $fonts[ $font ] = array();
866 }
867 if ( ! in_array( $font_weight, $fonts[ $font ], true ) ) {
868 $fonts[ $font ][] = $font_weight;
869 }
870 }
871 }
872
873 // We're done here for informational modules.
874 if ( self::OPTIN_MODE !== $this->module_mode ) {
875 return $fonts;
876 }
877
878 $has_mailchimp = ! empty( $this->get_provider_settings( 'mailchimp' ) );
879
880 $form_fields = $this->get_form_fields();
881 $has_success_message = 'show_success' === $this->emails->after_successful_submission;
882
883 $elements_optin = array(
884 'form_extras' => $has_mailchimp,
885 'input' => true,
886 'select' => $has_mailchimp,
887 'checkbox' => $has_mailchimp,
888 'dropdown' => $has_mailchimp,
889 'gdpr' => ! empty( $form_fields['gdpr'] ),
890 'recaptcha' => ! empty( $form_fields['recaptcha'] ),
891 'submit_button' => true,
892 'success_message_paragraph' => $has_success_message,
893 'success_message_heading_one' => $has_success_message,
894 'success_message_heading_two' => $has_success_message,
895 'success_message_heading_three' => $has_success_message,
896 'success_message_heading_four' => $has_success_message,
897 'success_message_heading_five' => $has_success_message,
898 'success_message_heading_six' => $has_success_message,
899 'error_message' => true,
900 );
901
902 foreach ( $elements_optin as $element_name => $is_shown ) {
903 if ( ! $is_shown ) {
904 continue;
905 }
906
907 $font = $this->design->{ $element_name . '_font_family' };
908 if ( 'custom' !== $font ) {
909 $font_weight = $this->design->{ $element_name . '_font_weight' };
910 if ( ! isset( $fonts[ $font ] ) ) {
911 $fonts[ $font ] = array();
912 }
913 if ( ! in_array( $font_weight, $fonts[ $font ], true ) ) {
914 $fonts[ $font ][] = $font_weight;
915 }
916 }
917 }
918 return $fonts;
919 }
920
921 /**
922 * Find the unique name of field without overriding others.
923 *
924 * @since 4.3.3
925 *
926 * @param array $sanitized_fields Array for fields that are previously sanitized.
927 * @param string $field_name field name which will be compare.
928 *
929 * @return array
930 */
931 private function get_unique_field_name( $sanitized_fields, $field_name ) {
932 $new_name = $field_name;
933 $i = 0;
934
935 while ( array_key_exists( $new_name, $sanitized_fields ) ) {
936 $i++;
937 $new_name = $field_name . '-' . $i;
938 }
939
940 return $new_name;
941 }
942
943 }
944