PluginProbe
Snippet Shortcodes / 5.2.1
Snippet Shortcodes v5.2.1
5.2.1 5.2 5.1.8 5.1.6 5.1.7 5.1.5 trunk 1.0 1.1 1.2 1.3 1.3.1 1.4 1.5 1.5.1 1.6 1.6.1 1.7 1.7.1 1.7.2 1.7.3 1.7.4 1.8 2.0 2.0.1 All 74 releases
shortcode-variables / includes / functions.php

functions.php in Snippet Shortcodes 5.2.1, at includes/functions.php

1,006 lines 25.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 defined('ABSPATH') or die('Jog on!');
4
5 /**
6 * Is the Premium plugin enabled and do we have a valid license?
7 *
8 * @return bool
9 */
10 function sh_cd_is_premium() {
11
12 if ( false === sh_cd_is_premium_plugin_activated() ) {
13 return false;
14 }
15
16 return apply_filters( 'sh-cd-license-is-premium', false );
17 }
18
19 /**
20 * Is Premium plugin enabled?
21 */
22 function sh_cd_is_premium_plugin_activated() {
23 return defined( 'YK_SS_PLUGIN_NAME' );
24 }
25
26 /**
27 * Generate a site hash to identify this site.
28 **/
29 function sh_cd_generate_site_hash() {
30
31 $site_hash = get_option( 'sh-cd-hash' );
32
33 // Generate a basic site key from URL and plugin slug
34 if( false == $site_hash ) {
35
36 $site_hash = md5( 'yeken-sh-cd-' . site_url() );
37 $site_hash = substr( $site_hash, 0, 6 );
38
39 update_option( 'sh-cd-hash', $site_hash );
40
41 }
42 return $site_hash;
43 }
44
45 /**
46 * Save / Insert a shortcode
47 *
48 * @return bool
49 */
50 function sh_cd_shortcodes_save_post() {
51
52 $fields = apply_filters( 'sh-cd-post-field-keys', [ 'id', 'slug', 'previous_slug', 'data', 'disabled', 'multisite', 'editor' ] );
53
54 // Capture the raw $_POST fields, the save functions will process and validate the data
55 $shortcode = sh_cd_get_values_from_post( $fields );
56
57 // If we are not premium, then the user is not allowed to change the site slug (otherwise they could just re-use variables and by pass the limit)
58 if ( ! sh_cd_is_premium() && false === empty( $shortcode[ 'previous_slug' ] ) ) {
59 $shortcode[ 'slug' ] = $shortcode[ 'previous_slug' ];
60 }
61
62 return sh_cd_db_shortcodes_save( $shortcode );
63 }
64
65 /**
66 * Replace user parameters within a shortcode e.g. look for %%parameter%% and replace
67 *
68 * Values landing inside a URL-bearing attribute (href, src, action, etc.) are escaped
69 * with esc_url() rather than esc_attr(), so a value such as "javascript:alert(1)" can't
70 * be substituted straight into a link - esc_attr() alone doesn't strip dangerous schemes.
71 *
72 * @param $shortcode
73 * @param $user_defined_parameters
74 *
75 * @return mixed
76 */
77 function sh_cd_apply_user_defined_parameters( $shortcode, $user_defined_parameters ){
78
79 // Ensure we have something to do!
80 if ( true === empty( $user_defined_parameters ) || false === is_array( $user_defined_parameters ) ) {
81 return $shortcode;
82 }
83
84 // HTML attributes whose value is a URL - substitutions landing inside one of these get esc_url() instead of esc_attr().
85 $url_attributes = apply_filters( 'sh-cd-url-attributes', [ 'href', 'src', 'action', 'formaction', 'cite', 'background', 'poster', 'longdesc', 'usemap' ] );
86
87 foreach ( $user_defined_parameters as $key => $value ) {
88
89 $placeholder = '%%' . $key . '%%';
90
91 if ( false === strpos( $shortcode, $placeholder ) ) {
92 continue;
93 }
94
95 // First, swap in any occurrence sitting inside a URL attribute value, escaped with esc_url().
96 $shortcode = preg_replace_callback(
97 '/(?<![\w-])(' . implode( '|', $url_attributes ) . ')(\s*=\s*)("|\')((?:(?!\3).)*)\3/i',
98 function( $matches ) use ( $placeholder, $value ) {
99 $attribute_value = str_replace( $placeholder, esc_url( $value ), $matches[4] );
100 return $matches[1] . $matches[2] . $matches[3] . $attribute_value . $matches[3];
101 },
102 $shortcode
103 );
104
105 // Anything left over (i.e. not inside a URL attribute) is a normal attribute/text substitution.
106 $shortcode = str_replace( $placeholder, esc_attr( $value ), $shortcode );
107 }
108
109 return $shortcode;
110 }
111
112 /**
113 * Generate a unique slug
114 *
115 * @param $slug
116 *
117 * @return string
118 */
119 function sh_cd_slug_generate( $slug, $exising_id = NULL ) {
120
121 if ( true === empty( $slug ) ) {
122 return NULL;
123 }
124
125 $slug = sanitize_key( $slug );
126
127 $original_slug = $slug;
128
129 $try = 1;
130
131 // Ensure the slug is unique
132 while ( false === sh_cd_slug_is_unique( $slug, $exising_id ) ) {
133
134 $slug = sprintf( '%s_%d', $original_slug, $try );
135
136 $try++;
137 }
138
139 return $slug;
140 }
141
142 /**
143 * Clone an existing shortcode!
144 *
145 * @param $id
146 *
147 * @return bool
148 */
149 function sh_cd_clone( $id ) {
150
151 if( false === sh_cd_is_premium() ) {
152 return true;
153 }
154
155 if ( false === is_numeric( $id ) ) {
156 return false;
157 }
158
159 $to_be_cloned = sh_cd_db_shortcodes_by_id( $id );
160
161 if ( true === empty( $to_be_cloned ) ) {
162 return false;
163 }
164
165 unset( $to_be_cloned['id'] );
166
167 return sh_cd_db_shortcodes_save( $to_be_cloned );
168 }
169
170 /**
171 * Display message in admin UI
172 *
173 * @param $text
174 * @param bool $error
175 */
176 function sh_cd_message_display( $text, $error = false ) {
177
178 if ( true === empty( $text ) ) {
179 return;
180 }
181
182 printf( '<div class="%s"><p>%s</p></div>',
183 true === $error ? 'error' : 'updated',
184 esc_html( $text )
185 );
186
187 //TODO: Hook this to use admin_notices
188 }
189
190 /**
191 * Fetch cache item
192 *
193 * @param $key
194 *
195 * @return mixed
196 */
197 function sh_cd_cache_get( $key ) {
198
199 $key = sh_cd_cache_generate_key( $key );
200
201 return get_transient( $key );
202 }
203
204 /**
205 * Set cache item
206 *
207 * @param $key
208 * @param $data
209 */
210 function sh_cd_cache_set( $key, $data, $expire = NULL ) {
211
212
213 $expire = ( false === empty( $expire ) ) ? (int) $expire : 1 * HOUR_IN_SECONDS;
214
215 $key = sh_cd_cache_generate_key( $key );
216
217 set_transient( $key, $data, $expire );
218
219 do_action( 'sh-cd-global-cache-delete' );
220 }
221
222 /**
223 * Delete cache for given shortcode slug / ID
224 *
225 * @param $slug_or_key
226 */
227 function sh_cd_cache_delete_by_slug_or_key( $slug_or_key ) {
228
229 if ( true === is_numeric( $slug_or_key ) ) {
230
231 $slug_or_key = sh_cd_db_shortcodes_get_slug_by_id( $slug_or_key );
232
233 sh_cd_cache_delete( $slug_or_key );
234
235 } else {
236 sh_cd_cache_delete( $slug_or_key );
237 }
238
239 // Delete site option
240 $slug_or_key = SH_CD_PREFIX . $slug_or_key;
241
242 delete_site_option( $slug_or_key );
243
244 }
245
246 /**
247 * Delete cache item
248 *
249 * @param $key
250 *
251 * @return mixed
252 */
253 function sh_cd_cache_delete( $key, $trigger_global_hook = true ) {
254
255 $key = sh_cd_cache_generate_key( $key );
256
257 if ( true === $trigger_global_hook ) {
258 do_action( 'sh-cd-global-cache-delete' );
259 }
260
261 return delete_transient( $key );
262 }
263
264
265 /**
266 * Generate cache key
267 *
268 * @param $key
269 *
270 * @return string
271 */
272 function sh_cd_cache_generate_key( $key ) {
273 return SH_CD_SHORTCODE . SH_CD_PLUGIN_VERSION . $key;
274 }
275
276 /**
277 * Return link to list own shortcodes
278 *
279 * @return mixed
280 */
281 function sh_cd_link_your_shortcodes() {
282
283 $link = admin_url('admin.php?page=sh-cd-shortcode-variables-your-shortcodes');
284
285 return esc_url( $link );
286 }
287
288 /**
289 * Return link to add own shortcode
290 *
291 * @return mixed
292 */
293 function sh_cd_link_your_shortcodes_add() {
294
295 $link = admin_url('admin.php?page=sh-cd-shortcode-variables-your-shortcodes&action=add');
296
297 return esc_url( $link );
298 }
299
300 /**
301 * Return link to edit own shortcode
302 *
303 * @return mixed
304 */
305 function sh_cd_link_your_shortcodes_edit( $id ) {
306
307 $link = admin_url('admin.php?page=sh-cd-shortcode-variables-your-shortcodes&action=edit&id=' . (int) $id );
308
309 return esc_url( $link );
310 }
311
312 /**
313 * Either fetch data from the $_POST object or from the array passed in!
314 *
315 * @param $object
316 * @param $key
317 * @return string
318 */
319 function sh_cd_get_value_from_post_or_obj( $object, $key ) {
320
321 if ( true === isset( $_POST[ $key ] ) ) {
322 return $_POST[ $key ];
323 }
324
325 if ( true === isset( $object[ $key ] ) ) {
326 return $object[ $key ];
327 }
328
329 return '';
330 }
331
332 /**
333 * Either fetch data from the $_POST object for the given object keys
334 *
335 * @param $keys
336 * @return array
337 */
338 function sh_cd_get_values_from_post( $keys ) {
339
340 $data = [];
341
342 foreach ( $keys as $key ) {
343
344 if ( true === isset( $_POST[ $key ] ) ) {
345 $data[ $key ] = $_POST[ $key ];
346 } else {
347 $data[ $key ] = '';
348 }
349
350 }
351
352 return $data;
353 }
354
355 /**
356 * Toggle the status of a shortcode
357 *
358 * @param $id
359 */
360 function sh_cd_toggle_status( $id ) {
361
362 $slug = sh_cd_db_shortcodes_by_id( (int) $id );
363
364 if ( false === empty( $slug ) ) {
365
366 $status = ( 1 === (int) $slug['disabled'] ) ? 0 : 1 ;
367
368 sh_cd_db_shortcodes_update_status( $id, $status );
369
370 return $status;
371 }
372
373 return NULL;
374 }
375
376 /**
377 * Toggle the multisite of a shortcode
378 *
379 * @param $id
380 * @return int|null
381 */
382 function sh_cd_toggle_multisite( $id ) {
383
384 $slug = sh_cd_db_shortcodes_by_id( (int) $id );
385
386 if ( false === empty( $slug ) ) {
387
388 $multisite = ( 1 === (int) $slug['multisite'] ) ? 0 : 1 ;
389
390 sh_cd_db_shortcodes_update_multisite( $id, $multisite );
391
392 return $multisite;
393 }
394
395 return NULL;
396 }
397
398 /**
399 * Display an upgrade button
400 *
401 * @param string $css_class
402 * @param null $link
403 */
404 function sh_cd_upgrade_button( $css_class = '', $link = NULL ) {
405
406 $link = ( false === empty( $link ) ) ? $link : SH_CD_UPGRADE_LINK . '?hash=' . sh_cd_generate_site_hash() ;
407
408 $price = sh_cd_license_price();
409 $price = ( false === empty( $price ) ) ? sprintf( '- £%s %s', $price, __( 'a year ', SH_CD_SLUG ) ) : '';
410
411 echo sprintf('<a href="%s" class="button-primary sh-cd-upgrade-button sh-cd-button %s"><i class="far fa-star"></i> %s %s</a>',
412 esc_url( $link ),
413 esc_attr( ' ' . $css_class ),
414 __( 'Purchase a license ', SH_CD_SLUG ),
415 $price
416 );
417 }
418
419 /**
420 * Display an upgrade button
421 *
422 * @param string $css_class
423 * @param null $link
424 */
425 function sh_cd_premium_shortcode_download( $return = false ) {
426
427 $link = SH_CD_GET_PREMIUM_LINK . '?hash=' . sh_cd_generate_site_hash();
428
429 $html = sprintf('<a href="%s" class="button-primary sh-cd-button sh-cd-upgrade-button"><i class="fas fa-download"></i> %s</a>',
430 esc_url( $link ),
431 __( 'Download Premium Plugin', SH_CD_SLUG )
432 );
433
434 if ( true === $return ) {
435 return $html;
436 }
437
438 echo $html;
439 }
440
441 /**
442 * Is multsite functionality active for this install?
443 *
444 * @return bool
445 */
446 function sh_cd_is_multisite_enabled() {
447
448 if ( true === defined( 'YK_TEST_IS_MULTISITE' ) && true === YK_TEST_IS_MULTISITE ) {
449 return true;
450 }
451
452 if ( false === is_multisite() ) {
453 return false;
454 }
455
456 if ( false === sh_cd_is_premium() ) {
457 return false;
458 }
459
460 return true;
461 }
462
463 /**
464 * Fetch all multisite slugs
465 *
466 * @return array|null
467 */
468 function sh_cd_multisite_slugs() {
469
470 if ( false === is_multisite() ) {
471 return [];
472 }
473
474 $cache = sh_cd_cache_get( 'sh-cd-multisite-slugs' );
475
476 if ( false !== $cache ) {
477 return $cache;
478 }
479
480 $slugs = sh_cd_db_shortcodes_multisite_slugs();
481
482 $slugs = ( false === empty( $slugs ) ) ? wp_list_pluck( $slugs, 'slug' ) : [];
483
484 // Cache this for a short time
485 sh_cd_cache_set( 'sh-cd-multisite-slugs', $slugs, 30 );
486
487 return ( true === is_array( $slugs ) ) ? $slugs : [];
488 }
489
490 /**
491 * Have we reached the limit of free shortcodes?
492 * @return bool
493 */
494 function sh_cd_reached_free_limit() {
495
496 if ( true === sh_cd_is_premium() ) {
497 return false;
498 }
499
500 $existing_shortcodes = sh_cd_db_shortcodes_count();
501
502 if ( true === empty( $existing_shortcodes ) ) {
503 return false;
504 }
505
506 return ( (int) $existing_shortcodes >= sh_cd_get_free_limit() );
507 }
508
509 /**
510 * Return free limit for shortcodes
511 */
512 function sh_cd_get_free_limit() {
513 return 10;
514 }
515
516 /**
517 * Get the minimum user role allowed for viewing data pages in admin
518 * @return mixed|void
519 */
520 function sh_cd_permission_role() {
521
522 // If not premium, then admin only
523 if ( false === sh_cd_is_premium() ) {
524 return 'manage_options';
525 }
526
527 $permission_role = get_option( 'sh-cd-edit-permissions', 'manage_options' );
528
529 return ( false === empty( $permission_role ) ) ? $permission_role : 'manage_options';
530 }
531
532 /**
533 * Does the user have the correct permissions to view this page?
534 */
535 function sh_cd_permission_check() {
536
537 $allowed_viewer = sh_cd_permission_role();
538
539 if ( false === current_user_can( $allowed_viewer ) ) {
540 wp_die( __( 'You do not have sufficient permissions to access this page.', SH_CD_SLUG ) );
541 }
542 }
543
544 /**
545 * Is the shortcode [sv slug="sc-db-value-by-id"] enabled
546 * @return bool (default false)
547 */
548 function sh_cd_is_shortcode_db_value_by_id_enabled() {
549
550 if ( false === sh_cd_is_premium() ) {
551 return false;
552 }
553
554 // Disabling by filter overrides the setting in WP admin
555 if ( true === apply_filters( 'disable-ss-sc-db-value-by-id', __return_false() ) ) {
556 return false;
557 }
558
559 $value = get_option( 'sh-cd-shortcode-db-value-by-id-enabled', false );
560
561 return sh_cd_to_bool( $value );
562 }
563
564 /**
565 * Display upgrade notice
566 *
567 * @param bool $pro_plus
568 */
569 function sh_cd_display_pro_upgrade_notice( $title = NULL, $content = '', $class = '' ) {
570
571 $title = ( true === empty( $title ) ) ? __( 'Upgrade Snippet Shortcodes and get more features!', SH_CD_SLUG ) : $title;
572
573 ?>
574 <div class="postbox sh-cd-advertise-premium <?php echo esc_attr( $class ) ?>">
575 <h3 class="hndle"><i class="fa-regular fa-star"></i> <?php echo esc_html( $title ) ?></h3>
576 <div style="padding: 0px 15px 0px 15px">
577 <p><?php echo wp_kses( $content, [ 'ul' => [ 'class' ], 'li' => [], 'strong' => [], 'span' => [], 'div' => [] ] ); ?></p>
578 <p><a href="<?php echo esc_url( admin_url('admin.php?page=sh-cd-shortcode-variables-upgrade') ); ?>" class="button-primary sh-cd-upgrade-button"><i class="fa-regular fa-star"></i> <?php echo __( 'Get Premium', SH_CD_SLUG ); ?></a></p>
579 </div>
580 </div>
581 <?php
582 }
583
584 /**
585 * Display a star to prompt for a Premioum upgrade
586 *
587 * @param bool $pro_plus
588 */
589 function sh_cd_display_premium_star() {
590
591 if ( true === sh_cd_is_premium() ) {
592 return ''; // We don't want to show the star if the user has already upgraded
593 }
594
595 return sprintf ('<a href="%s"><i class="fa-regular fa-star"></i></a>',
596 esc_url( admin_url('admin.php?page=sh-cd-shortcode-variables-upgrade') )
597 );
598 }
599
600 /**
601 * Display info symbol with tooltip
602 */
603 function sh_cd_display_info_tooltip( $text ) {
604
605 return sprintf ('<i class="fa-regular fa-circle-question sh-cd-tooltip" title="%s">',
606 esc_html( $text )
607 );
608 }
609
610 /**
611 * Process a CSV attachment and import into database
612 *
613 * @param $attachment_id
614 *
615 * @param bool $dry_run
616 *
617 * @return string
618 */
619 function sh_cd_import_csv( $attachment_id, $dry_run = true ) {
620
621 sh_cd_permission_check();
622
623 if ( false === sh_cd_is_premium() ) {
624 return 'This is a premium feature';
625 }
626
627 $csv_path = get_attached_file( $attachment_id );
628 $admin_id = get_current_user_id();
629
630 if ( true === empty( $csv_path ) || false === file_exists( $csv_path )) {
631 return 'Error: Error loading CSV from disk.';
632 }
633
634 $csv = array_map('str_getcsv', file( $csv_path ) );
635
636 if ( true === empty( $csv ) ) {
637 return 'Error: The CSV appears to be empty.';
638 }
639
640 // Lowercase the header row up front so it's compared consistently against our
641 // (lowercase) column names both here and per-row below - previously the header row
642 // was validated case-sensitively while each data row's keys were lowercased.
643 $csv[0] = array_map( 'strtolower', $csv[0] );
644
645 array_walk($csv, function(&$a) use ($csv) {
646 $a = array_combine($csv[0], $a);
647 });
648
649 $validate_header_result = sh_cd_import_csv_validate_header( $csv[0] );
650
651 if ( true !== $validate_header_result ) {
652 return $validate_header_result;
653 }
654
655 array_shift($csv );
656
657 if ( true === empty( $csv ) ) {
658 return 'Error: The CSV appears to be empty (when header hs been removed).';
659 }
660
661 $errors = 0;
662
663 $output = sprintf( '%d rows to process...' . PHP_EOL, count( $csv ) );
664
665 if ( true === $dry_run ) {
666 $output .= 'DRY RUN MODE! No data will be imported.' . PHP_EOL;
667 }
668
669 foreach ( $csv as $row ) {
670
671 if ( $errors >= 50 ) {
672 $output .= 'Aborted! More than 50 errors have been detected in this file.' . PHP_EOL;
673 break;
674 }
675
676 $row = array_change_key_case( $row ); // Force CSV headers to lowercase
677
678 $validation_result = sh_cd_import_csv_validate_row( $row );
679
680 // Validate a row before proceeding
681 if ( true !== $validation_result ) {
682 $output .= $validation_result . PHP_EOL;
683 $errors++;
684 continue;
685 }
686
687 if ( false === $dry_run ) {
688
689 $shortcode = [ 'previous_slug' => '' ];
690
691 foreach ( sh_cd_csv_columns() as $column_name => $column ) {
692
693 if ( false === isset( $row[ $column_name ] ) ) {
694 continue;
695 }
696
697 $shortcode = array_merge( $shortcode, call_user_func( $column[ 'import' ], $row[ $column_name ] ) );
698 }
699
700 $result = sh_cd_db_shortcodes_save( $shortcode );
701
702 if ( false === $result ) {
703 $output .= 'Skipped: Error inserting into database (most likely a field contains too many characters or in the wrong format): ' . implode( ',', $row ) . PHP_EOL;
704 }
705 }
706
707 }
708
709 if ( $errors > 0 ) {
710 $output .= sprintf( '%d errors were detected and the rows skipped.' . PHP_EOL, $errors );
711 }
712
713 $output .= 'Completed.';
714
715 return $output;
716
717 }
718
719 /**
720 * Verify header row
721 * @param $header_row
722 *
723 * @return bool|string
724 */
725 function sh_cd_import_csv_validate_header( $header_row ) {
726
727 $required_columns = array_keys( array_filter( sh_cd_csv_columns(), function( $column ) {
728 return true === $column[ 'required' ];
729 } ) );
730
731 foreach ( $required_columns as $column ) {
732
733 if ( false === isset( $header_row[ $column ] ) ) {
734 return 'Missing column: ' . $column . '. Expecting at least: ' . implode( ',', $required_columns ) . PHP_EOL;
735 }
736 }
737
738 return true;
739 }
740
741 /**
742 * Validate CSV row
743 * @param $csv_row
744 *
745 * @return bool|string
746 */
747 function sh_cd_import_csv_validate_row( $csv_row ) {
748
749 if ( true === empty( $csv_row[ 'slug' ] ) ) {
750 return 'Skipped: Missing slug: ' . implode( ',', $csv_row );
751 }
752
753 if ( true === empty( $csv_row[ 'content' ] ) ) {
754 return 'Skipped: Missing content: ' . implode( ',', $csv_row );
755 }
756
757 $allowed_bools = [ 'yes', 'no', 'true', 'false', '1', '0' ];
758
759 if ( true === empty( $csv_row[ 'global' ] ) ||
760 false === in_array( $csv_row[ 'global' ], $allowed_bools ) ) {
761 return 'Skipped: Invalid "global" value. Must be "yes" or "no": ' . implode( ',', $csv_row );
762 }
763
764 if ( true === empty( $csv_row[ 'enabled' ] ) ||
765 false === in_array( $csv_row[ 'enabled' ], $allowed_bools ) ) {
766 return 'Skipped: Invalid "enabled" value. Must be "yes" or "no": ' . implode( ',', $csv_row );
767 }
768
769 // Give any Premium-registered columns a chance to reject their own value, so dry-run
770 // mode surfaces bad input instead of it being silently coerced later on save.
771 foreach ( sh_cd_csv_columns() as $column_name => $column ) {
772
773 if ( false === isset( $csv_row[ $column_name ] ) || false === isset( $column[ 'validate' ] ) ) {
774 continue;
775 }
776
777 $validation_result = call_user_func( $column[ 'validate' ], $csv_row[ $column_name ] );
778
779 if ( true !== $validation_result ) {
780 return $validation_result;
781 }
782 }
783
784 return true;
785 }
786
787 /**
788 * Ordered map of CSV column name => column definition, used by both sh_cd_import_csv() and
789 * sh_cd_export_csv(). Extensible via the 'sh-cd-csv-columns' filter so Premium can add columns
790 * for its own fields without core knowing about them - column names must be unique (a later
791 * registration silently overwrites an earlier one of the same name, same as any other array-shaped
792 * filter in this plugin).
793 *
794 * Each column definition:
795 * 'required' => bool column must be present in the CSV header row
796 * 'export' => callable( array $shortcode ): string decoded shortcode row -> CSV cell value
797 * 'import' => callable( string $value ): array CSV cell value -> partial $shortcode to merge
798 * 'validate' => callable( string $value ): true|string optional; true, or an error message
799 *
800 * @return array
801 */
802 function sh_cd_csv_columns() {
803
804 $columns = [
805 'slug' => [
806 'required' => true,
807 'export' => function( $shortcode ) { return $shortcode[ 'slug' ]; },
808 'import' => function( $value ) { return [ 'slug' => $value ]; },
809 ],
810 'content' => [
811 'required' => true,
812 'export' => function( $shortcode ) { return stripslashes( $shortcode[ 'data' ] ); },
813 'import' => function( $value ) { return [ 'data' => $value ]; },
814 ],
815 'global' => [
816 'required' => true,
817 'export' => function( $shortcode ) { return ( 1 === (int) $shortcode[ 'multisite' ] ) ? 'yes' : 'no'; },
818 'import' => function( $value ) { return [ 'multisite' => sh_cd_to_bool( $value ) ? 1 : 0 ]; },
819 ],
820 'enabled' => [
821 'required' => true,
822 'export' => function( $shortcode ) { return ( 1 === (int) $shortcode[ 'disabled' ] ) ? 'no' : 'yes'; },
823 'import' => function( $value ) { return [ 'disabled' => sh_cd_to_bool( $value ) ? 0 : 1 ]; },
824 ],
825 ];
826
827 return apply_filters( 'sh-cd-csv-columns', $columns );
828 }
829
830 /**
831 * Convert string to bool
832 * @param $string
833 * @return mixed
834 */
835 function sh_cd_to_bool( $string ) {
836 return filter_var( $string, FILTER_VALIDATE_BOOLEAN );
837 }
838
839 /**
840 * Export all shortcodes as a CSV string in the same column format sh_cd_import_csv() expects
841 * (see sh_cd_csv_columns()), so the output can be re-imported unchanged.
842 *
843 * @return string
844 */
845 function sh_cd_export_csv() {
846
847 sh_cd_permission_check();
848
849 $shortcodes = sh_cd_db_shortcodes_all();
850 $columns = sh_cd_csv_columns();
851
852 $stream = fopen( 'php://temp', 'r+' );
853
854 fputcsv( $stream, array_keys( $columns ) );
855
856 foreach ( $shortcodes as $shortcode ) {
857
858 // sh_cd_db_shortcodes_all() returns raw DB rows - run each through the same
859 // 'sh-cd-db-loaded-shortcode' filter used when loading a single shortcode, so
860 // Premium's JSON-encoded columns (device_type, roles, etc.) are decoded back into
861 // arrays before the column export callbacks below run.
862 $shortcode = sh_cd_db_filter_loaded_shortcode( $shortcode );
863
864 $row = [];
865
866 foreach ( $columns as $column ) {
867 $row[] = call_user_func( $column[ 'export' ], $shortcode );
868 }
869
870 fputcsv( $stream, $row );
871 }
872
873 rewind( $stream );
874 $csv = stream_get_contents( $stream );
875 fclose( $stream );
876
877 return $csv;
878 }
879
880 /**
881 * Our version of kses and the HTML we are happy with
882 */
883 function sh_cd_wp_kses( $value ) {
884
885 $basic_tags = wp_kses_allowed_html( 'html' );
886
887 $basic_tags[ 'a' ] = [ 'id' => true, 'class' => true, 'href' => true, 'title' => true, 'target' => true];
888 $basic_tags[ 'canvas' ] = [ 'id' => true, 'class' => true ];
889 $basic_tags[ 'div' ] = [ 'id' => true, 'class' => true, 'style' => true ];
890 $basic_tags[ 'i' ] = [ 'id' => true, 'class' => true ];
891 $basic_tags[ 'p' ] = [ 'id' => true, 'class' => true ];
892 $basic_tags[ 'span' ] = [ 'id' => true, 'class' => true ];
893 $basic_tags[ 'table' ] = [ 'id' => true, 'class' => true ];
894 $basic_tags[ 'tr' ] = [ 'id' => true, 'class' => true ];
895 $basic_tags[ 'td' ] = [ 'id' => true, 'class' => true ];
896 $basic_tags[ 'li' ] = [ 'class' => true ];
897
898 return wp_kses( $value, $basic_tags );
899 }
900
901 /**
902 * Return the current url
903 */
904 function sh_cd_get_current_url() {
905 $protocol = (
906 ( isset($_SERVER['HTTPS'] ) && 'on' == $_SERVER['HTTPS'] ) ||
907 ( isset($_SERVER['SERVER_PORT'] ) && 443 == $_SERVER['SERVER_PORT'] )
908 ) ? 'https://' : 'http://';
909
910 return $protocol . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
911 }
912
913 /**
914 * Get selected default editor
915 */
916 function sh_cd_default_editor_get() {
917 return get_option( 'sh-cd-option-default-editor', 'tinymce' );
918 }
919
920 /**
921 * Is editor valid
922 */
923 function sh_cd_editors_is_valid( $editor ) {
924 $editors = sh_cd_editors_options();
925
926 return ! empty( $editors[ $editor ] );
927 }
928
929 /**
930 * Return valid editors
931 */
932 function sh_cd_editors_options( $keys_only = true ) {
933 return [ 'tinymce' => __( 'WordPress Editor', SH_CD_SLUG ), 'code' => __( 'HTML Editor', SH_CD_SLUG ) ];
934 }
935
936 /**
937 * Are tooltips enabled?
938 */
939 function sh_cd_tooltips_is_enabled() {
940 return ( 'yes' === get_option( 'sh-cd-option-tool-tips-enabled', 'yes' ) );
941 }
942
943 /**
944 * Is render-count analytics (tracking how many times each shortcode is rendered, incrementing
945 * a DB counter on every render) enabled? A Premium-only feature, on by default - lets a
946 * high-traffic site opt out of the extra database write on every shortcode render.
947 *
948 * @return bool (default true when Premium, always false otherwise)
949 */
950 function sh_cd_is_render_count_enabled() {
951
952 if ( false === sh_cd_is_premium() ) {
953 return false;
954 }
955
956 return ( 'yes' === get_option( 'sh-cd-option-render-count-enabled', 'yes' ) );
957 }
958
959 /**
960 * Fetch icons for given shortcode
961 *
962 * @param [type] $shortcode
963 * @param boolean $return_array
964 * @return void
965 */
966 function sh_cd_icons_for_shortcode( $shortcode, $return_array = false ) {
967
968 if ( true === empty( $shortcode ) ) {
969 return [];
970 }
971
972 $icons = [];
973
974 if ( false === empty( $shortcode[ 'header' ] ) ) {
975 $icons[] = sprintf( '<i class="fa-solid fa-heading sh-cd-option-icon sh-cd-tooltip" title="%s"></i>', esc_html( __( 'Insert into WP Header', SH_CD_SLUG ) ) );
976 }
977
978 if ( false === empty( $shortcode[ 'footer' ] ) ) {
979 $icons[] = sprintf( '<i class="fa-solid fa-shoe-prints sh-cd-option-icon sh-cd-tooltip" title="%s"></i>', esc_html( __( 'Insert into WP Footer', SH_CD_SLUG ) ) );
980 }
981
982 if ( false === empty( $shortcode[ 'device_type' ] ) ) {
983 $shortcode[ 'device_type' ] = json_decode( $shortcode[ 'device_type' ] );
984 }
985
986 if ( true === is_array( $shortcode[ 'device_type' ] ) ) {
987
988 if ( true === in_array( 'desktop', $shortcode[ 'device_type' ] ) ) {
989 $icons[] = sprintf( '<i class="fa-solid fa-desktop sh-cd-option-icon sh-cd-tooltip" title="%s"></i>', esc_html( __( 'Display only on desktop devices', SH_CD_SLUG ) ) );
990 }
991
992 if ( true === in_array( 'mobile', $shortcode[ 'device_type' ] ) ) {
993 $icons[] = sprintf( '<i class="fa-solid fa-mobile-screen sh-cd-option-icon sh-cd-tooltip" title="%s"></i>', esc_html( __( 'Display only on mobile devices', SH_CD_SLUG ) ) );
994 }
995
996 if ( true === in_array( 'tablet', $shortcode[ 'device_type' ] ) ) {
997 $icons[] = sprintf( '<i class="fa-solid fa-tablet-screen-button sh-cd-option-icon sh-cd-tooltip" title="%s"></i>', esc_html( __( 'Display only on tablet devices', SH_CD_SLUG ) ) );
998 }
999 }
1000
1001 if ( true === $return_array ) {
1002 return $icons;
1003 }
1004
1005 return ( false === empty( $icons ) ) ? implode( PHP_EOL, $icons ) : '';
1006 }