$value ) { $placeholder = '%%' . $key . '%%'; if ( false === strpos( $shortcode, $placeholder ) ) { continue; } // First, swap in any occurrence sitting inside a URL attribute value, escaped with esc_url(). $shortcode = preg_replace_callback( '/(?
%s
', true === $error ? 'error' : 'updated', esc_html( $text ) ); //TODO: Hook this to use admin_notices } /** * Fetch cache item * * @param $key * * @return mixed */ function sh_cd_cache_get( $key ) { $key = sh_cd_cache_generate_key( $key ); return get_transient( $key ); } /** * Set cache item * * @param $key * @param $data */ function sh_cd_cache_set( $key, $data, $expire = NULL ) { $expire = ( false === empty( $expire ) ) ? (int) $expire : 1 * HOUR_IN_SECONDS; $key = sh_cd_cache_generate_key( $key ); set_transient( $key, $data, $expire ); do_action( 'sh-cd-global-cache-delete' ); } /** * Delete cache for given shortcode slug / ID * * @param $slug_or_key */ function sh_cd_cache_delete_by_slug_or_key( $slug_or_key ) { if ( true === is_numeric( $slug_or_key ) ) { $slug_or_key = sh_cd_db_shortcodes_get_slug_by_id( $slug_or_key ); sh_cd_cache_delete( $slug_or_key ); } else { sh_cd_cache_delete( $slug_or_key ); } // Delete site option $slug_or_key = SH_CD_PREFIX . $slug_or_key; delete_site_option( $slug_or_key ); } /** * Delete cache item * * @param $key * * @return mixed */ function sh_cd_cache_delete( $key, $trigger_global_hook = true ) { $key = sh_cd_cache_generate_key( $key ); if ( true === $trigger_global_hook ) { do_action( 'sh-cd-global-cache-delete' ); } return delete_transient( $key ); } /** * Generate cache key * * @param $key * * @return string */ function sh_cd_cache_generate_key( $key ) { return SH_CD_SHORTCODE . SH_CD_PLUGIN_VERSION . $key; } /** * Return link to list own shortcodes * * @return mixed */ function sh_cd_link_your_shortcodes() { $link = admin_url('admin.php?page=sh-cd-shortcode-variables-your-shortcodes'); return esc_url( $link ); } /** * Return link to add own shortcode * * @return mixed */ function sh_cd_link_your_shortcodes_add() { $link = admin_url('admin.php?page=sh-cd-shortcode-variables-your-shortcodes&action=add'); return esc_url( $link ); } /** * Return link to edit own shortcode * * @return mixed */ function sh_cd_link_your_shortcodes_edit( $id ) { $link = admin_url('admin.php?page=sh-cd-shortcode-variables-your-shortcodes&action=edit&id=' . (int) $id ); return esc_url( $link ); } /** * Either fetch data from the $_POST object or from the array passed in! * * @param $object * @param $key * @return string */ function sh_cd_get_value_from_post_or_obj( $object, $key ) { if ( true === isset( $_POST[ $key ] ) ) { return $_POST[ $key ]; } if ( true === isset( $object[ $key ] ) ) { return $object[ $key ]; } return ''; } /** * Either fetch data from the $_POST object for the given object keys * * @param $keys * @return array */ function sh_cd_get_values_from_post( $keys ) { $data = []; foreach ( $keys as $key ) { if ( true === isset( $_POST[ $key ] ) ) { $data[ $key ] = $_POST[ $key ]; } else { $data[ $key ] = ''; } } return $data; } /** * Toggle the status of a shortcode * * @param $id */ function sh_cd_toggle_status( $id ) { $slug = sh_cd_db_shortcodes_by_id( (int) $id ); if ( false === empty( $slug ) ) { $status = ( 1 === (int) $slug['disabled'] ) ? 0 : 1 ; sh_cd_db_shortcodes_update_status( $id, $status ); return $status; } return NULL; } /** * Toggle the multisite of a shortcode * * @param $id * @return int|null */ function sh_cd_toggle_multisite( $id ) { $slug = sh_cd_db_shortcodes_by_id( (int) $id ); if ( false === empty( $slug ) ) { $multisite = ( 1 === (int) $slug['multisite'] ) ? 0 : 1 ; sh_cd_db_shortcodes_update_multisite( $id, $multisite ); return $multisite; } return NULL; } /** * Display an upgrade button * * @param string $css_class * @param null $link */ function sh_cd_upgrade_button( $css_class = '', $link = NULL ) { $link = ( false === empty( $link ) ) ? $link : SH_CD_UPGRADE_LINK . '?hash=' . sh_cd_generate_site_hash() ; $price = sh_cd_license_price(); $price = ( false === empty( $price ) ) ? sprintf( '- £%s %s', $price, __( 'a year ', SH_CD_SLUG ) ) : ''; echo sprintf(' %s %s', esc_url( $link ), esc_attr( ' ' . $css_class ), __( 'Purchase a license ', SH_CD_SLUG ), $price ); } /** * Display an upgrade button * * @param string $css_class * @param null $link */ function sh_cd_premium_shortcode_download( $return = false ) { $link = SH_CD_GET_PREMIUM_LINK . '?hash=' . sh_cd_generate_site_hash(); $html = sprintf(' %s', esc_url( $link ), __( 'Download Premium Plugin', SH_CD_SLUG ) ); if ( true === $return ) { return $html; } echo $html; } /** * Is multsite functionality active for this install? * * @return bool */ function sh_cd_is_multisite_enabled() { if ( true === defined( 'YK_TEST_IS_MULTISITE' ) && true === YK_TEST_IS_MULTISITE ) { return true; } if ( false === is_multisite() ) { return false; } if ( false === sh_cd_is_premium() ) { return false; } return true; } /** * Fetch all multisite slugs * * @return array|null */ function sh_cd_multisite_slugs() { if ( false === is_multisite() ) { return []; } $cache = sh_cd_cache_get( 'sh-cd-multisite-slugs' ); if ( false !== $cache ) { return $cache; } $slugs = sh_cd_db_shortcodes_multisite_slugs(); $slugs = ( false === empty( $slugs ) ) ? wp_list_pluck( $slugs, 'slug' ) : []; // Cache this for a short time sh_cd_cache_set( 'sh-cd-multisite-slugs', $slugs, 30 ); return ( true === is_array( $slugs ) ) ? $slugs : []; } /** * Have we reached the limit of free shortcodes? * @return bool */ function sh_cd_reached_free_limit() { if ( true === sh_cd_is_premium() ) { return false; } $existing_shortcodes = sh_cd_db_shortcodes_count(); if ( true === empty( $existing_shortcodes ) ) { return false; } return ( (int) $existing_shortcodes >= sh_cd_get_free_limit() ); } /** * Return free limit for shortcodes */ function sh_cd_get_free_limit() { return 10; } /** * Get the minimum user role allowed for viewing data pages in admin * @return mixed|void */ function sh_cd_permission_role() { // If not premium, then admin only if ( false === sh_cd_is_premium() ) { return 'manage_options'; } $permission_role = get_option( 'sh-cd-edit-permissions', 'manage_options' ); return ( false === empty( $permission_role ) ) ? $permission_role : 'manage_options'; } /** * Does the user have the correct permissions to view this page? */ function sh_cd_permission_check() { $allowed_viewer = sh_cd_permission_role(); if ( false === current_user_can( $allowed_viewer ) ) { wp_die( __( 'You do not have sufficient permissions to access this page.', SH_CD_SLUG ) ); } } /** * Is the shortcode [sv slug="sc-db-value-by-id"] enabled * @return bool (default false) */ function sh_cd_is_shortcode_db_value_by_id_enabled() { if ( false === sh_cd_is_premium() ) { return false; } // Disabling by filter overrides the setting in WP admin if ( true === apply_filters( 'disable-ss-sc-db-value-by-id', __return_false() ) ) { return false; } $value = get_option( 'sh-cd-shortcode-db-value-by-id-enabled', false ); return sh_cd_to_bool( $value ); } /** * Display upgrade notice * * @param bool $pro_plus */ function sh_cd_display_pro_upgrade_notice( $title = NULL, $content = '', $class = '' ) { $title = ( true === empty( $title ) ) ? __( 'Upgrade Snippet Shortcodes and get more features!', SH_CD_SLUG ) : $title; ?> ', esc_url( admin_url('admin.php?page=sh-cd-shortcode-variables-upgrade') ) ); } /** * Display info symbol with tooltip */ function sh_cd_display_info_tooltip( $text ) { return sprintf ('', esc_html( $text ) ); } /** * Process a CSV attachment and import into database * * @param $attachment_id * * @param bool $dry_run * * @return string */ function sh_cd_import_csv( $attachment_id, $dry_run = true ) { sh_cd_permission_check(); if ( false === sh_cd_is_premium() ) { return 'This is a premium feature'; } $csv_path = get_attached_file( $attachment_id ); $admin_id = get_current_user_id(); if ( true === empty( $csv_path ) || false === file_exists( $csv_path )) { return 'Error: Error loading CSV from disk.'; } $csv = array_map('str_getcsv', file( $csv_path ) ); if ( true === empty( $csv ) ) { return 'Error: The CSV appears to be empty.'; } // Lowercase the header row up front so it's compared consistently against our // (lowercase) column names both here and per-row below - previously the header row // was validated case-sensitively while each data row's keys were lowercased. $csv[0] = array_map( 'strtolower', $csv[0] ); array_walk($csv, function(&$a) use ($csv) { $a = array_combine($csv[0], $a); }); $validate_header_result = sh_cd_import_csv_validate_header( $csv[0] ); if ( true !== $validate_header_result ) { return $validate_header_result; } array_shift($csv ); if ( true === empty( $csv ) ) { return 'Error: The CSV appears to be empty (when header hs been removed).'; } $errors = 0; $output = sprintf( '%d rows to process...' . PHP_EOL, count( $csv ) ); if ( true === $dry_run ) { $output .= 'DRY RUN MODE! No data will be imported.' . PHP_EOL; } foreach ( $csv as $row ) { if ( $errors >= 50 ) { $output .= 'Aborted! More than 50 errors have been detected in this file.' . PHP_EOL; break; } $row = array_change_key_case( $row ); // Force CSV headers to lowercase $validation_result = sh_cd_import_csv_validate_row( $row ); // Validate a row before proceeding if ( true !== $validation_result ) { $output .= $validation_result . PHP_EOL; $errors++; continue; } if ( false === $dry_run ) { $shortcode = [ 'previous_slug' => '' ]; foreach ( sh_cd_csv_columns() as $column_name => $column ) { if ( false === isset( $row[ $column_name ] ) ) { continue; } $shortcode = array_merge( $shortcode, call_user_func( $column[ 'import' ], $row[ $column_name ] ) ); } $result = sh_cd_db_shortcodes_save( $shortcode ); if ( false === $result ) { $output .= 'Skipped: Error inserting into database (most likely a field contains too many characters or in the wrong format): ' . implode( ',', $row ) . PHP_EOL; } } } if ( $errors > 0 ) { $output .= sprintf( '%d errors were detected and the rows skipped.' . PHP_EOL, $errors ); } $output .= 'Completed.'; return $output; } /** * Verify header row * @param $header_row * * @return bool|string */ function sh_cd_import_csv_validate_header( $header_row ) { $required_columns = array_keys( array_filter( sh_cd_csv_columns(), function( $column ) { return true === $column[ 'required' ]; } ) ); foreach ( $required_columns as $column ) { if ( false === isset( $header_row[ $column ] ) ) { return 'Missing column: ' . $column . '. Expecting at least: ' . implode( ',', $required_columns ) . PHP_EOL; } } return true; } /** * Validate CSV row * @param $csv_row * * @return bool|string */ function sh_cd_import_csv_validate_row( $csv_row ) { if ( true === empty( $csv_row[ 'slug' ] ) ) { return 'Skipped: Missing slug: ' . implode( ',', $csv_row ); } if ( true === empty( $csv_row[ 'content' ] ) ) { return 'Skipped: Missing content: ' . implode( ',', $csv_row ); } $allowed_bools = [ 'yes', 'no', 'true', 'false', '1', '0' ]; if ( true === empty( $csv_row[ 'global' ] ) || false === in_array( $csv_row[ 'global' ], $allowed_bools ) ) { return 'Skipped: Invalid "global" value. Must be "yes" or "no": ' . implode( ',', $csv_row ); } if ( true === empty( $csv_row[ 'enabled' ] ) || false === in_array( $csv_row[ 'enabled' ], $allowed_bools ) ) { return 'Skipped: Invalid "enabled" value. Must be "yes" or "no": ' . implode( ',', $csv_row ); } // Give any Premium-registered columns a chance to reject their own value, so dry-run // mode surfaces bad input instead of it being silently coerced later on save. foreach ( sh_cd_csv_columns() as $column_name => $column ) { if ( false === isset( $csv_row[ $column_name ] ) || false === isset( $column[ 'validate' ] ) ) { continue; } $validation_result = call_user_func( $column[ 'validate' ], $csv_row[ $column_name ] ); if ( true !== $validation_result ) { return $validation_result; } } return true; } /** * Ordered map of CSV column name => column definition, used by both sh_cd_import_csv() and * sh_cd_export_csv(). Extensible via the 'sh-cd-csv-columns' filter so Premium can add columns * for its own fields without core knowing about them - column names must be unique (a later * registration silently overwrites an earlier one of the same name, same as any other array-shaped * filter in this plugin). * * Each column definition: * 'required' => bool column must be present in the CSV header row * 'export' => callable( array $shortcode ): string decoded shortcode row -> CSV cell value * 'import' => callable( string $value ): array CSV cell value -> partial $shortcode to merge * 'validate' => callable( string $value ): true|string optional; true, or an error message * * @return array */ function sh_cd_csv_columns() { $columns = [ 'slug' => [ 'required' => true, 'export' => function( $shortcode ) { return $shortcode[ 'slug' ]; }, 'import' => function( $value ) { return [ 'slug' => $value ]; }, ], 'content' => [ 'required' => true, 'export' => function( $shortcode ) { return stripslashes( $shortcode[ 'data' ] ); }, 'import' => function( $value ) { return [ 'data' => $value ]; }, ], 'global' => [ 'required' => true, 'export' => function( $shortcode ) { return ( 1 === (int) $shortcode[ 'multisite' ] ) ? 'yes' : 'no'; }, 'import' => function( $value ) { return [ 'multisite' => sh_cd_to_bool( $value ) ? 1 : 0 ]; }, ], 'enabled' => [ 'required' => true, 'export' => function( $shortcode ) { return ( 1 === (int) $shortcode[ 'disabled' ] ) ? 'no' : 'yes'; }, 'import' => function( $value ) { return [ 'disabled' => sh_cd_to_bool( $value ) ? 0 : 1 ]; }, ], ]; return apply_filters( 'sh-cd-csv-columns', $columns ); } /** * Convert string to bool * @param $string * @return mixed */ function sh_cd_to_bool( $string ) { return filter_var( $string, FILTER_VALIDATE_BOOLEAN ); } /** * Export all shortcodes as a CSV string in the same column format sh_cd_import_csv() expects * (see sh_cd_csv_columns()), so the output can be re-imported unchanged. * * @return string */ function sh_cd_export_csv() { sh_cd_permission_check(); $shortcodes = sh_cd_db_shortcodes_all(); $columns = sh_cd_csv_columns(); $stream = fopen( 'php://temp', 'r+' ); fputcsv( $stream, array_keys( $columns ) ); foreach ( $shortcodes as $shortcode ) { // sh_cd_db_shortcodes_all() returns raw DB rows - run each through the same // 'sh-cd-db-loaded-shortcode' filter used when loading a single shortcode, so // Premium's JSON-encoded columns (device_type, roles, etc.) are decoded back into // arrays before the column export callbacks below run. $shortcode = sh_cd_db_filter_loaded_shortcode( $shortcode ); $row = []; foreach ( $columns as $column ) { $row[] = call_user_func( $column[ 'export' ], $shortcode ); } fputcsv( $stream, $row ); } rewind( $stream ); $csv = stream_get_contents( $stream ); fclose( $stream ); return $csv; } /** * Our version of kses and the HTML we are happy with */ function sh_cd_wp_kses( $value ) { $basic_tags = wp_kses_allowed_html( 'html' ); $basic_tags[ 'a' ] = [ 'id' => true, 'class' => true, 'href' => true, 'title' => true, 'target' => true]; $basic_tags[ 'canvas' ] = [ 'id' => true, 'class' => true ]; $basic_tags[ 'div' ] = [ 'id' => true, 'class' => true, 'style' => true ]; $basic_tags[ 'i' ] = [ 'id' => true, 'class' => true ]; $basic_tags[ 'p' ] = [ 'id' => true, 'class' => true ]; $basic_tags[ 'span' ] = [ 'id' => true, 'class' => true ]; $basic_tags[ 'table' ] = [ 'id' => true, 'class' => true ]; $basic_tags[ 'tr' ] = [ 'id' => true, 'class' => true ]; $basic_tags[ 'td' ] = [ 'id' => true, 'class' => true ]; $basic_tags[ 'li' ] = [ 'class' => true ]; return wp_kses( $value, $basic_tags ); } /** * Return the current url */ function sh_cd_get_current_url() { $protocol = ( ( isset($_SERVER['HTTPS'] ) && 'on' == $_SERVER['HTTPS'] ) || ( isset($_SERVER['SERVER_PORT'] ) && 443 == $_SERVER['SERVER_PORT'] ) ) ? 'https://' : 'http://'; return $protocol . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"]; } /** * Get selected default editor */ function sh_cd_default_editor_get() { return get_option( 'sh-cd-option-default-editor', 'tinymce' ); } /** * Is editor valid */ function sh_cd_editors_is_valid( $editor ) { $editors = sh_cd_editors_options(); return ! empty( $editors[ $editor ] ); } /** * Return valid editors */ function sh_cd_editors_options( $keys_only = true ) { return [ 'tinymce' => __( 'WordPress Editor', SH_CD_SLUG ), 'code' => __( 'HTML Editor', SH_CD_SLUG ) ]; } /** * Are tooltips enabled? */ function sh_cd_tooltips_is_enabled() { return ( 'yes' === get_option( 'sh-cd-option-tool-tips-enabled', 'yes' ) ); } /** * Is render-count analytics (tracking how many times each shortcode is rendered, incrementing * a DB counter on every render) enabled? A Premium-only feature, on by default - lets a * high-traffic site opt out of the extra database write on every shortcode render. * * @return bool (default true when Premium, always false otherwise) */ function sh_cd_is_render_count_enabled() { if ( false === sh_cd_is_premium() ) { return false; } return ( 'yes' === get_option( 'sh-cd-option-render-count-enabled', 'yes' ) ); } /** * Fetch icons for given shortcode * * @param [type] $shortcode * @param boolean $return_array * @return void */ function sh_cd_icons_for_shortcode( $shortcode, $return_array = false ) { if ( true === empty( $shortcode ) ) { return []; } $icons = []; if ( false === empty( $shortcode[ 'header' ] ) ) { $icons[] = sprintf( '', esc_html( __( 'Insert into WP Header', SH_CD_SLUG ) ) ); } if ( false === empty( $shortcode[ 'footer' ] ) ) { $icons[] = sprintf( '', esc_html( __( 'Insert into WP Footer', SH_CD_SLUG ) ) ); } if ( false === empty( $shortcode[ 'device_type' ] ) ) { $shortcode[ 'device_type' ] = json_decode( $shortcode[ 'device_type' ] ); } if ( true === is_array( $shortcode[ 'device_type' ] ) ) { if ( true === in_array( 'desktop', $shortcode[ 'device_type' ] ) ) { $icons[] = sprintf( '', esc_html( __( 'Display only on desktop devices', SH_CD_SLUG ) ) ); } if ( true === in_array( 'mobile', $shortcode[ 'device_type' ] ) ) { $icons[] = sprintf( '', esc_html( __( 'Display only on mobile devices', SH_CD_SLUG ) ) ); } if ( true === in_array( 'tablet', $shortcode[ 'device_type' ] ) ) { $icons[] = sprintf( '', esc_html( __( 'Display only on tablet devices', SH_CD_SLUG ) ) ); } } if ( true === $return_array ) { return $icons; } return ( false === empty( $icons ) ) ? implode( PHP_EOL, $icons ) : ''; }