PluginProbe
Snippet Shortcodes / 5.1
Snippet Shortcodes v5.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
← All changes | includes/functions.php +29 -167 trunk5.1 View file →
@@ -64,12 +64,8 @@
64 64
65 65 /**
66 66 * Replace user parameters within a shortcode e.g. look for %%parameter%% and replace
67 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 68 * @param $shortcode
73 69 * @param $user_defined_parameters
74 70 *
75 71 * @return mixed
@@ -80,31 +76,10 @@
80 76 if ( true === empty( $user_defined_parameters ) || false === is_array( $user_defined_parameters ) ) {
81 77 return $shortcode;
82 78 }
83 79
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 80 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 );
81 + $shortcode = str_replace( '%%' . $key . '%%', $value, $shortcode );
107 82 }
108 83
109 84 return $shortcode;
110 85 }
@@ -309,8 +284,21 @@
309 284 return esc_url( $link );
310 285 }
311 286
312 287 /**
288 + * Return link to delete own shortcode
289 + *
290 + * @param $id
291 + * @return mixed
292 + */
293 +function sh_cd_link_your_shortcodes_delete( $id ) {
294 +
295 + $link = admin_url('admin.php?page=sh-cd-shortcode-variables-your-shortcodes&action=delete&id=' . (int) $id );
296 +
297 + return esc_url( $link );
298 +}
299 +
300 +/**
313 301 * Either fetch data from the $_POST object or from the array passed in!
314 302 *
315 303 * @param $object
316 304 * @param $key
@@ -617,9 +605,11 @@
617 605 * @return string
618 606 */
619 607 function sh_cd_import_csv( $attachment_id, $dry_run = true ) {
620 608
621 - sh_cd_permission_check();
609 + if ( false === sh_cd_permission_check() ) {
610 + return 'You do not have the correct admin permissions';
611 + }
622 612
623 613 if ( false === sh_cd_is_premium() ) {
624 614 return 'This is a premium feature';
625 615 }
@@ -636,13 +626,8 @@
636 626 if ( true === empty( $csv ) ) {
637 627 return 'Error: The CSV appears to be empty.';
638 628 }
639 629
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 630 array_walk($csv, function(&$a) use ($csv) {
646 631 $a = array_combine($csv[0], $a);
647 632 });
648 633
@@ -685,19 +670,15 @@
685 670 }
686 671
687 672 if ( false === $dry_run ) {
688 673
689 - $shortcode = [ 'previous_slug' => '' ];
674 + $shortcode = [ 'slug' => $row[ 'slug' ],
675 + 'previous_slug' => '',
676 + 'data' => $row[ 'content' ],
677 + 'disabled' => ! sh_cd_to_bool( $row[ 'enabled' ] ),
678 + 'multisite' => sh_cd_to_bool( $row[ 'global' ] )
679 + ];
690 680
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 681 $result = sh_cd_db_shortcodes_save( $shortcode );
701 682
702 683 if ( false === $result ) {
703 684 $output .= 'Skipped: Error inserting into database (most likely a field contains too many characters or in the wrong format): ' . implode( ',', $row ) . PHP_EOL;
@@ -723,16 +704,14 @@
723 704 * @return bool|string
724 705 */
725 706 function sh_cd_import_csv_validate_header( $header_row ) {
726 707
727 - $required_columns = array_keys( array_filter( sh_cd_csv_columns(), function( $column ) {
728 - return true === $column[ 'required' ];
729 - } ) );
708 + $expected_headers = [ 'slug', 'content', 'global', 'enabled' ];
730 709
731 - foreach ( $required_columns as $column ) {
710 + foreach ( $expected_headers as $column ) {
732 711
733 712 if ( false === isset( $header_row[ $column ] ) ) {
734 - return 'Missing column: ' . $column . '. Expecting at least: ' . implode( ',', $required_columns ) . PHP_EOL;
713 + return 'Missing column: ' . $column . '. Expecting: ' . implode( ',', $expected_headers ) . PHP_EOL;
735 714 }
736 715 }
737 716
738 717 return true;
@@ -749,10 +728,10 @@
749 728 if ( true === empty( $csv_row[ 'slug' ] ) ) {
750 729 return 'Skipped: Missing slug: ' . implode( ',', $csv_row );
751 730 }
752 731
753 - if ( true === empty( $csv_row[ 'content' ] ) ) {
754 - return 'Skipped: Missing content: ' . implode( ',', $csv_row );
732 + if ( false === empty( $isset[ 'content' ] ) ) {
733 + return 'Skipped: Content: ' . implode( ',', $csv_row );
755 734 }
756 735
757 736 $allowed_bools = [ 'yes', 'no', 'true', 'false', '1', '0' ];
758 737
@@ -765,70 +744,12 @@
765 744 false === in_array( $csv_row[ 'enabled' ], $allowed_bools ) ) {
766 745 return 'Skipped: Invalid "enabled" value. Must be "yes" or "no": ' . implode( ',', $csv_row );
767 746 }
768 747
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 748 return true;
785 749 }
786 750
787 751 /**
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 752 * Convert string to bool
832 753 * @param $string
833 754 * @return mixed
834 755 */
@@ -836,49 +757,8 @@
836 757 return filter_var( $string, FILTER_VALIDATE_BOOLEAN );
837 758 }
838 759
839 760 /**
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 761 * Our version of kses and the HTML we are happy with
882 762 */
883 763 function sh_cd_wp_kses( $value ) {
884 764
@@ -940,24 +820,8 @@
940 820 return ( 'yes' === get_option( 'sh-cd-option-tool-tips-enabled', 'yes' ) );
941 821 }
942 822
943 823 /**
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 824 * Fetch icons for given shortcode
961 825 *
962 826 * @param [type] $shortcode
963 827 * @param boolean $return_array
@@ -979,12 +843,10 @@
979 843 $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 844 }
981 845
982 846 if ( false === empty( $shortcode[ 'device_type' ] ) ) {
847 +
983 848 $shortcode[ 'device_type' ] = json_decode( $shortcode[ 'device_type' ] );
984 - }
985 -
986 - if ( true === is_array( $shortcode[ 'device_type' ] ) ) {
987 849
988 850 if ( true === in_array( 'desktop', $shortcode[ 'device_type' ] ) ) {
989 851 $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 852 }