PK!##ultimate_helper.phpnu[ $value ) { if ( ! empty( $value ) ) { if ( stripos( $value, '^' ) !== false ) { $tmvav_array = explode( '^', $value ); if ( is_array( $tmvav_array ) && ! empty( $tmvav_array ) ) { if ( ! empty( $tmvav_array ) ) { if ( isset( $tmvav_array[0] ) ) { $mainarr[ $tmvav_array[0] ] = ( isset( $tmvav_array[1] ) ) ? $tmvav_array[1] : ''; } } } } else { $mainarr['id'] = $temp_id; $mainarr['url'] = $temp_url; } } } } if ( '' != $data ) { switch ( $data ) { case 'url': // First - Priority for ID. if ( ! empty( $mainarr['id'] ) && 'null' != $mainarr['id'] ) { $image_url = ''; // Get image URL, If input is number - e.g. 100x48 / 140x40 / 350x53. if ( 1 === preg_match( '/^\d/', $size ) ) { $size = explode( 'x', $size ); // resize image using vc helper function - wpb_resize. $img = wpb_resize( $mainarr['id'], null, $size[0], $size[1], true ); if ( $img ) { $image_url = $img['url']; } } else { // Get image URL, If input is string - [thumbnail, medium, large, full]. $hasimage = wp_get_attachment_image_src( $mainarr['id'], $size ); // returns an array. $image_url = isset( $hasimage[0] ) ? $hasimage[0] : ''; } if ( isset( $image_url ) && ! empty( $image_url ) ) { $final = $image_url; } else { // Second - Priority for URL - get {image from url}. if ( isset( $mainarr['url'] ) ) { $final = ult_get_url( $mainarr['url'] ); } } } else { // Second - Priority for URL - get {image from url}. if ( isset( $mainarr['url'] ) ) { $final = ult_get_url( $mainarr['url'] ); } } break; case 'title': $final = isset( $mainarr['title'] ) ? $mainarr['title'] : get_post_meta( $mainarr['id'], '_wp_attachment_image_title', true ); break; case 'caption': $final = isset( $mainarr['caption'] ) ? $mainarr['caption'] : get_post_meta( $mainarr['id'], '_wp_attachment_image_caption', true ); break; case 'alt': $final = isset( $mainarr['alt'] ) ? $mainarr['alt'] : get_post_meta( $mainarr['id'], '_wp_attachment_image_alt', true ); break; case 'description': $final = isset( $mainarr['description'] ) ? $mainarr['description'] : get_post_meta( $mainarr['id'], '_wp_attachment_image_description', true ); break; case 'json': $final = wp_json_encode( $mainarr ); break; case 'sizes': $img_size = getimagesqueresize( $img_id, $img_size ); $img = wpb_getImageBySize( array( 'attach_id' => $img_id, 'thumb_size' => $img_size, 'class' => 'vc_single_image-img', ) ); $final = $img; break; case 'array': default: $final = $mainarr; break; } } } return $final; } add_filter( 'ult_get_img_single', 'ult_img_single_init', 10, 3 ); } if ( ! function_exists( 'ult_get_url' ) ) { /** * Ult_get_url. * * @param string $img Img. */ function ult_get_url( $img ) { if ( isset( $img ) && ! empty( $img ) ) { return $img; } } } // USE THIS CODE TO SUPPORT CUSTOM SIZE OPTION. if ( ! function_exists( 'getimagesqueresize' ) ) { /** * GetImageSquereSize. * * @param string $img_id Image ID. * @param string $img_size Image Size. */ function getimagesqueresize( $img_id, $img_size ) { if ( preg_match_all( '/(\d+)x(\d+)/', $img_size, $sizes ) ) { $exact_size = array( 'width' => isset( $sizes[1][0] ) ? $sizes[1][0] : '0', 'height' => isset( $sizes[2][0] ) ? $sizes[2][0] : '0', ); } else { $image_downsize = image_downsize( $img_id, $img_size ); $exact_size = array( 'width' => $image_downsize[1], 'height' => $image_downsize[2], ); } if ( isset( $exact_size['width'] ) && (int) $exact_size['width'] !== (int) $exact_size['height'] ) { $img_size = (int) $exact_size['width'] > (int) $exact_size['height'] ? $exact_size['height'] . 'x' . $exact_size['height'] : $exact_size['width'] . 'x' . $exact_size['width']; } return $img_size; } } /* Ultimate Box Shadow */ if ( ! function_exists( 'ultimate_get_box_shadow' ) ) { /** * GetImageSquereSize. * * @param string $content Content. * @param string $data Image Data. */ function ultimate_get_box_shadow( $content = null, $data = '' ) { // e.g. horizontal:14px|vertical:20px|blur:30px|spread:40px|color:#81d742|style:inset|. $final = ''; if ( '' != $content ) { // Create an array. $mainstr = explode( '|', $content ); $string = ''; $mainarr = array(); if ( ! empty( $mainstr ) && is_array( $mainstr ) ) { foreach ( $mainstr as $key => $value ) { if ( ! empty( $value ) ) { $string = explode( ':', $value ); if ( is_array( $string ) ) { if ( ! empty( $string[1] ) && 'outset' != $string[1] ) { $mainarr[ $string[0] ] = $string[1]; } } } } } $rm_bar = str_replace( '|', '', $content ); $rm_colon = str_replace( ':', ' ', $rm_bar ); $rmkeys = str_replace( 'horizontal', '', $rm_colon ); $rmkeys = str_replace( 'vertical', '', $rmkeys ); $rmkeys = str_replace( 'blur', '', $rmkeys ); $rmkeys = str_replace( 'spread', '', $rmkeys ); $rmkeys = str_replace( 'color', '', $rmkeys ); $rmkeys = str_replace( 'style', '', $rmkeys ); $rmkeys = str_replace( 'outset', '', $rmkeys ); // Remove outset from style - To apply {outset} box. shadow. if ( '' != $data ) { switch ( $data ) { case 'data': $final = $rmkeys; break; case 'array': $final = $mainarr; break; case 'css': default: $final = 'box-shadow:' . $rmkeys . ';'; break; } } else { $final = 'box-shadow:' . $rmkeys . ';'; } } return $final; } add_filter( 'ultimate_getboxshadow', 'ultimate_get_box_shadow', 10, 3 ); } PK!gg(class-wpau-my-youtube-channel-widget.phpnu[ __( 'Serve YouTube videos from channel or playlist right to widget area', 'youtube-channel' ), 'customize_selective_refresh' => true, ) ); } // END function __construct() // Outputs the content of the widget public function widget( $args, $instance ) { global $wpau_my_youtube_channel; // Skip rendering attempt if no widget_id is available. if ( empty( $args['widget_id'] ) && true !== boolval( $wpau_my_youtube_channel->defaults['block_preview'] ) ) { return; } echo $args['before_widget']; if ( ! empty( $instance['title'] ) ) { echo $args['before_title'] . apply_filters( 'widget_title', $instance['title'], $instance, YTC_PLUGIN_SLUG ) . $args['after_title']; } echo $wpau_my_youtube_channel->generate_ytc_block( $instance ); echo $args['after_widget']; } // END public function widget() public function form( $instance ) { global $wpau_my_youtube_channel; $defaults = get_option( 'youtube_channel_defaults' ); // Outputs the options form for widget settings // General Options $title = ! empty( $instance['title'] ) ? sanitize_text_field( $instance['title'] ) : ''; $class = ! empty( $instance['class'] ) ? sanitize_html_classes( $instance['class'] ) : ''; $handle = ! empty( $instance['handle'] ) ? sanitize_text_field( $instance['handle'] ) : $defaults['handle']; $vanity = ! empty( $instance['vanity'] ) ? sanitize_text_field( $instance['vanity'] ) : $defaults['vanity']; // deprecated $channel = ! empty( $instance['channel'] ) ? ytc_sanitize_api_key( $instance['channel'] ) : $defaults['channel']; $username = ! empty( $instance['username'] ) ? sanitize_key( $instance['username'] ) : $defaults['username']; // deprecated $playlist = ! empty( $instance['playlist'] ) ? ytc_sanitize_api_key( $instance['playlist'] ) : $defaults['playlist']; $resource = isset( $instance['resource'] ) ? intval( $instance['resource'] ) : intval( $defaults['resource'] ); // resource to use: channel, favorites, playlist $cache = isset( $instance['cache'] ) ? intval( $instance['cache'] ) : intval( $defaults['cache'] ); $fetch = ! empty( $instance['fetch'] ) ? intval( $instance['fetch'] ) : intval( $defaults['fetch'] ); // items to fetch $num = ! empty( $instance['num'] ) ? intval( $instance['num'] ) : intval( $defaults['num'] ); // number of items to show $skip = isset( $instance['skip'] ) ? intval( $instance['skip'] ) : 0; // number of items to skip $privacy = ! empty( $instance['privacy'] ) ? boolval( $instance['privacy'] ) : false; $random = ! empty( $instance['random'] ) ? boolval( $instance['random'] ) : false; // Video Settings $ratio = ! empty( $instance['ratio'] ) ? intval( $instance['ratio'] ) : intval( $defaults['ratio'] ); $width = ! empty( $instance['width'] ) ? intval( $instance['width'] ) : intval( $defaults['width'] ); $responsive = isset( $instance['responsive'] ) ? boolval( $instance['responsive'] ) : true; $display = ! empty( $instance['display'] ) ? sanitize_key( $instance['display'] ) : sanitize_key( $defaults['display'] ); $thumb_quality = ! empty( $instance['thumb_quality'] ) ? sanitize_key( $instance['thumb_quality'] ) : sanitize_key( $defaults['thumb_quality'] ); $no_thumb_title = ! empty( $instance['no_thumb_title'] ) ? boolval( $instance['no_thumb_title'] ) : false; $controls = ! empty( $instance['controls'] ) ? boolval( $instance['controls'] ) : false; $autoplay = ! empty( $instance['autoplay'] ) ? boolval( $instance['autoplay'] ) : false; $autoplay_mute = ! empty( $instance['autoplay_mute'] ) ? boolval( $instance['autoplay_mute'] ) : false; $norel = ! empty( $instance['norel'] ) ? boolval( $instance['norel'] ) : false; // Content Layout $showtitle = ! empty( $instance['showtitle'] ) ? sanitize_key( $instance['showtitle'] ) : 'none'; $linktitle = ! empty( $instance['linktitle'] ) ? boolval( $instance['linktitle'] ) : false; $titletag = ! empty( $instance['titletag'] ) ? strtolower( esc_attr( $instance['titletag'] ) ) : 'h3'; $showdesc = ! empty( $instance['showdesc'] ) ? boolval( $instance['showdesc'] ) : false; $modestbranding = ! empty( $instance['modestbranding'] ) ? boolval( $instance['modestbranding'] ) : false; $desclen = ! empty( $instance['desclen'] ) ? intval( $instance['desclen'] ) : 0; $hideanno = ! empty( $instance['hideanno'] ) ? boolval( $instance['hideanno'] ) : false; // Link to Channel $link_to = ! empty( $instance['link_to'] ) ? sanitize_key( $instance['link_to'] ) : 'none'; $goto_txt = ! empty( $instance['goto_txt'] ) ? sanitize_text_field( $instance['goto_txt'] ) : ''; $popup_goto = isset( $instance['popup_goto'] ) ? intval( $instance['popup_goto'] ) : intval( $defaults['popup_goto'] ); ?>

get_field_id( '' ) > 0 ) : ?> slug = YTC_PLUGIN_SLUG; $this->option_name = YTC_PLUGIN_OPTION_KEY; $this->defaults = get_option( $this->option_name ); // register actions add_action( 'admin_init', array( $this, 'register_settings' ) ); add_action( 'admin_menu', array( $this, 'add_menu' ) ); } // END public function __construct /** * hook into WP's register_settings action hook */ public function register_settings() { global $wpau_my_youtube_channel; // =========================== General =========================== // --- Add settings section General so we can add fields to it --- add_settings_section( 'ytc_general', // Section Name esc_html__( 'General', 'youtube-channel' ), // Section Title array( &$this, 'settings_general_section_description' ), // Section Callback Function esc_attr( $this->slug . '_general' ) // Page Name ); // --- Add Fields to General section --- // YouTube Data API Key add_settings_field( $this->option_name . 'apikey', // Setting Slug esc_html__( 'YouTube Data API Key', 'youtube-channel' ), // Title array( &$this, 'settings_field_input_text' ), // Callback esc_attr( $this->slug . '_general' ), // Page Name 'ytc_general', // Section Name array( 'field' => $this->option_name . '[apikey]', 'desc_required' => true, 'desc_global' => true, 'description' => __( 'Your YouTube Data API Key', 'youtube-channel' ), 'desc_link_url' => 'https://console.developers.google.com/project', 'desc_link_txt' => __( 'Google Developers Console', 'youtube-channel' ), 'class' => 'regular-text password blur-on-lose-focus', 'value' => isset( $this->defaults['apikey'] ) ? esc_html( $this->defaults['apikey'] ) : '', ) // args ); // Channel ID add_settings_field( $this->option_name . 'channel', // Setting Slug esc_html__( 'YouTube Channel ID', 'youtube-channel' ), // Title array( &$this, 'settings_field_input_text' ), // Callback esc_attr( $this->slug . '_general' ), // Page Name 'ytc_general', // Section Name array( 'field' => $this->option_name . '[channel]', 'label_for' => $this->option_name . '[channel]', 'desc_required' => true, 'desc_link_url' => 'https://www.youtube.com/account_advanced', 'desc_link_txt' => __( 'YouTube Advanced Settings', 'youtube-channel' ), 'description' => __( 'Your YouTube Channel ID you can get from', 'youtube-channel' ), 'class' => 'regular-text', 'value' => isset( $this->defaults['channel'] ) ? $this->defaults['channel'] : '', ) // args ); // Handle (new in 2022) add_settings_field( $this->option_name . 'handle', // id esc_html__( 'YouTube Handle', 'youtube-channel' ), // Title array( &$this, 'settings_field_input_text' ), // Callback esc_attr( $this->slug . '_general' ), // Page Name 'ytc_general', // section array( 'field' => $this->option_name . '[handle]', 'label_for' => $this->option_name . '[handle]', 'desc_link_url' => 'https://www.youtube.com/handle', 'desc_link_txt' => __( 'Your handle', 'youtube-channel' ), 'description' => __( 'Your YouTube Handle handle including @ from', 'youtube-channel' ), 'class' => 'regular-text', 'value' => isset( $this->defaults['handle'] ) ? $this->defaults['handle'] : '', ) // args ); // Vanity (deprecated in 2022) add_settings_field( $this->option_name . 'vanity', // id esc_html__( 'YouTube Vanity Name', 'youtube-channel' ), // Title array( &$this, 'settings_field_input_text' ), // Callback esc_attr( $this->slug . '_general' ), // Page Name 'ytc_general', // section array( 'field' => $this->option_name . '[vanity]', 'label_for' => $this->option_name . '[vanity]', 'desc_deprecated' => true, 'description' => sprintf( // translators: %s is replaced with www.youtube.com/c/ __( 'Your YouTube Custom Name used to be part of %s', 'youtube-channel' ), 'www.youtube.com/c/' ), 'class' => 'regular-text deprecated', 'value' => isset( $this->defaults['vanity'] ) ? $this->defaults['vanity'] : '', ) // args ); // Username add_settings_field( $this->option_name . 'username', // id esc_html__( 'YouTube Username', 'youtube-channel' ), // Title array( &$this, 'settings_field_input_text' ), // Callback esc_attr( $this->slug . '_general' ), // Page Name 'ytc_general', // section array( 'field' => $this->option_name . '[username]', 'label_for' => $this->option_name . '[username]', 'desc_deprecated' => true, 'description' => __( 'Your YouTube legacy username', 'youtube-channel' ), 'class' => 'regular-text deprecated', 'value' => isset( $this->defaults['username'] ) ? $this->defaults['username'] : '', ) // args ); // Default Playlist add_settings_field( $this->option_name . 'playlist', // id esc_html__( 'Default Playlist ID', 'youtube-channel' ), // Title array( &$this, 'settings_field_input_text' ), // Callback esc_attr( $this->slug . '_general' ), // Page Name 'ytc_general', // section array( 'field' => $this->option_name . '[playlist]', 'label_for' => $this->option_name . '[playlist]', 'desc_optional' => true, 'description' => __( 'Enter default playlist ID (not playlist name)', 'youtube-channel' ), 'class' => 'regular-text', 'value' => isset( $this->defaults['playlist'] ) ? $this->defaults['playlist'] : '', ) // args ); // Resource add_settings_field( $this->option_name . 'resource', // id esc_html__( 'Resource to use', 'youtube-channel' ), // Title array( &$this, 'settings_field_select' ), // Callback esc_attr( $this->slug . '_general' ), // Page Name 'ytc_general', // section array( 'field' => $this->option_name . '[resource]', 'label_for' => $this->option_name . '[resource]', 'description' => __( 'What to use as resource for feeds', 'youtube-channel' ), 'class' => 'regular-text', 'value' => isset( $this->defaults['resource'] ) ? $this->defaults['resource'] : '0', 'items' => array( '0' => __( 'Channel', 'youtube-channel' ), // '1' => __( 'Favourites', 'youtube-channel' ), // deprecated since 3.23.0 // '3' => __( 'Liked Video', 'youtube-channel' ), // deprecated since 3.23.0 '2' => __( 'Playlist', 'youtube-channel' ), ), ) // args ); // Cache add_settings_field( $this->option_name . 'cache', // id esc_html__( 'Cache Timeout', 'youtube-channel' ), array( &$this, 'settings_field_select' ), esc_attr( $this->slug . '_general' ), // Page Name 'ytc_general', array( 'field' => $this->option_name . '[cache]', 'label_for' => $this->option_name . '[cache]', 'description' => __( 'Define caching timeout for YouTube feeds, in seconds', 'youtube-channel' ), 'class' => 'wide-text', 'value' => isset( $this->defaults['cache'] ) ? $this->defaults['cache'] : '300', 'items' => $wpau_my_youtube_channel->cache_timeouts, ) ); // Fetch add_settings_field( $this->option_name . 'fetch', // id esc_html__( 'Fetch', 'youtube-channel' ), // Title array( &$this, 'settings_field_input_number' ), // Callback esc_attr( $this->slug . '_general' ), // Page Name 'ytc_general', // section array( 'field' => $this->option_name . '[fetch]', 'label_for' => $this->option_name . '[fetch]', 'description' => __( 'Number of videos that will be fetched from YouTube and used for random pick (min 2, max 50, default 25)', 'youtube-channel' ), 'class' => 'num', 'value' => isset( $this->defaults['fetch'] ) ? $this->defaults['fetch'] : 25, 'min' => 1, 'max' => 50, 'std' => 25, ) // args ); // Show add_settings_field( $this->option_name . 'num', // id esc_html__( 'Show', 'youtube-channel' ), // Title array( &$this, 'settings_field_input_number' ), // Callback esc_attr( $this->slug . '_general' ), // Page Name 'ytc_general', // section array( 'field' => $this->option_name . '[num]', 'label_for' => $this->option_name . '[num]', 'description' => __( 'Number of videos to display', 'youtube-channel' ), 'class' => 'num', 'value' => isset( $this->defaults['num'] ) ? $this->defaults['num'] : 1, 'min' => 1, 'max' => 50, 'std' => 1, ) // args ); // Timeout add_settings_field( $this->option_name . 'timeout', // id esc_html__( 'Timeout', 'youtube-channel' ), // Title array( &$this, 'settings_field_input_number' ), // Callback esc_attr( $this->slug . '_general' ), // Page Name 'ytc_general', // section array( 'field' => $this->option_name . '[timeout]', 'label_for' => $this->option_name . '[timeout]', 'description' => __( 'Time in seconds, before the connection with YouTube DATA Api is dropped and an error is returned', 'youtube-channel' ), 'class' => 'timeout', 'value' => isset( $this->defaults['timeout'] ) ? $this->defaults['timeout'] : 5, 'min' => 5, 'max' => 360, 'std' => 5, ) // args ); // SSL Verify add_settings_field( $this->option_name . 'sslverify', // id esc_html__( 'Verify SSL', 'youtube-channel' ), // Title array( &$this, 'settings_field_checkbox' ), // Callback esc_attr( $this->slug . '_general' ), // Page Name 'ytc_general', // section array( 'field' => $this->option_name . '[sslverify]', 'label_for' => $this->option_name . '[sslverify]', 'label' => __( 'Yes', 'youtube-channel' ), 'description' => __( 'If your website host fail to verify SSL Certificate for GoogleApis.com server (if you see in YTC Error keywords cURL and SSL), disable this option.', 'youtube-channel' ), 'class' => 'checkbox', 'value' => isset( $this->defaults['sslverify'] ) ? $this->defaults['sslverify'] : false, ) // args ); // Enhanced privacy add_settings_field( $this->option_name . 'privacy', // id esc_html__( 'Enhanced Privacy', 'youtube-channel' ), // Title array( &$this, 'settings_field_checkbox' ), // Callback esc_attr( $this->slug . '_general' ), // Page Name 'ytc_general', // section array( 'field' => $this->option_name . '[privacy]', 'label_for' => $this->option_name . '[privacy]', 'label' => __( 'Yes', 'youtube-channel' ), 'desc_link_url' => 'https://support.google.com/youtube/answer/171780', 'desc_link_txt' => __( 'Learn more here', 'youtube-channel' ), 'description' => __( 'Enable this option to protect your visitors privacy.', 'youtube-channel' ), 'class' => 'checkbox', 'value' => isset( $this->defaults['privacy'] ) ? $this->defaults['privacy'] : false, ) // args ); // Local Images add_settings_field( $this->option_name . 'local_img', // id esc_html__( 'Local Images', 'youtube-channel' ), // Title array( &$this, 'settings_field_checkbox' ), // Callback esc_attr( $this->slug . '_general' ), // Page Name 'ytc_general', // section array( 'field' => $this->option_name . '[local_img]', 'label_for' => $this->option_name . '[local_img]', 'label' => __( 'Yes', 'youtube-channel' ), 'desc_link_url' => 'https://developer.chrome.com/docs/lighthouse/performance/uses-long-cache-ttl/', 'desc_link_txt' => __( 'Learn more here', 'youtube-channel' ), 'description' => __( 'Store video thumbnails locally. Can help with Speed Performance (efficient cache policy).', 'youtube-channel' ), 'class' => 'checkbox', 'value' => isset( $this->defaults['local_img'] ) ? $this->defaults['local_img'] : false, ) // args ); // Event Listener add_settings_field( $this->option_name . 'js_ev_listener', // id esc_html__( 'Event Listener', 'youtube-channel' ), // Title array( &$this, 'settings_field_checkbox' ), // Callback esc_attr( $this->slug . '_general' ), // Page Name 'ytc_general', // section array( 'field' => $this->option_name . '[js_ev_listener]', 'label_for' => $this->option_name . '[js_ev_listener]', 'label' => __( 'Yes', 'youtube-channel' ), 'description' => __( 'If YTC block fail to render on your website because of async/defer loading of JavaScript, try to enable this option to wrap YTC code within DOMContentLoaded event listener', 'youtube-channel' ), 'class' => 'checkbox', 'value' => isset( $this->defaults['js_ev_listener'] ) ? $this->defaults['js_ev_listener'] : false, ) // args ); // TinyMCE icon add_settings_field( $this->option_name . 'tinymce', // id esc_html__( 'TinyMCE button', 'youtube-channel' ), // Title array( &$this, 'settings_field_checkbox' ), // Callback esc_attr( $this->slug . '_general' ), // Page Name 'ytc_general', // section array( 'field' => $this->option_name . '[tinymce]', 'label_for' => $this->option_name . '[tinymce]', 'label' => __( 'Yes', 'youtube-channel' ), 'description' => sprintf( // translators: %s is replaced with plugin name __( 'Disable this option to hide %s button from TinyMCE toolbar on post and page editor.', 'youtube-channel' ), YTC_PLUGIN_NAME ), 'class' => 'checkbox', 'value' => isset( $this->defaults['tinymce'] ) ? $this->defaults['tinymce'] : false, ) // args ); // Widget Preview add_settings_field( $this->option_name . 'block_preview', // id esc_html__( 'Preview Widget in Block Editor', 'youtube-channel' ), // Title array( &$this, 'settings_field_checkbox' ), // Callback esc_attr( $this->slug . '_general' ), // Page Name 'ytc_general', // section array( 'field' => $this->option_name . '[block_preview]', 'label_for' => $this->option_name . '[block_preview]', 'label' => __( 'Yes', 'youtube-channel' ), 'description' => sprintf( // translators: %s is replaced with plugin name __( 'Disable this option to prevent %s Widget Preview gets rendered on Block Editor.', 'youtube-channel' ), YTC_PLUGIN_NAME ), 'class' => 'checkbox', 'value' => isset( $this->defaults['block_preview'] ) ? $this->defaults['block_preview'] : true, ) // args ); // --- Register setting General so $_POST handling is done --- register_setting( 'ytc_general', // Setting group $this->option_name, // option name array( $this, 'sanitize_options' ) ); // =========================== VIDEO =========================== // --- Add settings section Video so we can add fields to it --- add_settings_section( 'ytc_video', // Section Name esc_html__( 'Video Tweaks', 'youtube-channel' ), // Section Title array( &$this, 'settings_video_section_description' ), // Section Callback Function esc_attr( $this->slug . '_video' ) // Page Name ); // --- Add Fields to video section --- // Width add_settings_field( $this->option_name . 'width', // id esc_html__( 'Initial Width', 'youtube-channel' ), // Title array( &$this, 'settings_field_input_number' ), // Callback esc_attr( $this->slug . '_video' ), // Page Name 'ytc_video', // section array( 'field' => $this->option_name . '[width]', 'label_for' => $this->option_name . '[width]', 'description' => __( 'Set default width for displayed video, in pixels. This value have effect only if Responsive Video option is disabled!', 'youtube-channel' ), 'class' => 'num', 'value' => isset( $this->defaults['width'] ) ? $this->defaults['width'] : '306', 'min' => 120, 'max' => 1980, 'std' => 306, ) // args ); // Aspect Ratio add_settings_field( $this->option_name . 'ratio', // id esc_html__( 'Aspect ratio', 'youtube-channel' ), // Title array( &$this, 'settings_field_select' ), // Callback esc_attr( $this->slug . '_video' ), // Page Name 'ytc_video', // section array( 'field' => $this->option_name . '[ratio]', 'label_for' => $this->option_name . '[ratio]', 'description' => __( 'Select aspect ratio for displayed video', 'youtube-channel' ), 'class' => 'regular-text', 'value' => isset( $this->defaults['ratio'] ) ? $this->defaults['ratio'] : '3', 'items' => array( '3' => '16:9', '1' => '4:3', ), ) // args ); // Display add_settings_field( $this->option_name . 'display', // id esc_html__( 'Embed as', 'youtube-channel' ), // Title array( &$this, 'settings_field_select' ), // Callback esc_attr( $this->slug . '_video' ), // Page Name 'ytc_video', // section array( 'field' => $this->option_name . '[display]', 'label_for' => $this->option_name . '[display]', 'description' => __( 'Choose how to embed video block', 'youtube-channel' ), 'class' => 'regular-text', 'value' => isset( $this->defaults['display'] ) ? $this->defaults['display'] : 'thumbnail', 'items' => array( 'thumbnail' => __( 'Thumbnail', 'youtube-channel' ), 'iframe' => __( 'HTML5 (iframe)', 'youtube-channel' ), 'iframe2' => __( 'HTML5 (iframe) Asynchronous', 'youtube-channel' ), 'playlist' => __( 'Embedded Playlist', 'youtube-channel' ), ), ) // args ); // Thumbnail Quality add_settings_field( $this->option_name . 'thumb_quality', // id esc_html__( 'Thumbnail Quality', 'youtube-channel' ), // Title array( &$this, 'settings_field_select' ), // Callback esc_attr( $this->slug . '_video' ), // Page Name 'ytc_video', // section array( 'field' => $this->option_name . '[thumb_quality]', 'label_for' => $this->option_name . '[thumb_quality]', 'description' => __( 'Choose preferred thumbnail quality. Please be aware, if you select Maximum Resolution but video does not have that thumbnail, you will get broken thumbnail on page!', 'youtube-channel' ), 'class' => 'regular-text', 'value' => isset( $this->defaults['thumb_quality'] ) ? $this->defaults['thumb_quality'] : '0', 'items' => array( 'default' => __( 'Default Quality (120x90px)', 'youtube-channel' ), 'mqdefault' => __( 'Medium Quality (320x180px)', 'youtube-channel' ), 'hqdefault' => __( 'High Quality (480x360px)', 'youtube-channel' ), 'sddefault' => __( 'Standard Definition (640x480px)', 'youtube-channel' ), 'maxresdefault' => __( 'Maximum Resolution (1280x720px)', 'youtube-channel' ), ), ) // args ); // Responsive add_settings_field( $this->option_name . 'responsive', // id esc_html__( 'Responsive Video', 'youtube-channel' ), // Title array( &$this, 'settings_field_checkbox' ), // Callback esc_attr( $this->slug . '_video' ), // Page Name 'ytc_video', // section array( 'field' => $this->option_name . '[responsive]', 'label_for' => $this->option_name . '[responsive]', 'label' => __( 'Yes', ' __( 'Enable this option to make YTC videos and thumbnails responsive by default. Please note, this option will set videos and thumbnail to full width relative to parent container, and disable more than one video per row.', 'youtube-channel' ), 'class' => 'checkbox', 'value' => isset( $this->defaults['responsive'] ) ? $this->defaults['responsive'] : false, ) // args ); // Plays Inline add_settings_field( $this->option_name . 'playsinline', // id esc_html__( 'Play inline on iOS', 'youtube-channel' ), // Title array( &$this, 'settings_field_checkbox' ), // Callback esc_attr( $this->slug . '_video' ), // Page Name 'ytc_video', // section array( 'field' => $this->option_name . '[playsinline]', 'label_for' => $this->option_name . '[playsinline]', 'label' => __( 'Yes', 'youtube-channel' ), 'desc_link_url' => 'https://developers.google.com/youtube/player_parameters#playsinline', 'desc_link_txt' => __( 'Learn more here', 'youtube-channel' ), 'description' => __( 'Enable this option to override fullscreen playback on iOS, and force inline playback on page and in lightbox.', 'youtube-channel' ), 'class' => 'checkbox', 'value' => isset( $this->defaults['playsinline'] ) ? $this->defaults['playsinline'] : false, ) // args ); // No Lightbox add_settings_field( $this->option_name . 'nolightbox', // id esc_html__( 'Disable Lightbox', 'youtube-channel' ), // Title array( &$this, 'settings_field_checkbox' ), // Callback esc_attr( $this->slug . '_video' ), // Page Name 'ytc_video', // section array( 'field' => $this->option_name . '[nolightbox]', 'label_for' => $this->option_name . '[nolightbox]', 'label' => __( 'Yes', 'youtube-channel' ), 'description' => __( 'Enable this option to disable built-in lightbox for thumbnails (in case that you have youtube links lightbox trigger in theme or other plugin).', 'youtube-channel' ), 'class' => 'checkbox', 'value' => isset( $this->defaults['nolightbox'] ) ? $this->defaults['nolightbox'] : false, ) // args ); // Full Screen add_settings_field( $this->option_name . 'fullscreen', // id esc_html__( 'Full Screen', 'youtube-channel' ), // Title array( &$this, 'settings_field_checkbox' ), // Callback esc_attr( $this->slug . '_video' ), // Page Name 'ytc_video', // section array( 'field' => $this->option_name . '[fullscreen]', 'label_for' => $this->option_name . '[fullscreen]', 'label' => __( 'Yes', 'youtube-channel' ), 'description' => __( 'Enable this option to make available Full Screen button for embedded playlists.', 'youtube-channel' ), 'class' => 'checkbox', 'value' => isset( $this->defaults['fullscreen'] ) ? $this->defaults['fullscreen'] : false, ) // args ); // No Player Controls add_settings_field( $this->option_name . 'controls', // id esc_html__( 'Hide Player Controls', 'youtube-channel' ), // Title array( &$this, 'settings_field_checkbox' ), // Callback esc_attr( $this->slug . '_video' ), // Page Name 'ytc_video', // section array( 'field' => $this->option_name . '[controls]', 'label_for' => $this->option_name . '[controls]', 'label' => __( 'Yes', ' __( 'Enable this option to hide playback controls', 'youtube-channel' ), 'class' => 'checkbox', 'value' => isset( $this->defaults['controls'] ) ? $this->defaults['controls'] : false, ) // args ); // Autoplay add_settings_field( $this->option_name . 'autoplay', // id esc_html__( 'Autoplay video or playlist', 'youtube-channel' ), // Title array( &$this, 'settings_field_checkbox' ), // Callback esc_attr( $this->slug . '_video' ), // Page Name 'ytc_video', // section array( 'field' => $this->option_name . '[autoplay]', 'label_for' => $this->option_name . '[autoplay]', 'label' => __( 'Yes', ' __( 'Enable this option to start video playback right after block is rendered', 'youtube-channel' ), 'class' => 'checkbox', 'value' => isset( $this->defaults['autoplay'] ) ? $this->defaults['autoplay'] : false, ) // args ); // Mute on autoplay add_settings_field( $this->option_name . 'autoplay_mute', // id esc_html__( 'Mute video on autoplay', 'youtube-channel' ), // Title array( &$this, 'settings_field_checkbox' ), // Callback esc_attr( $this->slug . '_video' ), // Page Name 'ytc_video', // section array( 'field' => $this->option_name . '[autoplay_mute]', 'label_for' => $this->option_name . '[autoplay_mute]', 'label' => __( 'Yes', ' __( 'Enable this option to mute video when start autoplay', 'youtube-channel' ), 'class' => 'checkbox', 'value' => isset( $this->defaults['autoplay_mute'] ) ? $this->defaults['autoplay_mute'] : false, ) // args ); // Only channel related videos add_settings_field( $this->option_name . 'norel', // id esc_html__( 'Only channel related videos', 'youtube-channel' ), // Title array( &$this, 'settings_field_checkbox' ), // Callback esc_attr( $this->slug . '_video' ), // Page Name 'ytc_video', // section array( 'field' => $this->option_name . '[norel]', 'label_for' => $this->option_name . '[norel]', 'label' => __( 'Yes', ' __( 'Enable this option to show after finished playback only related videos that come from the same channel as the video that was just played', 'youtube-channel' ), 'class' => 'checkbox', 'value' => isset( $this->defaults['norel'] ) ? $this->defaults['norel'] : false, ) // args ); // Hide Annotations add_settings_field( $this->option_name . 'hideanno', // id esc_html__( 'Hide video annotations', 'youtube-channel' ), // Title array( &$this, 'settings_field_checkbox' ), // Callback esc_attr( $this->slug . '_video' ), // Page Name 'ytc_video', // section array( 'field' => $this->option_name . '[hideanno]', 'label_for' => $this->option_name . '[hideanno]', 'label' => __( 'Yes', ' __( 'Enable this option to hide video annotations (custom text set by uploader over video during playback)', 'youtube-channel' ), 'class' => 'checkbox', 'value' => isset( $this->defaults['hideanno'] ) ? $this->defaults['hideanno'] : false, ) // args ); // Hide YT logo add_settings_field( $this->option_name . 'modestbranding', // id esc_html__( 'Hide YouTube logo', 'youtube-channel' ), // Title array( &$this, 'settings_field_checkbox' ), // Callback esc_attr( $this->slug . '_video' ), // Page Name 'ytc_video', // section array( 'field' => $this->option_name . '[modestbranding]', 'label_for' => $this->option_name . '[modestbranding]', 'label' => __( 'Yes', ' __( 'Enable this option to hide YouTube logo from playback control bar. Does not work for all videos.', 'youtube-channel' ), 'class' => 'checkbox', 'value' => isset( $this->defaults['modestbranding'] ) ? $this->defaults['modestbranding'] : false, ) // args ); // --- Register setting Video so $_POST handling is done --- register_setting( 'ytc_video', // Setting group $this->option_name, // option name array( $this, 'sanitize_options' ) ); // =========================== CONTENT =========================== // --- Add settings section Content so we can add fields to it --- add_settings_section( 'ytc_content', // Section Name esc_html__( 'Content Tweaks', 'youtube-channel' ), // Section Title array( &$this, 'settings_content_section_description' ), // Section Callback Function esc_attr( $this->slug . '_content' ) // Page Name ); // --- Add Fields to video section --- // Video Title add_settings_field( $this->option_name . 'showtitle', // id esc_html__( 'Video title', 'youtube-channel' ), // Title array( &$this, 'settings_field_select' ), // Callback esc_attr( $this->slug . '_content' ), // Page Name 'ytc_content', // section array( 'field' => $this->option_name . '[showtitle]', 'label_for' => $this->option_name . '[showtitle]', 'description' => __( 'Select should we and where to display title of video.', 'youtube-channel' ), 'class' => 'regular-text', 'value' => isset( $this->defaults['showtitle'] ) ? $this->defaults['showtitle'] : 'none', 'items' => array( 'none' => __( 'Hide title', 'youtube-channel' ), 'above' => __( 'Above video/thumbnail', 'youtube-channel' ), 'below' => __( 'Below video/thumbnail', 'youtube-channel' ), 'inside' => __( 'Inside thumbnail, top aligned', 'youtube-channel' ), 'inside_b' => __( 'Inside thumbnail, bottom aligned', 'youtube-channel' ), ), ) // args ); // Link Video Title add_settings_field( $this->option_name . 'linktitle', // id esc_html__( 'Link Title to Video', 'youtube-channel' ), // Title array( &$this, 'settings_field_checkbox' ), // Callback esc_attr( $this->slug . '_content' ), // Page Name 'ytc_content', // section array( 'field' => $this->option_name . '[linktitle]', 'label_for' => $this->option_name . '[linktitle]', 'label' => __( 'Yes', 'youtube-channel' ), 'description' => __( 'Enable this option to link outside title to video.', 'youtube-channel' ), 'class' => 'checkbox', 'value' => isset( $this->defaults['linktitle'] ) ? $this->defaults['linktitle'] : false, ) // args ); // Video Title HTML Tag add_settings_field( $this->option_name . 'titletag', // id esc_html__( 'Title HTML tag', 'youtube-channel' ), // Title array( &$this, 'settings_field_select' ), // Callback esc_attr( $this->slug . '_content' ), // Page Name 'ytc_content', // section array( 'field' => $this->option_name . '[titletag]', 'label_for' => $this->option_name . '[titletag]', 'description' => __( 'Select which HTML tag to use for title wrapper. Fallback if not set in shortcode.', 'youtube-channel' ), 'class' => 'regular-text', 'value' => isset( $this->defaults['titletag'] ) ? $this->defaults['titletag'] : 'h3', 'items' => array( 'h3' => 'h3', 'h4' => 'h4', 'h5' => 'h5', 'div' => 'div', 'span' => 'span', 'strong' => 'strong', ), ) // args ); // Video Description add_settings_field( $this->option_name . 'showdesc', // id esc_html__( 'Video description', 'youtube-channel' ), // Title array( &$this, 'settings_field_checkbox' ), // Callback esc_attr( $this->slug . '_content' ), // Page Name 'ytc_content', // section array( 'field' => $this->option_name . '[showdesc]', 'label_for' => $this->option_name . '[showdesc]', 'label' => __( 'Show', 'youtube-channel' ), 'description' => __( 'Enable this option to show video description.', 'youtube-channel' ), 'class' => 'checkbox', 'value' => isset( $this->defaults['showdesc'] ) ? $this->defaults['showdesc'] : false, ) // args ); // Description length add_settings_field( $this->option_name . 'desclen', // id esc_html__( 'Description length', 'youtube-channel' ), // Title array( &$this, 'settings_field_input_number' ), // Callback esc_attr( $this->slug . '_content' ), // Page Name 'ytc_content', // section array( 'field' => $this->option_name . '[desclen]', 'label_for' => $this->option_name . '[desclen]', 'description' => __( 'Enter length for video description in characters (0 for full length).', 'youtube-channel' ), 'class' => 'num', 'value' => isset( $this->defaults['desclen'] ) ? $this->defaults['desclen'] : 0, 'min' => 0, 'max' => 2500, 'std' => 0, ) // args ); // --- Register setting Content so $_POST handling is done --- register_setting( 'ytc_content', // Setting group $this->option_name, // option name array( $this, 'sanitize_options' ) ); // =========================== LINK =========================== // --- Add settings section Link to Channel so we can add fields to it --- add_settings_section( 'ytc_link', // Section Name esc_html__( 'Link to Channel', 'youtube-channel' ), // Section Title array( &$this, 'settings_link_section_description' ), // Section Callback Function $this->slug . '_link' // Page Name ); // --- Add Fields to video section --- // Link to... add_settings_field( $this->option_name . 'link_to', // id esc_html__( 'Link to...', 'youtube-channel' ), // Title array( &$this, 'settings_field_select' ), // Callback $this->slug . '_link', // Page 'ytc_link', // section array( 'field' => $this->option_name . '[link_to]', 'label_for' => $this->option_name . '[link_to]', 'description' => __( 'Set where link will lead visitors', 'youtube-channel' ), 'class' => 'regular-text', 'value' => isset( $this->defaults['link_to'] ) ? $this->defaults['link_to'] : 'none', 'items' => array( 'none' => __( 'Hide link', 'youtube-channel' ), 'handle' => __( 'YouTube handle URL', 'youtube-channel' ), 'channel' => __( 'Channel page URL', 'youtube-channel' ), 'vanity' => __( 'Vanity custom URL (deprecated)', 'youtube-channel' ), 'legacy' => __( 'Legacy username page (deprecated)', 'youtube-channel' ), ), ) // args ); // Open in... add_settings_field( $this->option_name . 'popup_goto', // id esc_html__( 'Open link in...', 'youtube-channel' ), // Title array( &$this, 'settings_field_select' ), // Callback $this->slug . '_link', // Page 'ytc_link', // section array( 'field' => $this->option_name . '[popup_goto]', 'label_for' => $this->option_name . '[popup_goto]', 'description' => __( 'Set how link will be opened', 'youtube-channel' ), 'class' => 'regular-text', 'value' => isset( $this->defaults['popup_goto'] ) ? $this->defaults['popup_goto'] : '0', 'items' => array( '0' => __( 'same window', 'youtube-channel' ), // option 1 - new window (JavaScript) is deprecated since 3.23.2 '2' => __( 'new window (target="_blank")', 'youtube-channel' ), ), ) // args ); // Visit channel text add_settings_field( $this->option_name . 'goto_txt', // id esc_html__( 'Text for Visit channel link', 'youtube-channel' ), // Title array( &$this, 'settings_field_input_text' ), // Callback $this->slug . '_link', // Page 'ytc_link', // section array( 'field' => $this->option_name . '[goto_txt]', 'label_for' => $this->option_name . '[goto_txt]', 'class' => 'regular-text', 'description' => __( 'Set default title for link', 'youtube-channel' ), 'value' => isset( $this->defaults['goto_txt'] ) ? $this->defaults['goto_txt'] : '', ) // args ); // --- Register setting Content so $_POST handling is done --- register_setting( 'ytc_link', // Setting group $this->option_name, // option name array( $this, 'sanitize_options' ) ); } // END public function register_settings() /** * Add settings menu */ public function add_menu() { // Add a page to manage this plugin's settings add_options_page( YTC_PLUGIN_NAME, YTC_PLUGIN_NAME, 'manage_options', $this->slug, array( &$this, 'plugin_settings_page' ) ); } // END public function add_menu() // ===================== HELPERS ========================== public function settings_description_required() { return sprintf( '[%s]', esc_html__( 'Required', 'youtube-channel' ) ); } public function settings_description_global() { return sprintf( '[%2$s]', esc_html__( 'This option is global only and can`t be changed per widget/shortcode', 'youtube-channel' ), esc_html__( 'Global', 'youtube-channel' ) ); } public function settings_description_optional() { return sprintf( '[%2$s]', esc_html__( 'You can leave this option empty and set it per widget/shortcode.', 'youtube-channel' ), esc_html__( 'Optional', 'youtube-channel' ) ); } public function settings_description_deprecated() { return sprintf( '[%2$s]', esc_html__( 'YouTube deprecated this option. You can use it if you know it, but you cannot get it anymore from YouTube.', 'youtube-channel' ), esc_html__( 'Deprecated', 'youtube-channel' ) ); } // --- Section desciptions --- public function settings_general_section_description() { echo '

'; printf( // translators: %s is replaced with plugin name esc_html__( 'Configure general defaults for the %s used as fallback options for shortcodes and initial values for new widget(s).', 'youtube-channel' ), '' . esc_html( YTC_PLUGIN_NAME ) . '' ); echo '
'; echo esc_html__( 'Only some options are fallback for widget (Channel ID, Handle, Vanity Name, Legacy Username, Default Playlist), while other options are just initial set of settings for new widget.', 'youtube-channel' ); echo '

'; } // END public function settings_general_section_description() public function settings_video_section_description() { echo '

'; printf( // translators: %s is replaced with plugin name esc_html__( 'Configure video specific defaults for %s used as fallback options for shortcodes and initial set of options for new widget.', 'youtube-channel' ), '' . esc_html( YTC_PLUGIN_NAME ) . '' ); echo '

'; } // END public function settings_video_section_description() public function settings_content_section_description() { echo '

'; printf( // translators: %s is replaced with plugin name esc_html__( 'Configure defaults of content around and over videos for %s used as fallback options for shortcodes and initial set of options for new widget.', 'youtube-channel' ), '' . esc_html( YTC_PLUGIN_NAME ) . '' ); echo '

'; } // END public function settings_content_section_description() public function settings_link_section_description() { echo '

'; printf( // translators: %s is replaced with plugin name esc_html__( 'Configure defaults for link to channel below %s block used as fallback options for shortcodes and initial set of options for new widget.', 'youtube-channel' ), '' . esc_html( YTC_PLUGIN_NAME ) . '' ); echo '

'; } // END public function settings_link_section_description() /** * This function provides separator for settings fields */ public function settings_field_separator() { echo '
'; } // END public function settings_field_input_text() /** * Prepare prefix and sufix for field Description * * @param array $args Array of field options * * @return array Returns associated array of `prefix` and `sufix` */ public function settings_fields_description( $args ) { $desc_prefix = ''; $desc_sufix = ''; // desc_required if ( ! empty( $args['desc_required'] ) ) { $desc_prefix .= '[' . esc_html__( 'Required', 'youtube-channel' ) . ']'; } // desc_optional if ( ! empty( $args['desc_optional'] ) ) { $desc_prefix .= sprintf( '[%2$s]', esc_html__( 'You can leave this option empty and set it per widget/shortcode.', 'youtube-channel' ), esc_html__( 'Optional', 'youtube-channel' ) ); } // desc_deprecated if ( ! empty( $args['desc_deprecated'] ) ) { $desc_prefix .= sprintf( '[%2$s]', esc_html__( 'YouTube deprecated this option. You can use it if you know it, but you cannot get it anymore from YouTube.', 'youtube-channel' ), esc_html__( 'Deprecated', 'youtube-channel' ) ); } // desc_global if ( ! empty( $args['desc_global'] ) ) { $desc_prefix .= sprintf( '[%2$s]', esc_html__( 'This option is global only and can`t be changed per widget/shortcode', 'youtube-channel' ), esc_html__( 'Global', 'youtube-channel' ) ); } // desc_link if ( ! empty( $args['desc_link_url'] ) ) { if ( empty( $args['desc_link_txt'] ) ) { $args['desc_link_txt'] = $args['desc_link_url']; } $desc_sufix = sprintf( ' %2$s', esc_url( $args['desc_link_url'] ), esc_html( $args['desc_link_txt'] ) ); } return array( 'prefix' => $desc_prefix, 'sufix' => $desc_sufix, ); } /** * This function provides text inputs for settings fields */ public function settings_field_input_text( $args ) { $desc_ps = $this->settings_fields_description( $args ); printf( '

%4$s

', esc_attr( $args['field'] ), esc_attr( $args['value'] ), esc_attr( $args['class'] ), $desc_ps['prefix'] . ' ' . esc_html( $args['description'] ) . $desc_ps['sufix'] ); } // END public function settings_field_input_text() /** * This function provides number inputs for settings fields */ public function settings_field_input_number( $args ) { printf( '

%6$s

', esc_attr( $args['field'] ), esc_attr( $args['value'] ), intval( $args['min'] ), intval( $args['max'] ), esc_attr( $args['class'] ), esc_html( $args['description'] ) ); } // END public function settings_field_input_text() /** * This function provides select for settings fields */ public function settings_field_select( $args ) { $options = ''; foreach ( $args['items'] as $key => $val ) { $options .= sprintf( '', esc_attr( $key ), esc_html( $val ), $args['value'] === $key ? 'selected="selected"' : '' ); } printf( '

%3$s

', esc_attr( $args['field'] ), $options, esc_html( $args['description'] ) ); } // END public function settings_field_select() /** * This function provides checkbox for settings fields */ public function settings_field_checkbox( $args ) { $desc_ps = $this->settings_fields_description( $args ); printf( '

%5$s

', esc_attr( $args['field'] ), esc_attr( $args['class'] ), true === boolval( $args['value'] ) ? 'checked=checked' : '', isset( $args['label'] ) ? esc_html( $args['label'] ) : '', $desc_ps['prefix'] . esc_html( $args['description'] ) . $desc_ps['sufix'] ); } // END public function settings_field_checkbox() /** * Menu Callback */ public function plugin_settings_page() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions to access this page.' ), esc_html( YTC_PLUGIN_NAME ) ); } // Render the settings template require_once YTC_DIR_TEMPLATES . '/settings.php'; } // END public function plugin_settings_page() /** * process options before update * */ public function sanitize_options( $options ) { $sanitized = get_option( $this->option_name ); // If there is no POST option_page keyword, return initial plugin options if ( empty( $_POST['option_page'] ) ) { return $sanitized; } switch ( $_POST['option_page'] ) { // --- General --- case 'ytc_general': $sanitized['apikey'] = ! empty( $options['apikey'] ) ? ytc_sanitize_api_key( $options['apikey'] ) : ''; // string key $sanitized['channel'] = ! empty( $options['channel'] ) ? ytc_sanitize_api_key( $options['channel'] ) : ''; // string key $sanitized['handle'] = ! empty( $options['handle'] ) ? sanitize_user( $options['handle'], true ) : ''; // string $sanitized['vanity'] = ! empty( $options['vanity'] ) ? sanitize_user( $options['vanity'], true ) : ''; // string $sanitized['username'] = ! empty( $options['username'] ) ? sanitize_user( $options['username'], true ) : ''; // string $sanitized['playlist'] = ! empty( $options['playlist'] ) ? ytc_sanitize_api_key( $options['playlist'] ) : ''; // string key $sanitized['resource'] = isset( $options['resource'] ) ? intval( $options['resource'] ) : intval( $this->defaults['resource'] ); // int $sanitized['cache'] = isset( $options['cache'] ) ? intval( $options['cache'] ) : intval( $this->defaults['cache'] ); // int $sanitized['fetch'] = ! empty( $options['fetch'] ) ? intval( $options['fetch'] ) : intval( $this->defaults['fetch'] ); // int $sanitized['num'] = ! empty( $options['num'] ) ? intval( $options['num'] ) : intval( $this->defaults['num'] ); // int $sanitized['privacy'] = ! empty( $options['privacy'] ) && $options['privacy'] ? 1 : 0; // bool $sanitized['tinymce'] = ! empty( $options['tinymce'] ) && $options['tinymce'] ? 1 : 0; // bool $sanitized['sslverify'] = ! empty( $options['sslverify'] ) && $options['sslverify'] ? 1 : 0; // bool $sanitized['local_img'] = ! empty( $options['local_img'] ) && $options['local_img'] ? 1 : 0; // bool $sanitized['js_ev_listener'] = ! empty( $options['js_ev_listener'] ) && $options['js_ev_listener'] ? 1 : 0; // bool $sanitized['timeout'] = ! empty( $options['timeout'] ) ? intval( $options['timeout'] ) : intval( $this->defaults['timeout'] ); // int $sanitized['block_preview'] = ! empty( $options['block_preview'] ) && $options['block_preview'] ? 1 : 0; // bool break; // General // --- Video --- case 'ytc_video': $sanitized['width'] = ( ! empty( $options['width'] ) ) ? intval( $options['width'] ) : intval( $this->defaults['width'] ); // int $sanitized['ratio'] = ( isset( $options['ratio'] ) ) ? intval( $options['ratio'] ) : intval( $this->defaults['ratio'] ); // int $sanitized['display'] = ( ! empty( $options['display'] ) ) ? sanitize_key( $options['display'] ) : sanitize_key( $this->defaults['display'] ); // string $sanitized['thumb_quality'] = ( ! empty( $options['thumb_quality'] ) ) ? sanitize_key( $options['thumb_quality'] ) : sanitize_key( $this->defaults['thumb_quality'] ); // string $sanitized['responsive'] = ( ! empty( $options['responsive'] ) && $options['responsive'] ) ? 1 : 0; // bool $sanitized['playsinline'] = ( ! empty( $options['playsinline'] ) && $options['playsinline'] ) ? 1 : 0; // bool $sanitized['nolightbox'] = ( ! empty( $options['nolightbox'] ) && $options['nolightbox'] ) ? 1 : 0; // bool $sanitized['fullscreen'] = ( ! empty( $options['fullscreen'] ) && $options['fullscreen'] ) ? 1 : 0; // bool $sanitized['controls'] = ( ! empty( $options['controls'] ) && $options['controls'] ) ? 1 : 0; // bool $sanitized['autoplay'] = ( ! empty( $options['autoplay'] ) && $options['autoplay'] ) ? 1 : 0; // bool $sanitized['autoplay_mute'] = ( ! empty( $options['autoplay_mute'] ) && $options['autoplay_mute'] ) ? 1 : 0; // bool $sanitized['norel'] = ( ! empty( $options['norel'] ) && $options['norel'] ) ? 1 : 0; // bool $sanitized['modestbranding'] = ( ! empty( $options['modestbranding'] ) && $options['modestbranding'] ) ? 1 : 0; // bool $sanitized['hideanno'] = ( ! empty( $options['hideanno'] ) && $options['hideanno'] ) ? 1 : 0; // bool break; // Video // --- Content --- case 'ytc_content': $sanitized['showtitle'] = ! empty( $options['showtitle'] ) && in_array( $options['showtitle'], array( 'none', 'above', 'below', 'inside', 'inside_b' ), true ) ? sanitize_key( $options['showtitle'] ) : sanitize_key( $this->defaults['showtitle'] ); // string $sanitized['linktitle'] = ! empty( $options['linktitle'] ) && $options['linktitle'] ? 1 : 0; // bool $sanitized['titletag'] = ! empty( $options['titletag'] ) && in_array( strtolower( $options['titletag'] ), array( 'h3', 'h4', 'h5', 'div', 'span', 'strong' ), true ) ? sanitize_key( $options['titletag'] ) : sanitize_key( $this->defaults['titletag'] ); // string $sanitized['showdesc'] = ! empty( $options['showdesc'] ) && $options['showdesc'] ? 1 : 0; // bool $sanitized['desclen'] = ! empty( $options['desclen'] ) ? intval( $options['desclen'] ) : intval( $this->defaults['desclen'] ); // integer break; // Content // --- Link to Channel --- case 'ytc_link': $sanitized['link_to'] = isset( $options['link_to'] ) && in_array( $options['link_to'], array( 'none', 'handle', 'vanity', 'channel', 'legacy' ), true ) ? $options['link_to'] : $this->defaults['link_to']; // string $sanitized['goto_txt'] = ! empty( $options['goto_txt'] ) ? sanitize_text_field( $options['goto_txt'], true ) : sanitize_text_field( $this->defaults['goto_txt'] ); // text $sanitized['popup_goto'] = isset( $options['popup_goto'] ) && in_array( intval( $options['popup_goto'] ), array( 0, 1, 2 ), true ) ? intval( $options['popup_goto'] ) : intval( $this->defaults['popup_goto'] ); // integer 0 or 2 (1 is deprecated since 3.23.2) break; // Link to Channel } // switch // --- Update --- // now return sanitized options to be written to database return $sanitized; } // END public function sanitize_options() } // END class Wpau_My_Youtube_Channel_Settings PK!I!class-wpau-my-youtube-channel.phpnu[defaults['tinymce'] ) ) { add_filter( 'mce_external_plugins', array( $this, 'mce_external_plugins' ), 998 ); add_filter( 'mce_buttons', array( $this, 'mce_buttons' ), 999 ); } if ( is_admin() ) { // Initialize Plugin Settings Magic add_action( 'init', array( $this, 'admin_init' ) ); // Add various Dashboard notices (if needed) add_action( 'admin_notices', array( $this, 'admin_notices' ) ); // Enqueue scripts and styles for Widgets page add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) ); } else { // ELSE if ( is_admin() ) // Enqueue frontend scripts add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); add_action( 'wp_footer', array( $this, 'footer_scripts' ), 900 ); } // END if ( is_admin() ) // Load widget require_once 'class-wpau-my-youtube-channel-widget.php'; require_once 'class-wpau-my-youtube-channel-image-handler.php'; // Create an instance of the child class $this->image_handler = new Wpau_My_Youtube_Channel_Image_Handler(); // Register shortcodes `youtube_channel` and `ytc` add_shortcode( 'youtube_channel', array( $this, 'shortcode' ) ); add_shortcode( 'ytc', array( $this, 'shortcode' ) ); } // END public function __construct() /** * Activate the plugin * Credits: http://solislab.com/blog/plugin-activation-checklist/#update-routines */ public static function activate() { global $wpau_my_youtube_channel; $wpau_my_youtube_channel->init_options(); $wpau_my_youtube_channel->maybe_update(); } // END public static function activate() /** * Return initial options * * @return array Global defaults for current plugin version */ public function init_options() { $init = array( 'handle' => '', // [NEW 2023] YouTube Handle https://www.youtube.com/handle 'vanity' => '', // $this->vanity_id, 'channel' => '', // YouTube Channel ID https://www.youtube.com/account_advanced 'username' => '', // YouTube Username ID https://www.youtube.com/account_advanced 'playlist' => '', // $this->playlist_id, 'resource' => 0, // ex use_res 'cache' => 300, // 5 minutes // ex cache_time 'fetch' => 25, // ex maxrnd 'num' => 1, // ex vidqty 'skip' => 0, 'privacy' => 0, 'ratio' => 3, // 3 - 16:9, 1 - 4:3 (deprecated: 2 - 16:10) 'width' => 306, 'responsive' => 1, 'display' => 'thumbnail', // thumbnail, iframe, iframe2, playlist (deprecated: chromeless, object) 'thumb_quality' => 'hqdefault', // default, mqdefault, hqdefault, sddefault, maxresdefault 'fullscreen' => 0, 'controls' => 0, 'autoplay' => 0, 'autoplay_mute' => 0, 'norel' => 0, 'playsinline' => 0, // play video on mobile devices inline instead in native device player 'showtitle' => 'none', // above, below, inside, inside_b 'linktitle' => 0, 'titletag' => 'h3', 'showdesc' => 0, 'desclen' => 0, 'modestbranding' => 0, 'hideanno' => 0, 'goto_txt' => 'Visit our channel', 'popup_goto' => 0, // 0 same window, 1 new window JS, 2 new window target 'link_to' => 'none', // 0 legacy username, 1 channel, 2 vanity 'tinymce' => 1, // show TinyMCE button by default 'nolightbox' => false, // do not use lightbox global setting 'timeout' => 5, // timeout for wp_remote_get() 'sslverify' => true, 'local_img' => false, // [NEW 2024] Store video thumbnails locally 'js_ev_listener' => false, 'block_preview' => true, // [NEW 2023] Enable YTC Widget preview in Block editor ); add_option( 'youtube_channel_version', YTC_VER, '', 'no' ); add_option( 'youtube_channel_db_ver', YTC_VER_DB, '', 'no' ); add_option( YTC_PLUGIN_OPTION_KEY, $init, '', 'no' ); return $init; } // END public function init_options() /** * Check do we need to migrate options */ public function maybe_update() { // bail if this plugin data doesn't need updating if ( get_option( 'youtube_channel_db_ver' ) >= YTC_VER_DB ) { return; } require_once YTC_DIR . '/update.php'; au_youtube_channel_update(); } // END public function maybe_update() /** * Initialize Settings link for Plugins page and create Settings page */ public function admin_init() { // Localize cache options $this->cache_timeouts = array( '0' => __( 'Do not cache', 'youtube-channel' ), '60' => __( '1 minute', 'youtube-channel' ), '300' => __( '5 minutes', 'youtube-channel' ), '900' => __( '15 minutes', 'youtube-channel' ), '1800' => __( '30 minutes', 'youtube-channel' ), '3600' => __( '1 hour', 'youtube-channel' ), '7200' => __( '2 hours', 'youtube-channel' ), '18000' => __( '5 hours', 'youtube-channel' ), '36000' => __( '10 hours', 'youtube-channel' ), '43200' => __( '12 hours', 'youtube-channel' ), '64800' => __( '18 hours', 'youtube-channel' ), '86400' => __( '1 day', 'youtube-channel' ), '172800' => __( '2 days', 'youtube-channel' ), '259200' => __( '3 days', 'youtube-channel' ), '345600' => __( '4 days', 'youtube-channel' ), '432000' => __( '5 days', 'youtube-channel' ), '518400' => __( '6 days', 'youtube-channel' ), '604800' => __( '1 week', 'youtube-channel' ), '1209600' => __( '2 weeks', 'youtube-channel' ), '1814400' => __( '3 weeks', 'youtube-channel' ), '2419200' => __( '1 month', 'youtube-channel' ), ); // Add plugin Setting slink on Plugins page add_filter( 'plugin_action_links_' . plugin_basename( YTC_PLUGIN_FILE ), array( $this, 'add_action_links' ) ); // Add row on Plugins page add_filter( 'plugin_row_meta', array( $this, 'add_plugin_meta_links' ), 10, 2 ); require_once 'class-wpau-my-youtube-channel-settings.php'; global $wpau_my_youtube_channel_settings; if ( empty( $wpau_my_youtube_channel_settings ) ) { $wpau_my_youtube_channel_settings = new Wpau_My_Youtube_Channel_Settings(); } } // END public function admin_init() /** * Append Settings link for Plugins page * * @param array $links Array of default plugin links * * @return array Array of plugin links with appended link for Settings page */ public function add_action_links( $links ) { $settings_link = '' . esc_html__( 'Settings' ) . ''; array_unshift( $links, $settings_link ); return $links; // Return updated array of links } // END public function add_action_links( $links ) /** * Add link to plugin community support * * @param array $links Array of default plugin meta links * @param string $file Current hook file path * * @return array Array of default plugin meta links with appended link for Support community forum */ public function add_plugin_meta_links( $links, $file ) { if ( plugin_basename( YTC_PLUGIN_FILE ) === $file ) { $links[] = '' . esc_html__( 'Support' ) . ''; } // Return updated array of links return $links; } // END public function add_plugin_meta_links( $links, $file ) /** * Enqueue admin scripts and styles for widget customization */ public function admin_scripts() { global $pagenow; // Enqueue only on widget or post pages if ( ! in_array( $pagenow, array( 'widgets.php', 'customize.php', 'options-general.php', 'post.php', 'post-new.php' ), true ) ) { return; } // Enqueue on post page only if tinymce is enabled if ( in_array( $pagenow, array( 'post.php', 'post-new.php' ), true ) && empty( $this->defaults['tinymce'] ) ) { return; } wp_enqueue_style( esc_attr( YTC_PLUGIN_SLUG . '-admin' ), esc_url( YTC_URL . 'assets/css/admin.min.css' ), array(), YTC_VER ); // Enqueue script for widget in admin if ( in_array( $pagenow, array( 'widgets.php', 'customize.php' ), true ) ) { wp_enqueue_script( esc_attr( YTC_PLUGIN_SLUG . '-admin' ), esc_url( YTC_URL . 'assets/js/admin.min.js' ), array( 'jquery' ), YTC_VER, true ); } } // END public function admin_scripts() /** * Print dashboard notice * * @return string Formatted notice with usefull explanation */ public function admin_notices() { // Prepare vars for notices $notice = array( 'error' => '' ); // Inform if PHP version is lower than 7.4 if ( version_compare( PHP_VERSION, '7.4', '<' ) ) { $notice['error'] .= '

' . sprintf( // translators: %1$s stand for PHP version on server, %2$s is plugin name esc_html__( 'Your website running on web server with PHP version %1$s. Please note that %2$s requires PHP 7.4 or newer to work properly.', 'youtube-channel' ), PHP_VERSION, // 1 '' . esc_html( YTC_PLUGIN_NAME ) . '' // 2 ) . '

'; } // No YouTube DATA Api Key? if ( empty( $this->defaults['apikey'] ) ) { $notice['error'] .= '

' . sprintf( /* translators: %1$s is replaced with plugin name, * %2$s with translated label YouTube Data API Key, * %3$s with link to plugin General Settings page * %4$s with link to Google Developers Console, * %5$s with link to Data API Key guide, */ __( '%1$s require %2$s to be set on plugin %3$s page. You can generate your own key on %4$s by following %5$s.', 'youtube-channel' ), '' . esc_html( YTC_PLUGIN_NAME ) . '', // 1 '' . esc_html__( 'YouTube Data API Key', 'youtube-channel' ) . '', // 2 '' . esc_html__( 'General Settings', 'youtube-channel' ) . '', // 3 '' . esc_html__( 'Google Developers Console', 'youtube-channel' ) . '', // 4 '' . esc_html__( 'this tutorial', 'youtube-channel' ) . '' // 5 ) . '

'; } // Now output all prepared notices foreach ( $notice as $type => $message ) { if ( ! empty( $message ) ) { echo '
' . wp_kses( $message, array( 'p' => array(), 'strong' => array(), 'br' => array(), 'a' => array( 'href' => array(), 'target' => array(), ), ) ) . '
'; } } } // END public function admin_notices() /** * Get default options from DB and store it to variable $this->defaults * * @return void */ public function defaults() { $defaults = get_option( YTC_PLUGIN_OPTION_KEY ); if ( empty( $defaults ) ) { $defaults = $this->init_options(); } $this->defaults = $defaults; } // END public function defaults() /** * Enqueue frontend scripts and styles * * @return void */ public function enqueue_scripts() { // Check do we need our own lightbox? if ( empty( $this->defaults['nolightbox'] ) ) { wp_enqueue_style( 'bigger-picture', esc_url( YTC_URL . 'assets/lib/bigger-picture/css/bigger-picture.min.css' ), array(), YTC_VER ); wp_register_script( 'bigger-picture', esc_url( YTC_URL . 'assets/lib/bigger-picture/bigger-picture.min.js' ), array(), YTC_VER, true ); wp_enqueue_script( 'bigger-picture' ); wp_register_script( 'youtube-channel', esc_url( YTC_URL . 'assets/js/youtube-channel.min.js' ), array( 'bigger-picture' ), YTC_VER, true ); wp_enqueue_script( 'youtube-channel' ); } wp_enqueue_style( 'youtube-channel', esc_url( YTC_URL . 'assets/css/youtube-channel.min.css' ), array(), YTC_VER ); } // END public function enqueue_scripts() /** * Generate complete inline JavaScript code that conains * Async video load and lightbox init for thumbnails * * @return void */ public function footer_scripts() { if ( ( ! empty( $this->ytc_html5 ) || empty( $this->defaults['nolightbox'] ) ) ) { echo ''; } } // END public function footer_scripts() /** * Method to render YTC shortcode * * @param array $atts * * @return string */ public function shortcode( $atts ) { // Get general default settings $instance = $this->defaults; // Extract shortcode parameters $atts = shortcode_atts( array( 'handle' => $instance['handle'], 'vanity' => $instance['vanity'], 'channel' => $instance['channel'], 'username' => $instance['username'], 'playlist' => $instance['playlist'], 'res' => '', // (deprecated, but leave for back compatibility) ex res 'use_res' => '', // (deprecated, but leave for back compatibility) ex use_res 'resource' => $instance['resource'], // ex use_res 'only_pl' => 0, // disabled by default (was: $instance['only_pl'],) 'cache' => $instance['cache'], // ex cache_time 'privacy' => $instance['privacy'], // ex showvidesc 'fetch' => $instance['fetch'], // ex maxrnd 'num' => $instance['num'], // ex vidqty 'random' => 0, // ex getrnd 'ratio' => $instance['ratio'], 'width' => $instance['width'], 'responsive' => ! empty( $instance['responsive'] ) ? $instance['responsive'] : '0', 'show' => $instance['display'], // (deprecated, but keep for back compatibility) ex to_show 'display' => $instance['display'], 'thumb_quality' => $instance['thumb_quality'], 'no_thumb_title' => 0, 'controls' => $instance['controls'], 'autoplay' => $instance['autoplay'], 'mute' => $instance['autoplay_mute'], 'norel' => $instance['norel'], 'playsinline' => $instance['playsinline'], // play video on mobile devices inline instead in native device player 'showtitle' => $instance['showtitle'], // none, above, below, inside, inside_b 'linktitle' => ! empty( $instance['linktitle'] ) ? $instance['linktitle'] : '0', 'titletag' => $instance['titletag'], // h3, h4, h5, div, span, strong 'showdesc' => $instance['showdesc'], // ex showvidesc 'nobrand' => ! empty( $instance['modestbranding'] ) ? $instance['modestbranding'] : '0', 'desclen' => $instance['desclen'], // ex videsclen 'noanno' => $instance['hideanno'], 'goto_txt' => $instance['goto_txt'], 'popup' => $instance['popup_goto'], 'link_to' => $instance['link_to'], // none, vanity, channel, legacy 'class' => ! empty( $instance['class'] ) ? $instance['class'] : '', 'target' => '', 'skip' => 0, // how many items to skip ), $atts ); // backward compatibility for show -> display shortcode parameter if ( ! empty( $atts['show'] ) && $atts['show'] !== $atts['display'] && $atts['show'] !== $instance['display'] ) { $atts['display'] = $atts['show']; } // backward compatibility for use_res -> resource shortcode parameter if ( ! empty( $atts['use_res'] ) ) { $atts['resource'] = $atts['use_res']; } elseif ( ! empty( $atts['res'] ) ) { $atts['resource'] = $atts['res']; } // prepare instance for output $instance['handle'] = sanitize_user( $atts['handle'], true ); $instance['vanity'] = sanitize_user( $atts['vanity'], true ); $instance['channel'] = $atts['channel']; $instance['username'] = sanitize_user( $atts['username'], true ); $instance['playlist'] = $atts['playlist']; $instance['resource'] = $atts['resource']; // resource: 0 channel, 2 playlist (1 favorites and 3 liked are deprecated in 2023) $instance['cache'] = $atts['cache']; // in seconds, def 5min - settings? $instance['privacy'] = $atts['privacy']; // enhanced privacy $instance['fetch'] = (int) $atts['fetch']; $instance['num'] = (int) $atts['num']; // num: 1 $instance['random'] = $atts['random']; // use embedded playlist - false by default // Video Settings $instance['ratio'] = $atts['ratio']; // aspect ratio: 3 - 16:9, 2 - 16:10, 1 - 4:3 $instance['width'] = (int) $atts['width']; // 306 $instance['responsive'] = $atts['responsive']; // enable responsivenes? $instance['display'] = $atts['display']; // thumbnail, iframe, iframe2, playlist $instance['thumb_quality'] = $atts['thumb_quality']; // default, mqdefault, hqdefault, sddefault, maxresdefault $instance['no_thumb_title'] = $atts['no_thumb_title']; // hide tooltip for thumbnails $instance['controls'] = $atts['controls']; // hide controls, false by default $instance['autoplay'] = $atts['autoplay']; // autoplay disabled by default $instance['autoplay_mute'] = $atts['mute']; // mute sound on autoplay - disabled by default $instance['norel'] = $atts['norel']; // hide related videos $instance['playsinline'] = $atts['playsinline']; // inline plaer for iOS // Content Layout $instance['showtitle'] = $atts['showtitle']; // show video title, disabled by default $instance['linktitle'] = $atts['linktitle']; // link title to video, disabled by default $instance['titletag'] = $atts['titletag']; // title HTML tag wrapper, h3 by default $instance['showdesc'] = $atts['showdesc']; // show video description, disabled by default $instance['modestbranding'] = $atts['nobrand']; // hide YT logo $instance['desclen'] = (int) $atts['desclen']; // cut video description, number of characters $instance['hideanno'] = $atts['noanno']; // hide annotations, false by default // Link to Channel $instance['goto_txt'] = $atts['goto_txt']; // text for goto link - use settings $instance['popup_goto'] = $atts['popup']; // open channel in: 0 same window, 1 javascript new, 2 target new $instance['link_to'] = $atts['link_to']; // link to: none, vanity, legacy, channel // Customization $instance['class'] = sanitize_html_classes( $atts['class'] ); // custom additional class for container $instance['target'] = $atts['target']; // should use target for thumbnails w/o lightbox (`0` || `1`|`2`) $instance['skip'] = (int) $atts['skip']; return $this->generate_ytc_block( $instance ); } // END public function shortcode() /** * Generate HTML of YTC block * * @param array $instance Widget or Shortcode settings * * @return string HTML content of the YTC block */ public function generate_ytc_block( $instance ) { // Error message if no YouTube Data API Key if ( empty( $this->defaults['apikey'] ) ) { $error_msg = sprintf( // translators: %1$s is replaced with plugin name, %2$s with link to DATA API Key instructions __( '%1$s v3 requires YouTube DATA API Key to work. Learn more here.', 'youtube-channel' ), YTC_PLUGIN_NAME, 'https://urosevic.net/wordpress/plugins/youtube-channel/#youtube_data_api_key' ); return $this->front_debug( $error_msg ); } // 1) Get resource from widget/shortcode // 2) If not set, get global default // 3) if no global, get plugin's default if ( ! isset( $instance['resource'] ) ) { $instance['resource'] = $this->defaults['resource']; } $resource = intval( $instance['resource'] ); if ( empty( $resource ) && 0 !== $resource ) { $resource = intval( $this->defaults['resource'] ); if ( empty( $resource ) ) { $resource = 0; } } // Get Channel or Playlist ID based on requested resource switch ( $resource ) { // Playlist case '2': // 1) Get Playlist from shortcode/widget // 2) If not set, use global default // 3) If no global, throw error if ( ! empty( $instance['playlist'] ) ) { $playlist = ytc_sanitize_api_key( $instance['playlist'] ); } else { $playlist = ytc_sanitize_api_key( $this->defaults['playlist'] ); } // Now check has Playlist ID set or throw error if ( '' === $playlist ) { return $this->front_debug( 'Playlist selected as resource but no Playlist ID provided!' ); } break; // Channel, Favourites, Liked default: /* Channel */ // 1) Get channel from shortcode/widget // 2) If not set, use global default // 3) If no global, throw error if ( ! empty( $instance['channel'] ) ) { $channel = ytc_sanitize_api_key( $instance['channel'] ); } else { $channel = ytc_sanitize_api_key( $this->defaults['channel'] ); } // Now check is Channel ID set or throw error if ( '' === $channel ) { if ( 1 === (int) $resource ) { $resource_name = 'deprecated Favourited videos'; } elseif ( 3 === (int) $resource ) { $resource_name = 'deprecated Liked videos'; } else { $resource_name = 'Channel (User uploads)'; } $error_msg = sprintf( '%s selected as resource but no Channel ID provided!', $resource_name ); return $this->front_debug( $error_msg ); } } // END switch ($resource) /* OK, we have required resource (Playlist or Channel ID), so we can proceed to real job */ // Set custom class and responsive if needed $class = ( ! empty( $instance['class'] ) ) ? sanitize_html_classes( $instance['class'] ) : 'default'; if ( ! empty( $instance['responsive'] ) ) { $class .= ' responsive'; } if ( ! empty( $instance['display'] ) ) { $class .= ' ytc_display_' . esc_attr( $instance['display'] ); } switch ( $resource ) { case 2: // Playlist $resource_name = 'playlist'; $resource_id = $playlist; break; default: // Channel (#1 Favourites (FL) and #3 Liked (LL) are deprecated) $resource_name = 'channel'; $resource_id = preg_replace( '/^UC/', 'UU', $channel ); } // Start output string $output = ''; $output .= '
'; if ( empty( $instance['display'] ) ) { $instance['display'] = $this->defaults['display']; } if ( 'playlist' === $instance['display'] ) { // Insert as Embedded playlist $output .= $this->generate_playlist_embed( $resource_id, $instance ); } else { // Individual videos from channel or playlist (favourites and liked are deprecated) // Get max items for random video $fetch = empty( $instance['fetch'] ) ? $this->defaults['fetch'] : $instance['fetch']; if ( $fetch < 1 ) { $fetch = 10; } elseif ( $fetch > 50 ) { $fetch = 50; } // How many items to skip? $skip = 0; if ( ! empty( $instance['skip'] ) ) { $skip = intval( $instance['skip'] ) > 49 ? 49 : intval( $instance['skip'] ); } // If we have to skip more items than we have in fetch, set skip to $fetch-1 if ( $skip >= $fetch ) { $skip = $fetch - 1; } $resource_key = $resource_id . '_' . $fetch; // Do we need cache? Let we define cache fallback key $cache_key_fallback = sanitize_key( 'ytc_' . md5( $resource_key ) . '_fallback' ); // Do cache magic if ( ! empty( $instance['cache'] ) && $instance['cache'] > 0 ) { // generate feed cache key for caching time $cache_key = sanitize_key( 'ytc_' . md5( $resource_key ) . '_' . $instance['cache'] ); if ( ! empty( $_GET['ytc_force_recache'] ) ) { delete_transient( $cache_key ); } // get/set transient cache $json = get_transient( $cache_key ); if ( false === $json || empty( $json ) ) { // no cached JSON, get new $json = $this->fetch_youtube_feed( $resource_id, $fetch ); // set decoded JSON to transient cache_key set_transient( $cache_key, base64_encode( $json ), $instance['cache'] ); } else { // we already have cached feed JSON, get it encoded $json = base64_decode( $json ); } } else { // just get fresh feed if cache disabled $json = $this->fetch_youtube_feed( $resource_id, $fetch ); } // free some memory unset( $response ); // decode JSON data $json_output = json_decode( $json ); // YTC 3.0.7: Do we need this, still? // if current feed is messed up, try to get it from fallback cache if ( is_wp_error( $json_output ) && ! is_object( $json_output ) && empty( $json_output->items ) ) { // do we have fallback cache?! $json_fallback = get_transient( $cache_key_fallback ); if ( true === $json_fallback && ! empty( $json_fallback ) ) { $json_output = json_decode( base64_decode( $json_fallback ) ); // and free memory unset( $json_fallback ); } } // Get resource nice name based on selected resource $resource_nice_name = $this->get_resource_nice_name( $resource ); // Prevent further checks if we have WP Error or empty record even after fallback if ( is_wp_error( $json_output ) ) { $output .= $this->front_debug( $json_output->get_error_message() ); return $output; } elseif ( isset( $json_output->items ) && 0 === sizeof( $json_output->items ) ) { // translators: %1$s is replaced with resource nice name, %2$s is replaced with Resource ID $output .= $this->front_debug( sprintf( __( 'You have set to display videos from %1$s [resource list ID: %2$s], but there have no public videos in that resouce.' ), $resource_nice_name, $resource_id ) ); return $output; } elseif ( empty( $json_output ) ) { // translators: %1$s is replaced with URL to plugin FAQ page, %2$s with link to official plugin page $output .= $this->front_debug( sprintf( __( 'We have empty record for this feed. Please read FAQ and if that does not help, contact support.' ), 'https://wordpress.org/plugins/youtube-channel#faq', 'https://wordpress.org/support/plugin/youtube-channel/' ) ); return $output; } // Predefine `max_items` to prevent undefined notices $max_items = 0; if ( is_object( $json_output ) && ! empty( $json_output->items ) ) { // Sort by date uploaded $json_entry = $json_output->items; $num = ( empty( $instance['num'] ) ) ? $this->defaults['num'] : $instance['num']; if ( $num > $fetch ) { $fetch = $num; } $max_items = ( $fetch > sizeof( $json_entry ) ) ? sizeof( $json_entry ) : $fetch; if ( ! empty( $instance['random'] ) ) { $items = array_slice( $json_entry, 0, $max_items ); } else { if ( ! $num ) { $num = 1; } $items = array_slice( $json_entry, $skip, $num ); } } if ( 0 === $max_items ) { // Set default error message $error_msg = 'Unrecognized error experienced.'; // Append YouTube DATA API error reason as comment if ( ! empty( $json_output ) && is_object( $json_output ) && ! empty( $json_output->error->errors ) ) { // Error went in fetch_youtube_feed() if ( 'wpError' === $json_output->error->errors[0]->reason ) { $error_msg = $json_output->error->errors[0]->message; } elseif ( 'playlistNotFound' === $json_output->error->errors[0]->reason ) { // Playlist error from Google API if ( 'playlist' === $resource_name ) { $error_msg = "Please check did you set existing Playlist ID. You set to show videos from {$resource_nice_name}, but YouTube does not recognize {$resource_id} as existing and public playlist."; } else { $error_msg = "Please check did you set the proper Channel ID. You set to show videos from {$resource_nice_name}, but YouTube does not recognize {$channel} as an existing or public channel."; } } elseif ( 'keyInvalid' === $json_output->error->errors[0]->reason ) { // Invalid YouTube Data API Key // translators: %s is replaced with link to official plugin Installation page $error_msg = sprintf( __( "Double check YouTube Data API Key on General plugin tab and make sure it's correct. Read Installation document." ), 'https://wordpress.org/plugins/youtube-channel/installation/' ); } elseif ( 'ipRefererBlocked' === $json_output->error->errors[0]->reason ) { // Restricted access YouTube Data API Key $error_msg = 'Check YouTube Data API Key restrictions, empty cache if enabled by appending in the browser address bar parameter ?ytc_force_recache=1'; } elseif ( 'invalidChannelId' === $json_output->error->errors[0]->reason ) { // (deprecated?) Non existing Channel ID set // translators: %s is replaced with link to official plugin FAQ page $error_msg = sprintf( __( 'You have set wrong Channel ID. Fix that in General plugin settings, Widget and/or shortcode. Read FAQ document.' ), 'https://wordpress.org/plugins/youtube-channel/faq/' ); } elseif ( 'playlistItemsNotAccessible' === $json_output->error->errors[0]->reason ) { // Forbidden access to resource // translators: %s is replaced with Resource ID $error_msg = sprintf( __( "You do not have permission to access ressource %s (it's maybe set to private or even does not exists!)" ), $resource_id ); } else { $error_msg = sprintf( 'Reason: %1$s; Domain: %2$s; Message: %3$s', $json_output->error->errors[0]->reason, $json_output->error->errors[0]->domain, $json_output->error->errors[0]->message ); } } // END ! empty($json_output->error->errors) $output .= $this->front_debug( $error_msg ); } else { // ELSE if ($max_items == 0) // looks that feed is OK, let we update fallback that never expire set_transient( $cache_key_fallback, base64_encode( $json ), 0 ); // and now free some memory unset( $json, $json_output, $json_entry ); // set array for unique random item if ( ! empty( $instance['random'] ) ) { $random_used = array(); } /* AU:20141230 reduce number of videos if requested > available */ if ( $num > sizeof( $items ) ) { $num = sizeof( $items ); } $rand_fn = function_exists( 'random_int' ) ? 'random_int' : 'mt_rand'; for ( $y = 1; $y <= $num; ++$y ) { if ( ! empty( $instance['random'] ) ) { $random_item = $rand_fn( 0, ( count( $items ) - 1 ) ); while ( $y > 1 && in_array( $random_item, $random_used, true ) ) { $random_item = $rand_fn( 0, ( count( $items ) - 1 ) ); } $random_used[] = $random_item; $item = $items[ $random_item ]; } else { $item = $items[ $y - 1 ]; } // Generate single video block $video_block = $this->generate_video_block( $item, $instance, $y ); // Allow plugins/themes to override the default video block template. $video_block = apply_filters( 'ytc_print_video', $video_block, $item, $instance, $y ); // Append video block to final output $output .= $video_block; } // Free some memory unset( $random_used, $random_item, $json ); } // END if ($max_items == 0) } // single playlist or ytc way // Append link to channel on bootom of the widget $output .= $this->get_footer( $instance ); $output .= '
'; // fix overflow on crappy themes $output .= '
'; return wp_kses( $output, array( 'p' => array( 'class' => array(), ), 'div' => array( 'id' => array(), 'class' => array(), 'style' => array(), ), 'a' => array( 'href' => array(), 'target' => array(), 'title' => array(), 'class' => array(), 'data-iframe' => array(), 'data-title' => array(), ), 'h3' => array( 'id' => array(), 'class' => array(), 'title' => array(), 'style' => array(), ), 'h4' => array( 'id' => array(), 'class' => array(), 'title' => array(), 'style' => array(), ), 'h5' => array( 'id' => array(), 'class' => array(), 'title' => array(), 'style' => array(), ), 'strong' => array( 'class' => array(), ), 'span' => array( 'id' => array(), 'class' => array(), 'title' => array(), 'style' => array(), ), 'iframe' => array( 'id' => array(), 'src' => array(), 'title' => array(), 'width' => array(), 'height' => array(), 'style' => array(), 'allowfullscreen' => array(), ), ), ); } // END public function generate_ytc_block( $instance ) // --- HELPER FUNCTIONS --- /** * Download YouTube video feed through API 3.0 * @param string $id ID of resource * @param integer $items Number of items to fetch (min 2, max 50) * @return string JSON with videos */ private function fetch_youtube_feed( $resource_id, $items ) { $feed_url = 'https://www.googleapis.com/youtube/v3/playlistItems?'; $feed_url .= 'part=snippet'; $feed_url .= '&playlistId=' . ytc_sanitize_api_key( $resource_id ); $feed_url .= '&fields=items(snippet(title%2Cdescription%2CpublishedAt%2CresourceId(videoId)))'; $feed_url .= '&maxResults=' . intval( $items ); $feed_url .= '&key=' . ytc_sanitize_api_key( $this->defaults['apikey'] ); $wparg = array( 'timeout' => intval( $this->defaults['timeout'] ), 'sslverify' => $this->defaults['sslverify'] ? true : false, 'headers' => array( 'referer' => site_url() ), ); $response = wp_remote_get( $feed_url, $wparg ); // If we have WP error, make JSON with error if ( is_wp_error( $response ) ) { $json = sprintf( '{"error":{"errors":[{"reason":"wpError","message":"%s","domain":"wpRemoteGet"}]}}', $response->get_error_message() ); } else { $json = wp_remote_retrieve_body( $response ); } // Free some memory unset( $response ); return $json; } // END private function fetch_youtube_feed($resource_id, $items) /** * Print explanation of error for administrators (users with capability manage_options) * and hidden message for lower users and visitors * @param string $message Error message * @return string Formatted message for error */ private function front_debug( $message ) { // Show visible error to admin, Oops message to visitors and lower members if ( is_user_logged_in() && current_user_can( 'manage_options' ) ) { $output = "

YTC ERROR: $message

"; } else { $output = __( 'Oops, something went wrong.', 'youtube-channel' ); } return wp_kses( $output, array( 'strong' => array(), 'em' => array(), 'p' => array( 'class' => array(), ), 'a' => array( 'href' => array(), 'target' => array(), ), ), ); } // END private function debug( $message ) /** * Calculate height by provided width and aspect ratio * * @param integer $width Width in pixels * @param integer $ratio Selected aspect ratio (1 for 4:3, other for 16:9) * * @return integer Calculated height in pixels */ private function calculate_height( $width = 306, $ratio = 2 ) { switch ( $ratio ) { case 1: $height = round( ( intval( $width ) / 4 ) * 3 ); break; default: $height = round( ( intval( $width ) / 16 ) * 9 ); } return intval( $height ); } // END function calculate_height( $width = 306, $ratio ) /** * Generate link to YouTube channel/user * * @param array $instance Widget or shortcode settings * * @return string Generated HTML for block footer */ private function get_footer( $instance ) { // Get link to channel $link_to_channel = $this->get_link_to_channel( $instance ); // Wrap content, if we have it if ( ! empty( $link_to_channel ) ) { return '
'; } return; } // END function get_footer( $instance ) /** * Generate link to YouTube channel/user * * @param array $instance Widget or shortcode settings * * @return string Prepared link to channel HTML code */ private function get_link_to_channel( $instance ) { // Do we need to show goto link? if ( empty( $instance['link_to'] ) || 'none' === $instance['link_to'] ) { return; } // Initialize output string $output = ''; $goto_url = 'https://www.youtube.com/'; $handle = ! empty( $instance['handle'] ) ? $instance['handle'] : $this->defaults['handle']; $vanity = ! empty( $instance['vanity'] ) ? $instance['vanity'] : $this->defaults['vanity']; $username = ! empty( $instance['username'] ) ? $instance['username'] : $this->defaults['username']; $channel = ! empty( $instance['channel'] ) ? $instance['channel'] : $this->defaults['channel']; switch ( $instance['link_to'] ) { case 'handle': if ( empty( $handle ) ) { return ''; } // sanity handle content (strip all in front of last slash to cleanup handle ID only) if ( false !== strpos( $handle, 'youtube.com' ) ) { $handle = preg_replace( '/^.*\//', '', $handle ); } $goto_url .= $handle; break; case 'channel': if ( empty( $channel ) ) { return ''; } $goto_url .= "channel/$channel"; break; case 'vanity': // deprecated if ( empty( $vanity ) ) { return ''; } // sanity vanity content (strip all in front of last slash to cleanup vanity ID only) if ( false !== strpos( $vanity, 'youtube.com' ) ) { $vanity = preg_replace( '/^.*\//', '', $vanity ); } $goto_url .= "c/$vanity"; break; case 'legacy': // deprecated if ( empty( $username ) ) { return ''; } $goto_url .= "user/$username"; break; } $goto_txt = trim( $instance['goto_txt'] ); if ( empty( $goto_txt ) ) { $goto_txt = __( 'Visit our YouTube channel', 'youtube-channel' ); } // replace placeholders $goto_txt = str_replace( '%handle%', $handle, $goto_txt ); $goto_txt = str_replace( '%channel%', $channel, $goto_txt ); $goto_txt = str_replace( '%vanity%', $vanity, $goto_txt ); // deprecated $goto_txt = str_replace( '%user%', $username, $goto_txt ); // deprecated $newtab = __( 'in new window/tab', 'youtube-channel' ); switch ( $instance['popup_goto'] ) { case 1: // JavaScript is deprecated in 3.24.0 case 2: $output .= '' . esc_html( $goto_txt ) . ''; break; default: $output .= '' . esc_html( $goto_txt ) . ''; } // switch popup_goto return $output; } // END private function get_link_to_channel( $instance ) /** * Generate HTML output for single video block * * @param object $item Video object from JSON * @param array $instance Settings from widget or shortcode * @param int $y Order number of video * * @return string Prepared single video block as HTML */ private function generate_video_block( $item, $instance, $y ) { // Start output string $output = ''; // Calculate width and height if ( empty( $instance['ratio'] ) ) { $instance['ratio'] = $this->defaults['ratio']; } /** * @var integer $width Sanitized width of YouTube video */ $width = ! empty( $instance['width'] ) ? intval( $instance['width'] ) : intval( $this->defaults['width'] ); /** * @var integer $height Sanitized calculated height of YouTube video */ $height = $this->calculate_height( $width, $instance['ratio'] ); // How to display videos? if ( empty( $instance['display'] ) ) { $instance['display'] = 'thumbnail'; } // Extract details about video from Resource /** * @var string $yt_id Sanitized YouTube Video ID */ $yt_id = ytc_sanitize_api_key( $item->snippet->resourceId->videoId ); /** * @var string $yt_title Sanitized Title of YouTube video */ $yt_title = sanitize_text_field( $item->snippet->title ); // $yt_date = $item->snippet->publishedAt; // Enhanced privacy? /** * @var string $youtube_domain YouTube domain without protocol and trailing slash */ $youtube_domain = $this->get_youtube_domain( $instance ); /** * @var string $vnumclass HTML class `first` | `mid` | `last` */ $vnumclass = 'mid'; switch ( $y ) { case 1: $vnumclass = 'first'; break; case intval( $instance['num'] ): // $autoplay = false; $vnumclass = 'last'; break; default: $vnumclass = 'mid'; // $autoplay = false; break; } /** * Aspect ratio class * * @todo deprecate and always use ar16_9 * * @var string $arclass HTML class for aspect ratio ar4_3 | ar16_9 */ $arclass = $this->get_ar_class( $instance ); /** * @var string $title_html_tag HTML tag for title (eg. `h3`, `div`, `span`, `strong`, etc) */ $title_html_tag = isset( $instance['titletag'] ) ? sanitize_key( $instance['titletag'] ) : sanitize_key( $this->defaults['titletag'] ); $output .= '
'; // Show video title above video? if ( ! empty( $instance['showtitle'] ) ) { if ( // for non-thumbnail for `above` and `inside` ( 'thumbnail' !== $instance['display'] && in_array( $instance['showtitle'], array( 'above', 'inside' ), true ) ) || // for thubmanil only if it's `below` ( 'thumbnail' === $instance['display'] && 'above' === $instance['showtitle'] ) ) { if ( ! empty( $instance['linktitle'] ) ) { $output .= sprintf( '<%1$s class="ytc_title ytc_title_above">%2$s', $title_html_tag, esc_html( $yt_title ), $yt_id ); } else { $output .= sprintf( '<%1$s class="ytc_title ytc_title_above">%2$s', $title_html_tag, esc_html( $yt_title ), ); } } } // Print out video if ( 'iframe' === $instance['display'] ) { // Start wrapper for responsive item if ( $instance['responsive'] ) { $output .= '
'; } $output .= ''; // Close wrapper for responsive item if ( $instance['responsive'] ) { $output .= '
'; } } elseif ( 'iframe2' === $instance['display'] ) { // Start wrapper for responsive item if ( $instance['responsive'] ) { $output .= '
'; } /** * @var string $js_player_id Unique YTC item ID */ $js_player_id = count( $this->ytc_html5 ) . '_' . str_replace( '-', '_', $yt_id ); // Inject YT.player placeholder $output .= '
'; // Close wrapper for responsive item if ( $instance['responsive'] ) { $output .= '
'; } /** * Prepare YT.player settings object for single video item */ $ytc_player = array( 'height' => $height, 'width' => $width, 'videoId' => $yt_id, ); if ( ! empty( $instance['norel'] ) ) { $ytc_player['playerVars']['rel'] = 0; } if ( ! empty( $instance['controls'] ) ) { $ytc_player['playerVars']['controls'] = 0; } if ( ! empty( $instance['modestbranding'] ) ) { $ytc_player['playerVars']['modestbranding'] = 1; } if ( ! empty( $instance['playsinline'] ) ) { $ytc_player['playerVars']['playsinline'] = 1; } $ytc_player['playerVars']['wmmode'] = 'opaque'; if ( ! empty( $instance['hideanno'] ) ) { $ytc_player['iv_load_policy'] = 3; } // Autoplay may not work https://developer.chrome.com/blog/autoplay/ if ( ! empty( $instance['autoplay'] ) && 1 === $y && empty( $this->ytc_html5 ) ) { $ytc_player['playerVars']['autoplay'] = 1; if ( ! empty( $instance['autoplay_mute'] ) ) { $ytc_player['events'] = array( 'onReady' => 'ytc_playmute', ); } else { $ytc_player['events'] = array( 'onReady' => 'ytc_play', ); } } $ytc_player['origin'] = sanitize_url( $_SERVER['HTTP_HOST'] ); // Append YT.player object to site-wide ytc_html5 players $this->ytc_html5[ $js_player_id ] = $ytc_player; } else { // default is thumbnail /** * Target atribute for thumbnail anchor * * @var string $target Empty if lightbox is used or `target="youtube"` tag attribute */ $target = ''; /** * URL query with YouTube Video parameters * * @var string $http_query Empty or safe URL parameters */ $http_query = ''; if ( empty( $this->defaults['nolightbox'] ) ) { $params = array(); if ( ! empty( $instance['norel'] ) ) { $params['rel'] = 0; } if ( ! empty( $instance['modestbranding'] ) ) { $params['modestbranding'] = 1; } if ( ! empty( $instance['controls'] ) ) { $params['controls'] = 0; } if ( ! empty( $instance['playsinline'] ) ) { $params['playsinline'] = 1; } if ( ! empty( $instance['privacy'] ) ) { $params['enhanceprivacy'] = 1; } if ( ! empty( $instance['autoplay'] ) ) { $params['autoplay'] = 1; } $http_query = http_build_query( $params ); $lightbox_class = 'ytc-lightbox'; } else { $lightbox_class = 'ytc-nolightbox'; if ( ! empty( $instance['popup_goto'] ) ) { $target = 'target="youtube"'; } } /** * Title parameter for anchor tag * * @var string $tag_title Empty or `title="YouTube Video Sanitized Title"` tag attribute */ $tag_title = empty( $instance['no_thumb_title'] ) ? 'title="' . esc_html( $yt_title ) . '"' : ''; // Define video thumbnail if ( empty( $instance['thumb_quality'] ) || ! in_array( $instance['thumb_quality'], array( 'default', 'mqdefault', 'hqdefault', 'sddefault', 'maxresdefault' ), true ) ) { $instance['thumb_quality'] = 'hqdefault'; } /** * @var string $yt_thumb Sanitized URL to video thumbnail */ $yt_thumb = $this->image_handler->get_youtube_image_url( $yt_id, esc_attr( $instance['thumb_quality'] ) ); // $yt_thumb = 'https://img.youtube.com/vi/' . $yt_id . '/' . esc_attr( $instance['thumb_quality'] ) . '.jpg'; // Show video title inside video? /** * HTML code of video title inside the thumbnail * * @var string $title_inside_html HTML element for video title inside thumbnail */ $title_inside_html = ''; if ( ! empty( $instance['showtitle'] ) && in_array( $instance['showtitle'], array( 'inside', 'inside_b' ), true ) ) { $title_inside_html = sprintf( '<%1$s class="ytc_title ytc_title_inside %3$s">%2$s', $title_html_tag, esc_html( $yt_title ), 'inside_b' === $instance['showtitle'] ? 'ytc_title_inside_bottom' : '' ); } $output .= sprintf( '%7$s', $yt_id, // 1 $http_query, //2 $tag_title, // 3 $lightbox_class . ' ' . $arclass, // 4 $target, // 5 $yt_thumb, // 6 $title_inside_html, // 7 $youtube_domain, // 8 esc_html( $yt_title ) ); } // what to show conditions // Show video title below video? if ( ! empty( $instance['showtitle'] ) ) { if ( // for non-thumbnail for `below` and `inside_b` ( 'thumbnail' !== $instance['display'] && in_array( $instance['showtitle'], array( 'below', 'inside_b' ), true ) ) || // for thubmanil only if it's `below` ( 'thumbnail' === $instance['display'] && 'below' === $instance['showtitle'] ) ) { if ( ! empty( $instance['linktitle'] ) ) { $output .= sprintf( '<%1$s class="ytc_title ytc_title_below">%2$s', $title_html_tag, esc_html( $yt_title ), $yt_id ); } else { $output .= sprintf( '<%1$s class="ytc_title ytc_title_below">%2$s', $title_html_tag, esc_html( $yt_title ) ); } } } // do we need to show video description? if ( ! empty( $instance['showdesc'] ) ) { /** * YouTube Video description * * @todo If description should not be shortened, print HTML formatted desc * * @var string $video_description Raw HTML of YouTube video description */ $video_description = $item->snippet->description; if ( $instance['desclen'] > 0 ) { if ( function_exists( 'mb_strlen' ) && function_exists( 'mb_substr' ) ) { if ( mb_strlen( $video_description ) > $instance['desclen'] ) { $video_description = mb_substr( $video_description, 0, $instance['desclen'] ) . '…'; } } else { if ( strlen( $video_description ) > $instance['desclen'] ) { $video_description = substr( $video_description, 0, $instance['desclen'] ) . '…'; } } } if ( ! empty( $video_description ) ) { $output .= '

' . esc_html( $video_description ) . '

'; } } $output .= '
'; return $output; } // END private function generate_video_block( $item, $instance, $y ) /** * Function to print standard playlist embed code * * @param string $resource_id YouTube Channel or Playlist ID * @param object $instance Settings from widget or shortcode * * @return string Prepared HTML embed code */ private function generate_playlist_embed( $resource_id, $instance ) { $width = empty( $instance['width'] ) ? 306 : intval( $instance['width'] ); $height = $this->calculate_height( $width, $instance['ratio'] ); $autoplay = empty( $instance['autoplay'] ) ? '' : '&autoplay=1'; $modestbranding = empty( $instance['modestbranding'] ) ? '' : '&modestbranding=1'; $rel = empty( $instance['norel'] ) ? '' : '&rel=0'; $playsinline = empty( $instance['playsinline'] ) ? '' : '&playsinline=1'; // enhanced privacy $youtube_domain = $this->get_youtube_domain( $instance ); $arclass = $this->get_ar_class( $instance ); // Start output string $output = ''; $output .= '
'; $output .= '
'; $output .= ''; $output .= '
'; $output .= '
'; return $output; } // END private function generate_playlist_embed( $resource_id, $instance ) /** * Method to delete all YTC transient caches * @return string Report message about success or failed purge cache */ public function clear_all_cache() { if ( ! current_user_can( 'activate_plugins' ) || ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'action-ytc_clear_all_cache' ) ) ) { echo 'Oops, insufficient permissions to clear My YouTube Channel cache.'; wp_die(); } global $wpdb; $ret = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->options WHERE option_name LIKE %s OR option_name LIKE %s ", '_transient_timeout_ytc_%', '_transient_ytc_%' ) ); if ( false === $ret ) { echo 'Oops, we did not cleared any My YouTube Channel cache because some error occured'; } else { if ( 0 === $ret ) { echo 'Congratulations! You can chill, there is no My YouTube Channel caches.'; } else { echo "Success! We cleared $ret row/s with My YouTube Channel caches."; } } wp_die(); } // END public function clear_all_cache() /** * Return nice name for resource by provided resource ID * * @param integer $resource_id Resource ID * * @return string Resource nice name */ private function get_resource_nice_name( $resource_id ) { if ( 0 === (int) $resource_id ) { $resource_nice_name = 'Channel (User uploads)'; } elseif ( 1 === (int) $resource_id ) { $resource_nice_name = 'Favourited videos'; } elseif ( 2 === (int) $resource_id ) { $resource_nice_name = 'Liked videos'; } elseif ( 3 === (int) $resource_id ) { $resource_nice_name = 'Liked videos'; } else { $resource_nice_name = 'Unknown resource'; } return $resource_nice_name; } // END private function get_resource_nice_name( $resource_id ) /** * Define YouTube domain with or without nocookie sufix * * @param array $instance YTC object * * @return string YouTube domain without protocol and trailing slash */ private function get_youtube_domain( $instance ) { return empty( $instance['privacy'] ) ? 'www.youtube.com' : 'www.youtube-nocookie.com'; } // END private function get_youtube_domain /** * Define HTML class for video aspect ratio * * @param array $instance Settings from widget or shortcode * * @return string HTML class for aspect ratio ar4_3 | ar16_9 */ private function get_ar_class( $instance ) { return ! empty( $instance['ratio'] ) && 1 === (int) $instance['ratio'] ? 'ar4_3' : 'ar16_9'; } // END private function arclass() /** * Register TinyMCE button for YTC * * @param array $plugins Unmodified set of plugins * * @return array Set of TinyMCE plugins with YTC addition */ public function mce_external_plugins( $plugins ) { $plugins['youtube_channel'] = esc_url( YTC_URL . 'assets/js/tinymce/plugin.min.js' ); return $plugins; } // END public function mce_external_plugins() /** * Append TinyMCE button for YTC at the end of row 1 * * @param array $buttons Unmodified set of buttons * * @return array Set of TinyMCE buttons with YTC addition */ public function mce_buttons( $buttons ) { $buttons[] = 'youtube_channel_shortcode'; return $buttons; } // END public function mce_buttons() } // End class Wpau_My_Youtube_Channel PK!Ka/class-wpau-my-youtube-channel-image-handler.phpnu[defaults = get_option( YTC_PLUGIN_OPTION_KEY ); $upload_dir_info = wp_upload_dir(); $this->upload_dir = $upload_dir_info['basedir'] . '/my-youtube-channel/'; // Ensure the directory exists if ( ! file_exists( $this->upload_dir ) ) { wp_mkdir_p( $this->upload_dir ); } } public function get_youtube_image_url( $video_id, $video_quality ) { // Build remote image URL $remote_url = 'https://img.youtube.com/vi/' . $video_id . '/' . $video_quality . '.jpg'; // If we don't need to use locally stored images, return remote URL if ( true !== (bool) $this->defaults['local_img'] ) { return $remote_url; } // Prepare parameters for local storage $video_file_name = "yt-$video_id-$video_quality.jpg"; $local_file_path = $this->upload_dir . $video_file_name; // Check if the file exists locally if ( ! file_exists( $local_file_path ) ) { // Download the image from YouTube $this->download_image( $remote_url, $local_file_path ); } // Return the URL of the locally stored image $upload_dir_info = wp_upload_dir(); return $upload_dir_info['baseurl'] . '/my-youtube-channel/' . $video_file_name; } private function download_image( $remote_url, $local_file_path ) { $image_data = file_get_contents( $remote_url ); if ( false === $image_data ) { throw new Exception( "Failed to download image from $remote_url" ); } file_put_contents( $local_file_path, $image_data ); } } // Usage example // $wpau_my_youtube_channel_image_handler = new Wpau_My_Youtube_Image_Handler(); // $image_url = $wpau_my_youtube_channel_image_handler->get_youtube_image_url( 'VIDEO_ID', 'maxresdefault'); PK!Q8ͺclass-render.phpnu[ */ protected array $table = array(); /** * Table options that influence the output result. * * @since 1.0.0 * @var array */ protected array $render_options = array(); /** * Rendered HTML code of the table or PHP array. * * @since 1.0.0 * @var string|array> */ protected $output; /** * Trigger words for colspan, rowspan, or the combination of both. * * @since 1.0.0 * @var array */ protected array $span_trigger = array( 'colspan' => '#colspan#', 'rowspan' => '#rowspan#', 'span' => '#span#', ); /** * Buffer to store the counts of rowspan per column, initialized in _render_table(). * * @since 1.0.0 * @var int[] */ protected array $rowspan = array(); /** * Buffer to store the counts of colspan per row, initialized in _render_table(). * * @since 1.0.0 * @var int[] */ protected array $colspan = array(); /** * Whether the table has connected cells (colspan or rowspan), set in _render_table(). * * @since 3.0.0 */ protected bool $tbody_has_connected_cells = false; /** * Index of the last row of the visible data in the table, set in _render_table(). * * @since 1.0.0 */ protected int $last_row_idx; /** * Index of the last column of the visible data in the table, set in _render_table(). * * @since 1.0.0 */ protected int $last_column_idx; /** * Class constructor. * * @since 1.0.0 */ public function __construct() { // Unused. } /** * Set the table (data, options, visibility, ...) that is to be rendered. * * @since 1.0.0 * * @param array $table Table to be rendered. * @param array $render_options Options for rendering, from both "Edit" screen and Shortcode. */ public function set_input( array $table, array $render_options ): void { $this->table = $table; $this->render_options = $render_options; /** * Filters the table before the render process. * * @since 1.0.0 * * @param array $table The table. * @param array $render_options The render options for the table. */ $this->table = apply_filters( 'tablepress_table_raw_render_data', $this->table, $this->render_options ); } /** * Process the table rendering and return the HTML output. * * @since 1.0.0 * @since 2.0.0 Add the $format parameter. * * @param string $format Optional. Output format, 'html' (default) or 'array'. * @return string|array> HTML code of the rendered table, or a PHP array, or an error message. */ public function get_output( string $format = 'html' ) /* : string|array */ { // Evaluate math expressions/formulas. $this->_evaluate_table_data(); // Remove hidden rows and columns. $this->_prepare_render_data(); if ( 'html' !== $format ) { add_filter( 'tablepress_cell_content', 'wptexturize' ); } // Evaluate Shortcodes and escape cell content. $this->_process_render_data(); if ( 'html' !== $format ) { remove_filter( 'tablepress_cell_content', 'wptexturize' ); } switch ( $format ) { case 'html': $this->_render_table(); break; case 'array': $this->output = $this->table['data']; break; } return $this->output; } /** * Loop through the table to evaluate math expressions/formulas. * * @since 1.0.0 */ protected function _evaluate_table_data(): void { $orig_table = $this->table; if ( $this->render_options['evaluate_formulas'] ) { $formula_evaluator = TablePress::load_class( 'TablePress_Evaluate', 'class-evaluate.php', 'classes' ); $this->table['data'] = $formula_evaluator->evaluate_table_data( $this->table['data'], $this->table['id'] ); } /** * Filters the table after evaluating formulas in the table. * * @since 1.0.0 * * @param array $table The table with evaluated formulas. * @param array $orig_table The table with unevaluated formulas. * @param array $render_options The render options for the table. */ $this->table = apply_filters( 'tablepress_table_evaluate_data', $this->table, $orig_table, $this->render_options ); } /** * Remove all cells from the data set that shall not be rendered, because they are hidden. * * @since 1.0.0 */ protected function _prepare_render_data(): void { $orig_table = $this->table; $num_rows = count( $this->table['data'] ); $num_columns = ( $num_rows > 0 ) ? count( $this->table['data'][0] ) : 0; // Evaluate show/hide_rows/columns parameters. $actions = array( 'show', 'hide' ); $elements = array( 'rows', 'columns' ); foreach ( $actions as $action ) { foreach ( $elements as $element ) { if ( empty( $this->render_options[ "{$action}_{$element}" ] ) ) { $this->render_options[ "{$action}_{$element}" ] = array(); continue; } // Add all rows/columns to array if "all" value set for one of the four parameters. if ( 'all' === $this->render_options[ "{$action}_{$element}" ] ) { $this->render_options[ "{$action}_{$element}" ] = range( 0, ${'num_' . $element} - 1 ); continue; } // We have a list of rows/columns (possibly with ranges in it). $this->render_options[ "{$action}_{$element}" ] = explode( ',', $this->render_options[ "{$action}_{$element}" ] ); // Support for ranges like 3-6 or A-BA. $range_cells = array(); foreach ( $this->render_options[ "{$action}_{$element}" ] as $key => $value ) { $range_dash = strpos( $value, '-' ); if ( false !== $range_dash ) { unset( $this->render_options[ "{$action}_{$element}" ][ $key ] ); $start = trim( substr( $value, 0, $range_dash ) ); if ( ! is_numeric( $start ) ) { $start = TablePress::letter_to_number( $start ); } $end = trim( substr( $value, $range_dash + 1 ) ); if ( ! is_numeric( $end ) ) { $end = TablePress::letter_to_number( $end ); } $current_range = range( $start, $end ); $range_cells = array_merge( $range_cells, $current_range ); } } $this->render_options[ "{$action}_{$element}" ] = array_merge( $this->render_options[ "{$action}_{$element}" ], $range_cells ); /* * Parse single letters and change from regular numbering to zero-based numbering, * as rows/columns are indexed from 0 internally, but from 1 externally. */ foreach ( $this->render_options[ "{$action}_{$element}" ] as $key => $value ) { $value = trim( $value ); if ( ! is_numeric( $value ) ) { $value = TablePress::letter_to_number( $value ); } $this->render_options[ "{$action}_{$element}" ][ $key ] = (int) $value - 1; } // Remove duplicate entries and sort the array. $this->render_options[ "{$action}_{$element}" ] = array_unique( $this->render_options[ "{$action}_{$element}" ] ); sort( $this->render_options[ "{$action}_{$element}" ], SORT_NUMERIC ); } } // Load information about hidden rows and columns. // Get indexes of hidden rows (array value of 0). $hidden_rows = array_keys( $this->table['visibility']['rows'], 0, true ); $hidden_rows = array_merge( $hidden_rows, $this->render_options['hide_rows'] ); $hidden_rows = array_diff( $hidden_rows, $this->render_options['show_rows'] ); // Get indexes of hidden columns (array value of 0). $hidden_columns = array_keys( $this->table['visibility']['columns'], 0, true ); $hidden_columns = array_merge( $hidden_columns, $this->render_options['hide_columns'] ); $hidden_columns = array_merge( array_diff( $hidden_columns, $this->render_options['show_columns'] ) ); // Remove hidden rows and re-index. foreach ( $hidden_rows as $row_idx ) { unset( $this->table['data'][ $row_idx ] ); } $this->table['data'] = array_merge( $this->table['data'] ); // Remove hidden columns and re-index. foreach ( $this->table['data'] as $row_idx => $row ) { foreach ( $hidden_columns as $col_idx ) { unset( $row[ $col_idx ] ); } $this->table['data'][ $row_idx ] = array_merge( $row ); } /** * Filters the table after processing the table visibility information. * * @since 1.0.0 * * @param array $table The processed table. * @param array $orig_table The unprocessed table. * @param array $render_options The render options for the table. */ $this->table = apply_filters( 'tablepress_table_render_data', $this->table, $orig_table, $this->render_options ); } /** * Generate the data that is to be rendered. * * @since 2.0.0 */ protected function _process_render_data(): void { $orig_table = $this->table; // Deactivate nl2br() for this render process, if "convert_line_breaks" Shortcode parameter is set to false. if ( ! $this->render_options['convert_line_breaks'] ) { add_filter( 'tablepress_apply_nl2br', '__return_false', 9 ); // Priority 9, so that this filter can easily be overwritten at the default priority. } foreach ( $this->table['data'] as $row_idx => $row ) { foreach ( $row as $col_idx => $cell_content ) { // Print formulas that are escaped with '= (like in Excel) as text. if ( str_starts_with( $cell_content, "'=" ) ) { $cell_content = substr( $cell_content, 1 ); } $cell_content = $this->safe_output( $cell_content ); if ( str_contains( $cell_content, '[' ) ) { $cell_content = do_shortcode( $cell_content ); } /** * Filters the content of a single cell, after formulas have been evaluated, the output has been sanitized, and Shortcodes have been evaluated. * * @since 1.0.0 * * @param string $cell_content The cell content. * @param string $table_id The current table ID. * @param int $row_idx The row number of the cell. * @param int $col_idx The column number of the cell. */ $cell_content = apply_filters( 'tablepress_cell_content', $cell_content, $this->table['id'], $row_idx + 1, $col_idx + 1 ); $this->table['data'][ $row_idx ][ $col_idx ] = $cell_content; } } // Re-instate nl2br() behavior after this render process, if "convert_line_breaks" Shortcode parameter is set to false. if ( ! $this->render_options['convert_line_breaks'] ) { remove_filter( 'tablepress_apply_nl2br', '__return_false', 9 ); // Priority 9, so that this filter can easily be overwritten at the default priority. } /** * Filters the table after processing the table content handling. * * @since 2.0.0 * * @param array $table The processed table. * @param array $orig_table The unprocessed table. * @param array $render_options The render options for the table. */ $this->table = apply_filters( 'tablepress_table_content_render_data', $this->table, $orig_table, $this->render_options ); } /** * Generate the HTML output of the table. * * @since 1.0.0 */ protected function _render_table(): void { $num_rows = count( $this->table['data'] ); $num_columns = ( $num_rows > 0 ) ? count( $this->table['data'][0] ) : 0; // Check if there are rows and columns in the table (might not be the case after removing hidden rows/columns!). if ( 0 === $num_rows || 0 === $num_columns ) { $this->output = sprintf( __( '', 'tablepress' ), $this->table['id'] ); return; } // Counters for spans of rows and columns, init to 1 for each row and column (as that means no span). $this->rowspan = array_fill( 0, $num_columns, 1 ); $this->colspan = array_fill( 0, $num_rows, 1 ); /** * Filters the trigger keywords for "colspan" and "rowspan" * * @since 1.0.0 * * @param array $span_trigger The trigger keywords for combining table cells. * @param string $table_id The current table ID. */ $this->span_trigger = apply_filters( 'tablepress_span_trigger_keywords', $this->span_trigger, $this->table['id'] ); // Explode from string to array. $this->render_options['column_widths'] = ( ! empty( $this->render_options['column_widths'] ) ) ? explode( '|', $this->render_options['column_widths'] ) : array(); // Make array $this->render_options['column_widths'] have $columns entries. $this->render_options['column_widths'] = array_pad( $this->render_options['column_widths'], $num_columns, '' ); $output = ''; if ( $this->render_options['print_name'] ) { /** * Filters the HTML tag that wraps the printed table name. * * @since 1.0.0 * * @param string $tag The HTML tag around the table name. Default h2. * @param string $table_id The current table ID. */ $name_html_tag = apply_filters( 'tablepress_print_name_html_tag', 'h2', $this->table['id'] ); $name_attributes = array(); if ( ! empty( $this->render_options['html_id'] ) ) { $name_attributes['id'] = "{$this->render_options['html_id']}-name"; } /** * Filters the class attribute for the printed table name. * * @since 1.0.0 * @deprecated 1.13.0 Use {@see 'tablepress_table_name_tag_attributes'} instead. * * @param string $class The class attribute for the table name that can be used in CSS code. * @param string $table_id The current table ID. */ $name_attributes['class'] = apply_filters_deprecated( 'tablepress_print_name_css_class', array( "tablepress-table-name tablepress-table-name-id-{$this->table['id']}", $this->table['id'] ), 'TablePress 1.13.0', 'tablepress_table_name_tag_attributes' ); /** * Filters the attributes for the table name (HTML h2 element, by default). * * @since 1.13.0 * * @param array $name_attributes The attributes for the table name element. * @param array $table The current table. * @param array $render_options The render options for the table. */ $name_attributes = apply_filters( 'tablepress_table_name_tag_attributes', $name_attributes, $this->table, $this->render_options ); $name_attributes = $this->_attributes_array_to_string( $name_attributes ); $print_name_html = "<{$name_html_tag}{$name_attributes}>" . $this->safe_output( $this->table['name'] ) . "\n"; } if ( $this->render_options['print_description'] ) { /** * Filters the HTML tag that wraps the printed table description. * * @since 1.0.0 * * @param string $tag The HTML tag around the table description. Default span. * @param string $table_id The current table ID. */ $description_html_tag = apply_filters( 'tablepress_print_description_html_tag', 'span', $this->table['id'] ); $description_attributes = array(); if ( ! empty( $this->render_options['html_id'] ) ) { $description_attributes['id'] = "{$this->render_options['html_id']}-description"; } /** * Filters the class attribute for the printed table description. * * @since 1.0.0 * @deprecated 1.13.0 Use {@see 'tablepress_table_description_tag_attributes'} instead. * * @param string $class The class attribute for the table description that can be used in CSS code. * @param string $table_id The current table ID. */ $description_attributes['class'] = apply_filters_deprecated( 'tablepress_print_description_css_class', array( "tablepress-table-description tablepress-table-description-id-{$this->table['id']}", $this->table['id'] ), 'TablePress 1.13.0', 'tablepress_table_description_tag_attributes' ); /** * Filters the attributes for the table description (HTML span element, by default). * * @since 1.13.0 * * @param array $description_attributes The attributes for the table description element. * @param array $table The current table. * @param array $render_options The render options for the table. */ $description_attributes = apply_filters( 'tablepress_table_description_tag_attributes', $description_attributes, $this->table, $this->render_options ); $description_attributes = $this->_attributes_array_to_string( $description_attributes ); $print_description_html = "<{$description_html_tag}{$description_attributes}>" . $this->safe_output( $this->table['description'] ) . "\n"; } if ( $this->render_options['print_name'] && 'above' === $this->render_options['print_name_position'] ) { $output .= $print_name_html; } if ( $this->render_options['print_description'] && 'above' === $this->render_options['print_description_position'] ) { $output .= $print_description_html; } $thead = array(); $tfoot = array(); $tbody = array(); $this->last_row_idx = $num_rows - 1; $this->last_column_idx = $num_columns - 1; // Loop through rows in reversed order, to search for rowspan trigger keyword. $row_idx = $this->last_row_idx; // Render the table footer rows, if there is at least one extra row. if ( $this->render_options['table_foot'] > 0 && $num_rows >= $this->render_options['table_head'] + $this->render_options['table_foot'] ) { // @phpstan-ignore greaterOrEqual.invalid (`table_head` and `table_foot` are integers.) $last_tbody_idx = $this->last_row_idx - $this->render_options['table_foot']; while ( $row_idx > $last_tbody_idx ) { $tfoot[] = $this->_render_row( $row_idx, 'th' ); --$row_idx; } // Reverse rows because we looped through the rows in reverse order. $tfoot = array_reverse( $tfoot ); } // Render the table body rows. $last_thead_idx = $this->render_options['table_head'] - 1; while ( $row_idx > $last_thead_idx ) { $tbody[] = $this->_render_row( $row_idx, 'td' ); --$row_idx; } // Reverse rows because we looped through the rows in reverse order. $tbody = array_reverse( $tbody ); // Render the table header rows, if rows are left. while ( $row_idx > -1 ) { $thead[] = $this->_render_row( $row_idx, 'th' ); --$row_idx; } // Reverse rows because we looped through the rows in reverse order. $thead = array_reverse( $thead ); // tag. /** * Filters the content for the HTML caption element of the table. * * If the "Edit" link for a table is shown, it is also added to the caption element. * * @since 1.0.0 * * @param string $caption The content for the HTML caption element of the table. Default empty. * @param array $table The current table. */ $caption = apply_filters( 'tablepress_print_caption_text', '', $this->table ); $caption_style = ''; $caption_class = ''; if ( ! empty( $caption ) ) { /** * Filters the class attribute for the HTML caption element of the table. * * @since 1.0.0 * * @param string $class The class attribute for the HTML caption element of the table. * @param string $table_id The current table ID. */ $caption_class = apply_filters( 'tablepress_print_caption_class', "tablepress-table-caption tablepress-table-caption-id-{$this->table['id']}", $this->table['id'] ); $caption_class = ' class="' . $caption_class . '"'; } if ( ! empty( $this->render_options['edit_table_url'] ) ) { if ( empty( $caption ) ) { $caption_style = ' style="caption-side:bottom;text-align:left;border:none;background:none;margin:0;padding:0;"'; } else { $caption .= '
'; } $caption .= '' . __( 'Edit', 'default' ) . ''; } if ( ! empty( $caption ) ) { $caption = "{$caption}\n"; } // tag. $colgroup = ''; /** * Filters whether the HTML colgroup tag shall be added to the table output. * * @since 1.0.0 * * @param bool $print Whether the colgroup element shall be printed. * @param string $table_id The current table ID. */ if ( apply_filters( 'tablepress_print_colgroup_tag', false, $this->table['id'] ) ) { for ( $col_idx = 0; $col_idx < $num_columns; $col_idx++ ) { $attributes = ' class="colgroup-column-' . ( $col_idx + 1 ) . ' "'; /** * Filters the attributes of the HTML col tags in the HTML colgroup tag. * * @since 1.0.0 * * @param string $attributes The attributes in the col element. * @param string $table_id The current table ID. * @param int $col_idx The number of the column. */ $attributes = apply_filters( 'tablepress_colgroup_tag_attributes', $attributes, $this->table['id'], $col_idx + 1 ); $colgroup .= "\t\n"; } } if ( ! empty( $colgroup ) ) { $colgroup = "\n{$colgroup}\n"; } /* * , , and tags. */ if ( ! empty( $thead ) ) { $thead = "\n" . implode( '', $thead ) . "\n"; } else { $thead = ''; } if ( ! empty( $tfoot ) ) { $tfoot = "\n" . implode( '', $tfoot ) . "\n"; } else { $tfoot = ''; } $tbody_classes = array(); if ( $this->render_options['alternating_row_colors'] ) { $tbody_classes[] = 'row-striping'; } if ( $this->render_options['row_hover'] ) { $tbody_classes[] = 'row-hover'; } $tbody_class = implode( ' ', $tbody_classes ); if ( '' !== $tbody_class ) { $tbody_class = ' class="' . esc_attr( $tbody_class ) . '"'; } $tbody = "\n" . implode( '', $tbody ) . "\n"; // Attributes for the table (HTML table element). $table_attributes = array(); // "id" attribute. if ( ! empty( $this->render_options['html_id'] ) ) { $table_attributes['id'] = $this->render_options['html_id']; } // "class" attribute. $css_classes = array( 'tablepress', "tablepress-id-{$this->table['id']}", $this->render_options['extra_css_classes'], ); if ( $this->tbody_has_connected_cells ) { $css_classes[] = 'tbody-has-connected-cells'; } /** * Filters the CSS classes that are given to the HTML table element. * * @since 1.0.0 * * @param string[] $css_classes The CSS classes for the table element. * @param string $table_id The current table ID. */ $css_classes = apply_filters( 'tablepress_table_css_classes', $css_classes, $this->table['id'] ); // $css_classes might contain several classes in one array entry. $css_classes = explode( ' ', implode( ' ', $css_classes ) ); $css_classes = array_map( array( 'TablePress', 'sanitize_css_class' ), $css_classes ); $css_classes = array_unique( $css_classes ); $css_classes = array_filter( $css_classes ); // Remove empty entries. $css_classes = implode( ' ', $css_classes ); if ( '' !== $css_classes ) { $table_attributes['class'] = $css_classes; } // ARIA label attributes. if ( $this->render_options['print_name'] && ! empty( $this->render_options['html_id'] ) ) { $table_attributes['aria-labelledby'] = "{$this->render_options['html_id']}-name"; } if ( $this->render_options['print_description'] && ! empty( $this->render_options['html_id'] ) ) { $table_attributes['aria-describedby'] = "{$this->render_options['html_id']}-description"; } // "summary" attribute. $summary = ''; /** * Filters the content for the summary attribute of the HTML table element. * * The attribute is only added if it is not empty. * * @since 1.0.0 * * @param string $summary The content for the summary attribute of the table. Default empty. * @param array $table The current table. */ $summary = apply_filters( 'tablepress_print_summary_attr', $summary, $this->table ); if ( ! empty( $summary ) ) { $table_attributes['summary'] = esc_attr( $summary ); } // Legacy support for attributes that are not encouraged in HTML5. foreach ( array( 'cellspacing', 'cellpadding', 'border' ) as $attribute ) { if ( false !== $this->render_options[ $attribute ] ) { $table_attributes[ $attribute ] = (int) $this->render_options[ $attribute ]; } } /** * Filters the attributes for the table (HTML table element). * * @since 1.4.0 * * @param array $table_attributes The attributes for the table element. * @param array $table The current table. * @param array $render_options The render options for the table. */ $table_attributes = apply_filters( 'tablepress_table_tag_attributes', $table_attributes, $this->table, $this->render_options ); $table_attributes = $this->_attributes_array_to_string( $table_attributes ); $table_html = "\n"; $table_html .= $caption . $colgroup . $thead . $tbody . $tfoot; $table_html .= ''; /** * Filters the generated HTML code for the table, without HTML elements around it. * * @since 2.4.0 * * @param string $output The generated HTML for the table, without HTML elements around it. * @param array $table The current table. * @param array $render_options The render options for the table, without HTML elements around it. */ $table_html = apply_filters( 'tablepress_table_html', $table_html, $this->table, $this->render_options ); $output .= "\n{$table_html}\n"; unset( $table_html ); // Unset the potentially large variable to free up memory. // name/description below table (HTML already generated above). if ( $this->render_options['print_name'] && 'below' === $this->render_options['print_name_position'] ) { $output .= $print_name_html; // @phpstan-ignore variable.undefined (The variable is set above.) } if ( $this->render_options['print_description'] && 'below' === $this->render_options['print_description_position'] ) { $output .= $print_description_html; // @phpstan-ignore variable.undefined (The variable is set above.) } /** * Filters the generated HTML code for the table and HTML elements around it. * * @since 1.0.0 * * @param string $output The generated HTML for the table and HTML elements around it. * @param array $table The current table. * @param array $render_options The render options for the table and HTML elements around it. */ $this->output = apply_filters( 'tablepress_table_output', $output, $this->table, $this->render_options ); } /** * Generate the HTML of a row. * * @since 1.0.0 * * @param int $row_idx Index of the row to be rendered. * @param string $tag HTML tag to use for the cells (td or th). * @return string HTML for the row. */ protected function _render_row( int $row_idx, string $tag ): string { $row_cells = array(); // Loop through cells in reversed order, to search for colspan or rowspan trigger words. for ( $col_idx = $this->last_column_idx; $col_idx >= 0; $col_idx-- ) { $cell_content = $this->table['data'][ $row_idx ][ $col_idx ]; if ( $this->span_trigger['rowspan'] === $cell_content ) { // There will be a rowspan. if ( ! ( ( 0 === $row_idx ) // No rowspan inside first row. || ( $this->render_options['table_head'] === $row_idx ) // No rowspan into table head. || ( $this->last_row_idx - $this->render_options['table_foot'] + 1 === $row_idx ) // No rowspan out of table foot. ) ) { // Increase counter for rowspan in this column. ++$this->rowspan[ $col_idx ]; // Reset counter for colspan in this row, combined col- and rowspan might be happening. $this->colspan[ $row_idx ] = 1; continue; } // Invalid rowspan, so we set cell content from #rowspan# to empty. $cell_content = ''; } elseif ( $this->span_trigger['colspan'] === $cell_content ) { // There will be a colspan. if ( ! ( ( 0 === $col_idx ) // No colspan inside first column. || ( 1 === $col_idx && $this->render_options['first_column_th'] ) // No colspan into first column head. ) ) { // Increase counter for colspan in this row. ++$this->colspan[ $row_idx ]; // Reset counter for rowspan in this column, combined col- and rowspan might be happening. $this->rowspan[ $col_idx ] = 1; continue; } // Invalid colspan, so we set cell content from #colspan# to empty. $cell_content = ''; } elseif ( $this->span_trigger['span'] === $cell_content ) { // There will be a combined col- and rowspan. if ( ! ( ( 0 === $row_idx ) // No rowspan inside first row. || ( $this->render_options['table_head'] === $row_idx ) // No rowspan into table head. || ( $this->last_row_idx - $this->render_options['table_foot'] + 1 === $row_idx ) // No rowspan out of table foot. ) && ! ( ( 0 === $col_idx ) // No colspan inside first column. || ( 1 === $col_idx && $this->render_options['first_column_th'] ) // No colspan into first column head. ) ) { continue; } // Invalid span, so we set cell content from #span# to empty. $cell_content = ''; } // Attributes for the table cell (HTML td or th element). $tag_attributes = array(); // "colspan" and "rowspan" attributes. if ( $this->colspan[ $row_idx ] > 1 ) { // We have colspaned cells. $tag_attributes['colspan'] = (string) $this->colspan[ $row_idx ]; if ( ! $this->tbody_has_connected_cells && $row_idx > $this->render_options['table_head'] - 1 && $row_idx < $this->last_row_idx - $this->render_options['table_foot'] + 1 ) { // Set flag that there are connected cells in the tbody. $this->tbody_has_connected_cells = true; } } if ( $this->rowspan[ $col_idx ] > 1 ) { // We have rowspaned cells. $tag_attributes['rowspan'] = (string) $this->rowspan[ $col_idx ]; if ( ! $this->tbody_has_connected_cells && $row_idx > $this->render_options['table_head'] - 1 && $row_idx < $this->last_row_idx - $this->render_options['table_foot'] + 1 ) { // Set flag that there are connected cells in the tbody. $this->tbody_has_connected_cells = true; } } // "class" attribute. $cell_class = 'column-' . ( $col_idx + 1 ); /** * Filters the CSS classes that are given to a single cell (HTML td element) of a table. * * @since 1.0.0 * * @param string $cell_class The CSS classes for the cell. * @param string $table_id The current table ID. * @param string $cell_content The cell content. * @param int $row_idx The row number of the cell. * @param int $col_idx The column number of the cell. * @param int $colspan_row The number of combined columns for this cell. * @param int $rowspan_col The number of combined rows for this cell. */ $cell_class = apply_filters( 'tablepress_cell_css_class', $cell_class, $this->table['id'], $cell_content, $row_idx + 1, $col_idx + 1, $this->colspan[ $row_idx ], $this->rowspan[ $col_idx ] ); if ( ! empty( $cell_class ) ) { $tag_attributes['class'] = $cell_class; } // "style" attribute. if ( ( 0 === $row_idx ) && ! empty( $this->render_options['column_widths'][ $col_idx ] ) ) { $tag_attributes['style'] = 'width:' . preg_replace( '#[^0-9a-z.%]#', '', $this->render_options['column_widths'][ $col_idx ] ) . ';'; } /** * Filters the attributes for the table cell (HTML td or th element). * * @since 1.4.0 * * @param array $tag_attributes The attributes for the td or th element. * @param string $table_id The current table ID. * @param string $cell_content The cell content. * @param int $row_idx The row number of the cell. * @param int $col_idx The column number of the cell. * @param int $colspan_row The number of combined columns for this cell. * @param int $rowspan_col The number of combined rows for this cell. */ $tag_attributes = apply_filters( 'tablepress_cell_tag_attributes', $tag_attributes, $this->table['id'], $cell_content, $row_idx + 1, $col_idx + 1, $this->colspan[ $row_idx ], $this->rowspan[ $col_idx ] ); $tag_attributes = $this->_attributes_array_to_string( $tag_attributes ); if ( '' === $cell_content ) { $cell_tag = 'td'; // For accessibility, empty cells should use `td` and not `th` tags. } elseif ( $this->render_options['first_column_th'] && 0 === $col_idx ) { $cell_tag = 'th'; // Non-empty cells in the first column should use `th` tags, if enabled. } else { $cell_tag = $tag; // Otherwise, use the tag that was passed in as the default for the row. } $row_cells[] = "<{$cell_tag}{$tag_attributes}>{$cell_content}"; $this->colspan[ $row_idx ] = 1; // Reset. $this->rowspan[ $col_idx ] = 1; // Reset. } // Attributes for the table row (HTML tr element). $tr_attributes = array(); // "class" attribute. $row_classes = 'row-' . ( $row_idx + 1 ); /** * Filters the CSS classes that are given to a row (HTML tr element) of a table. * * @since 1.0.0 * * @param string $row_classes The CSS classes for the row. * @param string $table_id The current table ID. * @param string[] $row_cells The HTML code for the cells of the row. * @param int $row_idx The row number. * @param string[] $row_data The content of the cells of the row. */ $row_classes = apply_filters( 'tablepress_row_css_class', $row_classes, $this->table['id'], $row_cells, $row_idx + 1, $this->table['data'][ $row_idx ] ); if ( ! empty( $row_classes ) ) { $tr_attributes['class'] = $row_classes; } /** * Filters the attributes for the table row (HTML tr element). * * @since 1.4.0 * * @param array $tr_attributes The attributes for the tr element. * @param string $table_id The current table ID. * @param int $row_idx The row number. * @param string[] $row_data The content of the cells of the row. */ $tr_attributes = apply_filters( 'tablepress_row_tag_attributes', $tr_attributes, $this->table['id'], $row_idx + 1, $this->table['data'][ $row_idx ] ); $tr_attributes = $this->_attributes_array_to_string( $tr_attributes ); // Reverse rows because we looped through the cells in reverse order. $row_cells = array_reverse( $row_cells ); return "\n\t" . implode( '', $row_cells ) . "\n\n"; } /** * Convert an array of HTML tag attributes to a string. * * @since 1.4.0 * * @param array $attributes Attributes for the HTML tag in the array keys, and their values in the array values. * @return string The attributes as a string for usage in a HTML element. */ protected function _attributes_array_to_string( array $attributes ): string { $attributes_string = ''; foreach ( $attributes as $attribute => $value ) { $attributes_string .= " {$attribute}=\"{$value}\""; } return $attributes_string; } /** * Possibly replace certain HTML entities and replace line breaks with HTML. * * @since 1.0.0 * * @param string $text The string to process. * @return string Processed string for output. */ protected function safe_output( string $text ): string { /* * Replace any & with & that is not already an encoded entity (from function htmlentities2 in WP 2.8). * A complete htmlentities2() or htmlspecialchars() would encode tags, which we don't want. */ $text = (string) preg_replace( '/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,4};)/', '&', $text ); /** * Filters whether line breaks in the cell content shall be replaced with HTML br tags. * * @since 1.0.0 * * @param bool $replace Whether to replace line breaks with HTML br tags. Default true. * @param string $table_id The current table ID. */ if ( apply_filters( 'tablepress_apply_nl2br', true, $this->table['id'] ) ) { $text = nl2br( $text ); } return $text; } /** * Get the default render options, null means: Use option from "Edit" screen. * * @since 1.0.0 * * @return array Default render options. */ public function get_default_render_options(): array { // Attention: Array keys have to be lowercase, otherwise they won't match the Shortcode attributes, which will be passed in lowercase by WP. return array( 'alternating_row_colors' => null, 'block_preview' => false, 'border' => false, 'cache_table_output' => true, 'cellpadding' => false, 'cellspacing' => false, 'column_widths' => '', 'convert_line_breaks' => true, 'datatables_custom_commands' => null, 'datatables_datetime' => '', 'datatables_filter' => null, 'datatables_info' => null, 'datatables_lengthchange' => null, 'datatables_locale' => get_locale(), 'datatables_paginate' => null, 'datatables_paginate_entries' => null, 'datatables_scrollx' => null, 'datatables_scrolly' => false, 'datatables_sort' => null, 'evaluate_formulas' => true, 'extra_css_classes' => null, 'first_column_th' => false, 'hide_columns' => '', 'hide_rows' => '', 'id' => '', 'print_description' => null, 'print_description_position' => null, 'print_name' => null, 'print_name_position' => null, 'row_hover' => null, 'shortcode_debug' => false, 'show_columns' => '', 'show_rows' => '', 'table_foot' => null, 'table_head' => null, 'use_datatables' => null, ); } /** * Get the CSS code for the Preview iframe. * * @since 1.0.0 * * @return string CSS for the Preview iframe. */ public function get_preview_css(): string { $is_rtl = is_rtl(); $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' ); $default_css_minified = $tablepress_css->load_default_css_from_file( $is_rtl ); if ( false === $default_css_minified ) { $default_css_minified = ''; } $rtl_direction = $is_rtl ? "\ndirection: rtl;" : ''; return << /* iframe */ body { margin: 10px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;{$rtl_direction} } p { font-size: 13px; } {$default_css_minified} CSS; } } // class TablePress_Render PK!"&&!class-evaluate-phpspreadsheet.phpnu[> $table_data Table data in which formulas shall be evaluated. * @param string $table_id ID of the passed table. * @return array> Table data with evaluated formulas. */ public function evaluate_table_data( array $table_data, string $table_id ): array { $table_has_formulas = false; // Loop through all cells to check for formulas and convert notations. foreach ( $table_data as &$row ) { foreach ( $row as &$cell_content ) { if ( '' === $cell_content || '=' === $cell_content || '=' !== $cell_content[0] ) { continue; } $table_has_formulas = true; // Convert legacy "formulas in text" notation (`=Text {A3+B3} Text`) to standard Excel notation (`="Text "&A3+B3&" Text"`). if ( 1 === preg_match( '#{(.+?)}#', $cell_content ) ) { $cell_content = str_replace( '"', '""', $cell_content ); // Preserve existing quotation marks in text around formulas. $cell_content = '="' . substr( $cell_content, 1 ) . '"'; // Wrap the whole cell content in quotation marks, as there will be text around formulas. $cell_content = (string) preg_replace( '#{(.+?)}#', '"&$1&"', $cell_content, -1, $count ); // Convert all wrapped formulas to standard Excel notation. } } } unset( $row, $cell_content ); // Unset use-by-reference parameters of foreach loops. // No need to use the PHPSpreadsheet Calculation engine if the table does not contain formulas. if ( ! $table_has_formulas ) { return $table_data; } try { $spreadsheet = new \TablePress\PhpOffice\PhpSpreadsheet\Spreadsheet(); $worksheet = $spreadsheet->setActiveSheetIndex( 0 ); $worksheet->fromArray( /* $source */ $table_data, /* $nullValue */ '' ); // Don't allow cyclic references. \TablePress\PhpOffice\PhpSpreadsheet\Calculation\Calculation::getInstance( $spreadsheet )->cyclicFormulaCount = 0; /* * Register variables as Named Formulas. * The variables `ROW`, `COLUMN`, `CELL`, `PI`, and `E` should be considered deprecated and only their formulas should be used. */ $spreadsheet->addNamedFormula( new \TablePress\PhpOffice\PhpSpreadsheet\NamedFormula( 'TABLE_ID', $worksheet, $table_id ) ); $num_rows = (string) count( $table_data ); $spreadsheet->addNamedFormula( new \TablePress\PhpOffice\PhpSpreadsheet\NamedFormula( 'NUM_ROWS', $worksheet, $num_rows ) ); $num_columns = (string) count( $table_data[0] ); $spreadsheet->addNamedFormula( new \TablePress\PhpOffice\PhpSpreadsheet\NamedFormula( 'NUM_COLUMNS', $worksheet, $num_columns ) ); $spreadsheet->addNamedFormula( new \TablePress\PhpOffice\PhpSpreadsheet\NamedFormula( 'ROW', $worksheet, '=ROW()' ) ); $spreadsheet->addNamedFormula( new \TablePress\PhpOffice\PhpSpreadsheet\NamedFormula( 'COLUMN', $worksheet, '=COLUMN()' ) ); $spreadsheet->addNamedFormula( new \TablePress\PhpOffice\PhpSpreadsheet\NamedFormula( 'CELL', $worksheet, '=ADDRESS(ROW(),COLUMN(),4)' ) ); $spreadsheet->addNamedFormula( new \TablePress\PhpOffice\PhpSpreadsheet\NamedFormula( 'PI', $worksheet, '=PI()' ) ); $spreadsheet->addNamedFormula( new \TablePress\PhpOffice\PhpSpreadsheet\NamedFormula( 'E', $worksheet, '=EXP(1)' ) ); // Loop through all table cells and replace formulas with evaluated values. $cell_collection = $worksheet->getCellCollection(); foreach ( $table_data as $row_idx => &$row ) { foreach ( $row as $column_idx => &$cell_content ) { if ( strlen( $cell_content ) > 1 && '=' === $cell_content[0] ) { // Adapted from \TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::rangeToArray(). $cell_reference = \TablePress\PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex( $column_idx + 1 ) . ( $row_idx + 1 ); if ( $cell_collection->has( $cell_reference ) ) { $cell = $cell_collection->get( $cell_reference ); try { $cell_content = (string) $cell->getCalculatedValue(); // Convert hyperlinks, e.g. generated via `=HYPERLINK()` to HTML code. $cell_has_hyperlink = $worksheet->hyperlinkExists( $cell_reference ) && ! $worksheet->getHyperlink( $cell_reference )->isInternal(); if ( $cell_has_hyperlink ) { $url = $worksheet->getHyperlink( $cell_reference )->getUrl(); if ( '' !== $url ) { $url = esc_url( $url ); $cell_content = "{$cell_content}"; } } // Sanitize the output of the evaluated formula. $cell_content = wp_kses_post( $cell_content ); // Equals wp_filter_post_kses(), but without the unnecessary slashes handling. } catch ( \TablePress\PhpOffice\PhpSpreadsheet\Calculation\Exception $exception ) { $message = str_replace( 'Worksheet!', '', $exception->getMessage() ); $cell_content = "!ERROR! {$message}"; } } } } } unset( $row, $cell_content ); // Unset use-by-reference parameters of foreach loops. // Save PHP memory. $spreadsheet->disconnectWorksheets(); unset( $cell_collection, $worksheet, $spreadsheet ); } catch ( \TablePress\PhpOffice\PhpSpreadsheet\Calculation\Exception $exception ) { $message = str_replace( 'Worksheet!', '', $exception->getMessage() ); $table_data = array( array( "!ERROR! {$message}" ) ); } return $table_data; } } // class TablePress_Evaluate_PHPSpreadsheet PK!d?? class-css.phpnu[" with ">", but ">" is valid in CSS selectors. $css = str_replace( '>', '>', $css ); // strip_tags again, because of the just added ">" (KSES for a second time would again bring the ">" problem). $css = strip_tags( $css ); } $csstidy->set_cfg( 'remove_bslash', false ); $csstidy->set_cfg( 'compress_colors', false ); $csstidy->set_cfg( 'compress_font-weight', false ); $csstidy->set_cfg( 'lowercase_s', false ); $csstidy->set_cfg( 'optimise_shorthands', false ); $csstidy->set_cfg( 'remove_last_;', false ); $csstidy->set_cfg( 'case_properties', false ); $csstidy->set_cfg( 'sort_properties', false ); $csstidy->set_cfg( 'sort_selectors', false ); $csstidy->set_cfg( 'discard_invalid_selectors', false ); $csstidy->set_cfg( 'discard_invalid_properties', true ); $csstidy->set_cfg( 'merge_selectors', false ); $csstidy->set_cfg( 'css_level', 'CSS3.0' ); $csstidy->set_cfg( 'preserve_css', true ); $csstidy->set_cfg( 'timestamp', false ); $csstidy->set_cfg( 'template', 'default' ); $csstidy->parse( $css ); return $csstidy->print->plain(); } /** * Minify a string of CSS code, that should have been sanitized/tidied before. * * @since 1.1.0 * * @param string $css CSS code. * @return string Minified CSS code. */ public function minify_css( string $css ): string { $csstidy = TablePress::load_class( 'TablePress_CSSTidy', 'class.csstidy.php', 'libraries/csstidy' ); $csstidy->set_cfg( 'remove_bslash', false ); $csstidy->set_cfg( 'compress_colors', true ); $csstidy->set_cfg( 'compress_font-weight', true ); $csstidy->set_cfg( 'lowercase_s', false ); $csstidy->set_cfg( 'optimise_shorthands', 1 ); $csstidy->set_cfg( 'remove_last_;', true ); $csstidy->set_cfg( 'case_properties', false ); $csstidy->set_cfg( 'sort_properties', false ); $csstidy->set_cfg( 'sort_selectors', false ); $csstidy->set_cfg( 'discard_invalid_selectors', false ); $csstidy->set_cfg( 'discard_invalid_properties', true ); $csstidy->set_cfg( 'merge_selectors', false ); $csstidy->set_cfg( 'css_level', 'CSS3.0' ); $csstidy->set_cfg( 'preserve_css', false ); $csstidy->set_cfg( 'timestamp', false ); $csstidy->set_cfg( 'template', 'highest' ); $csstidy->parse( $css ); $css = $csstidy->print->plain(); // Remove all CSS comments from the minified CSS code, as CSSTidy does not remove those inside a CSS selector. return preg_replace( '!/\*[^*]*\*+([^/][^*]*\*+)*/\n?!', '', $css ); } /** * Get the location (file path or URL) of the "Custom CSS" file, depending on whether it's a Multisite install or not. * * @since 1.0.0 * * @param string $type "normal" version, "minified" version, or "combined" (with TablePress Default CSS) version. * @param string $location "path" or "url", for file path or URL. * @return string Full file path or full URL for the "Custom CSS" file. */ public function get_custom_css_location( string $type, string $location ): string { switch ( $type ) { case 'combined': $file = 'tablepress-combined.min.css'; break; case 'minified': $file = 'tablepress-custom.min.css'; break; case 'normal': default: $file = 'tablepress-custom.css'; break; } if ( is_multisite() ) { // Multisite installation: Use /wp-content/uploads/sites//. $upload_location = wp_upload_dir(); } else { // Singlesite installation: Use /wp-content/. $upload_location = array( 'basedir' => WP_CONTENT_DIR, 'baseurl' => content_url(), ); } switch ( $location ) { case 'url': $url = set_url_scheme( $upload_location['baseurl'] . '/' . $file ); /** * Filters the URL from which the "Custom CSS" file is loaded. * * @since 1.0.0 * * @param string $url URL of the "Custom CSS" file. * @param string $file File name of the "Custom CSS" file. * @param string $type Type of the "Custom CSS" file ("normal", "minified", or "combined"). */ $url = apply_filters( 'tablepress_custom_css_url', $url, $file, $type ); return $url; // break; // unreachable. case 'path': $path = $upload_location['basedir'] . '/' . $file; /** * Filters the file path on the server from which the "Custom CSS" file is loaded. * * @since 1.0.0 * * @param string $path File path of the "Custom CSS" file. * @param string $file File name of the "Custom CSS" file. * @param string $type Type of the "Custom CSS" file ("normal", "minified", or "combined"). */ $path = apply_filters( 'tablepress_custom_css_file_name', $path, $file, $type ); return $path; // break; // unreachable. default: // Return an empty string if no valid location was provided. return ''; // break; // unreachable. } } /** * Load the contents of the file with the "Custom CSS". * * @since 1.0.0 * * @param string $type Optional. Whether to load "normal" version or "minified" version. Default "normal". * @return string|false Custom CSS on success, false on error. */ public function load_custom_css_from_file( string $type = 'normal' ) /* : string|false */ { $filename = $this->get_custom_css_location( $type, 'path' ); // Check if file name is valid (0 means yes). if ( 0 !== validate_file( $filename ) ) { return false; } if ( ! @is_file( $filename ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged return false; } if ( ! @is_readable( $filename ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged return false; } return file_get_contents( $filename ); } /** * Loads the contents of the file with the TablePress Default CSS, * either for left-to-right (LTR) or right-to-left (RTL), in minified form. * * @since 1.1.0 * @since 2.0.0 Added the `$load_rtl` parameter. * * @param bool $load_rtl Optional. Whether to load LTR or RTL version. Default LTR. * @return string|false TablePress Default CSS on success, false on error. */ public function load_default_css_from_file( bool $load_rtl = false ) /* : string|false */ { $rtl = $load_rtl ? '-rtl' : ''; $filename = TABLEPRESS_ABSPATH . "css/build/default{$rtl}.css"; // Check if file name is valid (0 means yes). if ( 0 !== validate_file( $filename ) ) { return false; } if ( ! @is_file( $filename ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged return false; } if ( ! @is_readable( $filename ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged return false; } return file_get_contents( $filename ); } /** * Try to save "Custom CSS" to a file (requires "direct" method in WP_Filesystem, or stored FTP credentials). * * @since 1.1.0 * * @param string $custom_css_normal Custom CSS code to be saved. * @param string $custom_css_minified Minified CSS code to be saved. * @return bool True on success, false on failure. */ public function save_custom_css_to_file( string $custom_css_normal, string $custom_css_minified ): bool { /** * Filters whether the "Custom CSS" code shall be saved to a file on the server. * * @since 1.1.0 * * @param bool $save Whether to save the "Custom CSS" to a file. Default true. */ if ( ! apply_filters( 'tablepress_save_custom_css_to_file', true ) ) { return false; } // Start capturing the output, to later prevent it. ob_start(); $credentials = request_filesystem_credentials( '', '', false, '', null, false ); /* * Do we have credentials already? (Otherwise the form will have been rendered, which is not supported here.) * Or, if we have credentials, are they valid? */ if ( false === $credentials || ! WP_Filesystem( $credentials ) ) { // @phpstan-ignore argument.type ob_end_clean(); return false; } // We have valid access to the filesystem now -> try to save the files. return $this->_custom_css_save_helper( $custom_css_normal, $custom_css_minified ); } /** * Save "Custom CSS" to files, delete "Custom CSS" files, or return HTML for the credentials form. * * Only used from "Plugin Options" screen, save_custom_css_to_file() is used in cases where no form output/redirection is possible (e.g. during plugin updates). * * @since 1.0.0 * * @param string $custom_css_normal Custom CSS code to be saved. If empty, files will be deleted. * @param string $custom_css_minified Minified CSS code to be saved. * @return bool|string True on success, false on failure, or string of HTML for the credentials form for the WP_Filesystem API, if necessary. */ public function save_custom_css_to_file_plugin_options( string $custom_css_normal, string $custom_css_minified ) /* : bool|string */ { /** This filter is documented in classes/class-css.php */ if ( ! apply_filters( 'tablepress_save_custom_css_to_file', true ) ) { return false; } // Start capturing the output, to get HTML of the credentials form (if needed). ob_start(); $credentials = request_filesystem_credentials( '', '', false, '', null, false ); // Do we have credentials already? Otherwise the form will have been rendered already. if ( false === $credentials ) { $form_data = ob_get_clean(); $form_data = str_replace( 'name="upgrade" id="upgrade" class="button"', 'name="upgrade" id="upgrade" class="components-button is-primary"', $form_data ); // @phpstan-ignore argument.type return $form_data; } // We have received credentials, but don't know if they are valid yet. if ( ! WP_Filesystem( $credentials ) ) { // @phpstan-ignore argument.type // Credentials failed, so ask again (with $error flag true). request_filesystem_credentials( '', '', true, '', null, false ); $form_data = ob_get_clean(); $form_data = str_replace( 'name="upgrade" id="upgrade" class="button"', 'name="upgrade" id="upgrade" class="components-button is-primary"', $form_data ); // @phpstan-ignore argument.type return $form_data; } // We have valid access to the filesystem now -> try to save the files, or delete them if the "Custom CSS" is empty. if ( '' !== $custom_css_normal ) { return $this->_custom_css_save_helper( $custom_css_normal, $custom_css_minified ); } else { return $this->_custom_css_delete_helper(); } } /** * Save "Custom CSS" to files, if validated access to the WP_Filesystem exists. * * Helper function to prevent code duplication. * * @since 1.1.0 * * @global WP_Filesystem_* $wp_filesystem WordPress file system abstraction object. * @see save_custom_css_to_file() * @see save_custom_css_to_file_plugin_options() * * @param string $custom_css_normal Custom CSS code to be saved. * @param string $custom_css_minified Minified CSS code to be saved. * @return bool True on success, false on failure. */ protected function _custom_css_save_helper( string $custom_css_normal, string $custom_css_minified ): bool { global $wp_filesystem; /* * WP_CONTENT_DIR and (FTP-)Content-Dir can be different (e.g. if FTP working dir is /). * We need to account for that by replacing the path difference in the filename. */ $path_difference = str_replace( $wp_filesystem->wp_content_dir(), '', trailingslashit( WP_CONTENT_DIR ) ); $css_types = array( 'normal', 'minified', 'combined' ); $default_css_minified = $this->load_default_css_from_file( false ); if ( false === $default_css_minified ) { $default_css_minified = ''; } $file_content = array( 'normal' => $custom_css_normal, 'minified' => $custom_css_minified, 'combined' => $default_css_minified . $custom_css_minified, ); $total_result = true; // Whether all files were saved successfully. foreach ( $css_types as $css_type ) { $filename = $this->get_custom_css_location( $css_type, 'path' ); // Check if filename is valid (0 means yes). if ( 0 !== validate_file( $filename ) ) { $total_result = false; continue; } if ( '' !== $path_difference ) { $filename = str_replace( $path_difference, '', $filename ); } $result = $wp_filesystem->put_contents( $filename, $file_content[ $css_type ], FS_CHMOD_FILE ); $total_result = ( $total_result && $result ); } $this->_flush_caching_plugins_css_minify_caches(); return $total_result; } /** * Delete the "Custom CSS" files, if possible. * * @since 1.0.0 * * @return bool True on success, false on failure. */ public function delete_custom_css_files(): bool { // Start capturing the output, to later prevent it. ob_start(); $credentials = request_filesystem_credentials( '', '', false, '', null, false ); /* * Do we have credentials already? (Otherwise the form will have been rendered, which is not supported here.) * Or, if we have credentials, are they valid? */ if ( false === $credentials || ! WP_Filesystem( $credentials ) ) { // @phpstan-ignore argument.type ob_end_clean(); return false; } // We have valid access to the filesystem now -> try to delete the files. return $this->_custom_css_delete_helper(); } /** * Delete "Custom CSS" files, if validated access to the WP_Filesystem exists. * * Helper function to prevent code duplication * * @since 1.1.0 * * @global WP_Filesystem_* $wp_filesystem WordPress file system abstraction object. * @see delete_custom_css_files() * @see save_custom_css_to_file_plugin_options() * * @return bool True on success, false on failure. */ protected function _custom_css_delete_helper(): bool { global $wp_filesystem; /* * WP_CONTENT_DIR and (FTP-)Content-Dir can be different (e.g. if FTP working dir is /). * We need to account for that by replacing the path difference in the filename. */ $path_difference = str_replace( $wp_filesystem->wp_content_dir(), '', trailingslashit( WP_CONTENT_DIR ) ); $css_types = array( 'normal', 'minified', 'combined' ); $total_result = true; // Whether all files were deleted successfully. foreach ( $css_types as $css_type ) { $filename = $this->get_custom_css_location( $css_type, 'path' ); // Check if filename is valid (0 means yes). if ( 0 !== validate_file( $filename ) ) { $total_result = false; continue; } if ( '' !== $path_difference ) { $filename = str_replace( $path_difference, '', $filename ); } // We have valid access to the filesystem now -> try to delete the file. if ( $wp_filesystem->exists( $filename ) ) { $result = $wp_filesystem->delete( $filename ); $total_result = ( $total_result && $result ); } } $this->_flush_caching_plugins_css_minify_caches(); return $total_result; } /** * Flush the CSS minification caches of common caching plugins. * * @since 1.4.0 */ protected function _flush_caching_plugins_css_minify_caches(): void { /** This filter is documented in models/model-table.php */ if ( ! apply_filters( 'tablepress_flush_caching_plugins_caches', true ) ) { return; } // W3 Total Cache. if ( function_exists( 'w3tc_minify_flush' ) ) { w3tc_minify_flush(); } // WP Fastest Cache. if ( isset( $GLOBALS['wp_fastest_cache'] ) && is_callable( array( $GLOBALS['wp_fastest_cache'], 'deleteCache' ) ) ) { $GLOBALS['wp_fastest_cache']->deleteCache( true ); // @phpstan-ignore method.nonObject } } } // class TablePress_CSS PK!!oclass-import-base.phpnu[> $an_array Two-dimensional array to be padded. */ public function pad_array_to_max_cols( array &$an_array ): void { $max_columns = $this->count_max_columns( $an_array ); // Extend the array to at least one column. $max_columns = max( 1, $max_columns ); array_walk( $an_array, static function ( array &$row, int $col_idx ) use ( $max_columns ): void { $row = array_pad( $row, $max_columns, '' ); }, ); } /** * Get the highest number of columns in the rows. * * @since 1.0.0 * * @param array> $an_array Two-dimensional array. * @return int Highest number of columns in the rows of the array. */ protected function count_max_columns( array $an_array ): int { $max_columns = 0; foreach ( $an_array as $row_idx => $row ) { $num_columns = count( $row ); $max_columns = max( $num_columns, $max_columns ); } return $max_columns; } } // class TablePress_Import_Base PK!שXclass-wp_option.phpnu[option_name = $params['option_name']; $option_value = $this->_get_option( $this->option_name, null ); if ( ! is_null( $option_value ) ) { $this->option_value = (array) json_decode( $option_value, true ); } else { $this->option_value = $params['default_value']; } } /** * Check if Option is set. * * @since 1.0.0 * * @param string $name Name of the option to check. * @return bool Whether the option is set. */ public function is_set( string $name ): bool { return isset( $this->option_value[ $name ] ); } /** * Get a single Option, or get all Options. * * @since 1.0.0 * * @param string|false $name Optional. Name of a single option to get, or false for all options. * @param mixed $default_value Optional. Default value to return, if a single option $name does not exist. * @return mixed Value of the retrieved option $name or $default_value if it does not exist, or all options. */ public function get( /* string|false */ $name = false, /* string|int|float|bool|array|null */ $default_value = null ) /* : string|int|float|bool|array|null */ { if ( false === $name ) { return $this->option_value; } // Single Option wanted. if ( isset( $this->option_value[ $name ] ) ) { return $this->option_value[ $name ]; } else { return $default_value; } } /** * Update Option. * * @since 1.0.0 * * @param array $new_options New options (name => value). * @return bool True on success, false on failure. */ public function update( array $new_options ): bool { $this->option_value = $new_options; return $this->_update_option( $this->option_name, wp_json_encode( $this->option_value, TABLEPRESS_JSON_OPTIONS ) ); // @phpstan-ignore argument.type } /** * Delete Option. * * @since 1.0.0 * * @return bool True on success, false on failure. */ public function delete(): bool { return $this->_delete_option( $this->option_name ); } /* * Internal functions mapping - This needs to be re-defined by child classes. */ /** * Get the value of a WP Option with the WP Options API. * * @since 1.0.0 * * @param string $option_name Name of the WP Option. * @param mixed $default_value Default value of the WP Option. * @return mixed Current value of the WP Option, or $default_value if it does not exist. */ protected function _get_option( string $option_name, /* string|int|float|bool|array|null */ $default_value ) /* : string|int|float|bool|array|null */ { return get_option( $option_name, $default_value ); } /** * Update the value of a WP Option with the WP Options API. * * @since 1.0.0 * * @param string $option_name Name of the WP Option. * @param string $new_value New value of the WP Option. * @return bool True on success, false on failure. */ protected function _update_option( string $option_name, string $new_value ): bool { return update_option( $option_name, $new_value ); } /** * Delete a WP Option with the WP Options API. * * @since 1.0.0 * * @param string $option_name Name of the WP Option. * @return bool True on success, false on failure. */ protected function _delete_option( string $option_name ): bool { return delete_option( $option_name ); } } // class TablePress_WP_Option PK!WT""class-evaluate-legacy.phpnu[> */ protected array $table_data; /** * Storage for cell ranges that have been replaced in formulas. * * @since 1.0.0 * @var array */ protected array $known_ranges = array(); /** * Initialize the Formula Evaluation class, include the EvalMath class. * * @since 1.0.0 */ public function __construct() { $this->evalmath = TablePress::load_class( 'EvalMath', 'evalmath.class.php', 'libraries' ); // Don't raise PHP warnings. $this->evalmath->suppress_errors = true; } /** * Evaluate formulas in the passed table. * * @since 1.0.0 * * @param array> $table_data Table data in which formulas shall be evaluated. * @param string $table_id ID of the passed table. * @return array> Table data with evaluated formulas. */ public function evaluate_table_data( array $table_data, string $table_id ): array { $this->table_data = $table_data; $num_rows = count( $this->table_data ); // Exit early if there's no actual table data (e.g. after using the Row Filter module). if ( 0 === $num_rows ) { return $this->table_data; } $num_columns = count( $this->table_data[0] ); // Make fixed table data available as variables in formulas. $this->evalmath->variables['table_id'] = $table_id; $this->evalmath->variables['num_rows'] = $num_rows; $this->evalmath->variables['num_columns'] = $num_columns; // Use two for-loops instead of foreach here to be sure to always work on the "live" table data and not some in-memory copy. for ( $row_idx = 0; $row_idx < $num_rows; $row_idx++ ) { for ( $col_idx = 0; $col_idx < $num_columns; $col_idx++ ) { $this->table_data[ $row_idx ][ $col_idx ] = $this->_evaluate_cell( $this->table_data[ $row_idx ][ $col_idx ], $row_idx, $col_idx ); } } return $this->table_data; } /** * Parse and evaluate the content of a cell. * * @since 1.0.0 * * @param string $content Content of a cell. * @param int $row_idx Row index of the cell. * @param int $col_idx Column index of the cell. * @param string[] $parents Optional. List of cells that depend on this cell (to prevent circle references). * @return string Result of the parsing/evaluation. */ protected function _evaluate_cell( string $content, int $row_idx, int $col_idx, array $parents = array() ): string { if ( '' === $content || '=' === $content || '=' !== $content[0] ) { return $content; } // Cut off the leading =. $content = substr( $content, 1 ); // Support putting formulas in strings, like =Total: {A3+A4}. $expressions = array(); if ( preg_match_all( '#{(.+?)}#', $content, $expressions, PREG_SET_ORDER ) ) { $formula_in_string = true; } else { $formula_in_string = false; // Fill array so that it has the same structure as if it came from preg_match_all(). $expressions[] = array( $content, $content ); } foreach ( $expressions as $expression ) { $orig_expression = $expression[0]; $expression = $expression[1]; $replaced_references = array(); $replaced_ranges = array(); // Remove all whitespace characters. $expression = str_replace( array( "\n", "\r", "\t", ' ' ), '', $expression ); // Expand cell ranges (like A3:A6) to a list of single cells (like A3,A4,A5,A6). if ( preg_match_all( '#([A-Z]+)([0-9]+):([A-Z]+)([0-9]+)#', $expression, $referenced_cell_ranges, PREG_SET_ORDER ) ) { foreach ( $referenced_cell_ranges as $cell_range ) { if ( in_array( $cell_range[0], $replaced_ranges, true ) ) { continue; } $replaced_ranges[] = $cell_range[0]; if ( isset( $this->known_ranges[ $cell_range[0] ] ) ) { $expression = (string) preg_replace( '#(?known_ranges[ $cell_range[0] ], $expression ); continue; } // No -1 necessary for this transformation, as we don't actually access the table. $first_col = TablePress::letter_to_number( $cell_range[1] ); $first_row = (int) $cell_range[2]; $last_col = TablePress::letter_to_number( $cell_range[3] ); $last_row = (int) $cell_range[4]; $col_start = min( $first_col, $last_col ); $col_end = max( $first_col, $last_col ) + 1; // +1 for loop below $row_start = min( $first_row, $last_row ); $row_end = max( $first_row, $last_row ) + 1; // +1 for loop below $cell_list = array(); for ( $col = $col_start; $col < $col_end; $col++ ) { for ( $row = $row_start; $row < $row_end; $row++ ) { $column = TablePress::number_to_letter( $col ); $cell_list[] = "{$column}{$row}"; } } $cell_list = implode( ',', $cell_list ); $expression = (string) preg_replace( '#(?known_ranges[ $cell_range[0] ] = $cell_list; } } // Parse and evaluate single cell references (like A3 or XY312), while prohibiting circle references. if ( preg_match_all( '#([A-Z]+)([0-9]+)(?![0-9A-Z\(])#', $expression, $referenced_cells, PREG_SET_ORDER ) ) { foreach ( $referenced_cells as $cell_reference ) { if ( in_array( $cell_reference[0], $parents, true ) ) { return '!ERROR! Circle Reference'; } if ( in_array( $cell_reference[0], $replaced_references, true ) ) { continue; } $replaced_references[] = $cell_reference[0]; $ref_col = TablePress::letter_to_number( $cell_reference[1] ) - 1; $ref_row = (int) $cell_reference[2] - 1; if ( ! isset( $this->table_data[ $ref_row ][ $ref_col ] ) ) { return "!ERROR! Cell {$cell_reference[0]} does not exist"; } $ref_parents = $parents; $ref_parents[] = $cell_reference[0]; $result = $this->_evaluate_cell( $this->table_data[ $ref_row ][ $ref_col ], $ref_row, $ref_col, $ref_parents ); $this->table_data[ $ref_row ][ $ref_col ] = $result; // Bail if there was an error already. if ( str_contains( $result, '!ERROR!' ) ) { return $result; } // Remove all whitespace characters. $result = str_replace( array( "\n", "\r", "\t", ' ' ), '', $result ); // Treat empty cells as 0. if ( '' === $result ) { $result = '0'; } // Bail if the cell does not result in a number (meaning it was a number or expression before being evaluated). if ( ! is_numeric( $result ) ) { return "!ERROR! {$cell_reference[0]} does not contain a number or expression"; } $expression = (string) preg_replace( '#(?_evaluate_math_expression( $expression, $row_idx, $col_idx ); // Support putting formulas in strings, like =Total: {A3+A4}. if ( $formula_in_string ) { $content = str_replace( $orig_expression, $result, $content ); } else { $content = $result; } } return $content; } /** * Evaluate a math expression. * * @since 1.0.0 * * @param string $expression Math expression without leading = sign. * @param int $row_idx Row index of the cell with the expression. * @param int $col_idx Column index of the cell with the expression. * @return string Result of the evaluation. */ protected function _evaluate_math_expression( string $expression, int $row_idx, int $col_idx ): string { // Make current cell's name and row and column number available as variables in formulas. $this->evalmath->variables['row'] = $row_idx + 1; $this->evalmath->variables['column'] = $col_idx + 1; $this->evalmath->variables['cell'] = TablePress::number_to_letter( $this->evalmath->variables['column'] ) . $this->evalmath->variables['row']; // Straight up evaluation, without parsing of variable or function assignments (which is why we only need one instance of the object). $result = $this->evalmath->evaluate( $expression ); if ( false === $result ) { $result = '!ERROR! ' . $this->evalmath->last_error; } return (string) $result; } } // class TablePress_Evaluate_Legacy PK![|gAAclass-import-phpspreadsheet.phpnu[|WP_Error Table array on success, WP_Error on error. */ public function import_table( File $file ) /* : array|WP_Error */ { $data = file_get_contents( $file->location ); if ( false === $data ) { return new WP_Error( 'table_import_phpspreadsheet_data_read', '', $file->location ); } // Remove a possible UTF-8 Byte-Order Mark (BOM). $bom = pack( 'CCC', 0xef, 0xbb, 0xbf ); if ( str_starts_with( $data, $bom ) ) { $data = substr( $data, 3 ); } if ( '' === $data ) { return new WP_Error( 'table_import_phpspreadsheet_data_empty', '', $file->location ); } $table = $this->_maybe_import_json( $data ); if ( is_array( $table ) ) { return $table; } $table = $this->_maybe_import_html( $data ); if ( is_array( $table ) ) { return $table; } return $this->_import_phpspreadsheet( $file ); } /** * Tries to import a table with the JSON format. * * @since 2.0.0 * * @param string $data Data to import. * @return array|false Table array on success, false if the file is not a JSON file. */ protected function _maybe_import_json( string $data ) /* : array|false */ { $data = trim( $data ); // If the file does not begin / end with [ / ] or { / }, it's not a supported JSON file. $first_character = $data[0]; $last_character = $data[-1]; if ( ! ( '[' === $first_character && ']' === $last_character ) && ! ( '{' === $first_character && '}' === $last_character ) ) { return false; } $json_table = json_decode( $data, true ); // Check if JSON could be decoded. If not, this is probably not a JSON file. if ( is_null( $json_table ) ) { return false; } // Specifically cast to an array again. $json_table = (array) $json_table; if ( isset( $json_table['data'] ) ) { // JSON data contained a full export. $table = $json_table; } else { // JSON data contained only the data of a table, but no options. $table = array( 'data' => array() ); foreach ( $json_table as $row ) { // Turn row into indexed arrays with numeric keys. $row = array_values( (array) $row ); // Remove entries of multi-dimensional arrays. foreach ( $row as &$cell ) { if ( is_array( $cell ) ) { $cell = ''; } } unset( $cell ); // Unset use-by-reference parameter of foreach loop. $table['data'][] = $row; } } $this->pad_array_to_max_cols( $table['data'] ); return $table; } /** * Tries to import a table with the HTML format. * * @since 2.0.0 * * @param string $data Data to import. * @return array|WP_Error Table array on success, WP_Error if the file is not an HTML file. */ protected function _maybe_import_html( string $data ) /* : array|false */ { TablePress::load_file( 'html-parser.class.php', 'libraries' ); $table = HTML_Parser::parse( $data ); // Check if the HTML code could be parsed. If not, this is probably not an HTML file. if ( is_wp_error( $table ) ) { return $table; } $this->pad_array_to_max_cols( $table['data'] ); return $table; } /** * Tries to import a table via PHPSpreadsheet. * * @since 2.0.0 * * @param File $file File to import. * @return array|WP_Error Table array on success, WP_Error on error. */ protected function _import_phpspreadsheet( File $file ) /* : array|WP_Error */ { // Rename the temporary file, as PHPSpreadsheet tries to infer the format from the file's extension. if ( '' !== $file->extension ) { $file_data = pathinfo( $file->location ); if ( ! isset( $file_data['extension'] ) || $file->extension !== $file_data['extension'] ) { $temp_file = wp_tempnam(); $new_location = "{$temp_file}.{$file->extension}"; if ( $file->keep_file ) { // Copy the file, as the original should be kept. if ( copy( $file->location, $new_location ) ) { $file->location = $new_location; $file->keep_file = false; // Delete the newly created file after the import. } } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found if ( rename( $file->location, $new_location ) ) { $file->location = $new_location; } } } } try { // Treat all cell values as strings, except for formulas (due to recognition of quoted/escaped formulas like `'=A2`). \TablePress\PhpOffice\PhpSpreadsheet\Cell\Cell::setValueBinder( new \TablePress\PhpOffice\PhpSpreadsheet\Cell\StringValueBinder() ); \TablePress\PhpOffice\PhpSpreadsheet\Cell\Cell::getValueBinder()->setFormulaConversion( false ); // @phpstan-ignore method.notFound /* * Try to detect a reader from the file extension and MIME type. * Fall back to CSV if no reader could be determined. */ try { $reader = \TablePress\PhpOffice\PhpSpreadsheet\IOFactory::createReaderForFile( $file->location ); } catch ( \TablePress\PhpOffice\PhpSpreadsheet\Reader\Exception $exception ) { $reader = \TablePress\PhpOffice\PhpSpreadsheet\IOFactory::createReader( 'Csv' ); // Change the file extension to .csv, so that \TablePress\PhpOffice\PhpSpreadsheet\Reader\Csv::canRead() returns true. $temp_file = wp_tempnam(); $new_location = "{$temp_file}.csv"; if ( $file->keep_file ) { // Copy the file, as the original should be kept. if ( copy( $file->location, $new_location ) ) { $file->location = $new_location; $file->keep_file = false; // Delete the newly created file after the import. } } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found if ( rename( $file->location, $new_location ) ) { $file->location = $new_location; } } } $class_name = get_class( $reader ); $class_type = explode( '\\', $class_name ); $detected_format = strtolower( array_pop( $class_type ) ); if ( 'csv' === $detected_format ) { $reader->setInputEncoding( \TablePress\PhpOffice\PhpSpreadsheet\Reader\Csv::GUESS_ENCODING ); // @phpstan-ignore method.notFound // @phpstan-ignore method.notFound, smaller.alwaysFalse (PHPStan thinks that the Composer minimum version will always be fulfilled.) $reader->setEscapeCharacter( ( PHP_VERSION_ID < 70400 ) ? "\x0" : '' ); // Disable the proprietary escape mechanism of PHP's fgetcsv() in PHP >= 7.4. } $reader->setIncludeCharts( false ); $reader->setReadEmptyCells( true ); // For non-Excel files, import only the data, but ignore formatting. if ( ! in_array( $detected_format, array( 'xlsx', 'xls' ), true ) ) { $reader->setReadDataOnly( true ); } // For formats where it's supported, import only the first sheet. if ( in_array( $detected_format, array( 'csv', 'html', 'slk' ), true ) ) { $reader->setSheetIndex( 0 ); // @phpstan-ignore method.notFound } $spreadsheet = $reader->load( $file->location ); $worksheet = $spreadsheet->getActiveSheet(); $cell_collection = $worksheet->getCellCollection(); $comments = $worksheet->getComments(); $table = array( 'data' => array(), ); $min_col = 'A'; $min_row = 1; $max_col = $worksheet->getHighestColumn(); $max_row = $worksheet->getHighestRow(); // Adapted from \TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::rangeToArray(). ++$max_col; // Due to for-loop with characters for columns. for ( $row = $min_row; $row <= $max_row; $row++ ) { $row_data = array(); for ( $col = $min_col; $col !== $max_col; $col++ ) { $cell_reference = $col . $row; if ( ! $cell_collection->has( $cell_reference ) ) { $row_data[] = ''; continue; } $cell = $cell_collection->get( $cell_reference ); $value = $cell->getValue(); if ( is_null( $value ) ) { $row_data[] = ''; continue; } $cell_has_hyperlink = $worksheet->hyperlinkExists( $cell_reference ) && ! $worksheet->getHyperlink( $cell_reference )->isInternal(); if ( $value instanceof \TablePress\PhpOffice\PhpSpreadsheet\RichText\RichText ) { $cell_data = $this->parse_rich_text( $value, $cell_has_hyperlink ); } else { $cell_data = (string) $value; } // Apply data type formatting. $style = $spreadsheet->getCellXfByIndex( $cell->getXfIndex() ); $format = $style->getNumberFormat()->getFormatCode() ?? \TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_GENERAL; /* * When cells in Excel files are formatted as "Text", quotation marks are removed, due to https://github.com/PHPOffice/PhpSpreadsheet/pull/3344. * Setting the format to "General" seems to prevent that. */ if ( \TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_TEXT === $format && ! is_numeric( $cell_data ) ) { $format = \TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_GENERAL; } // Fix floating point precision issues with numbers in the "General" Excel .xlsx format. if ( 'xlsx' === $detected_format && \TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_GENERAL === $format && is_numeric( $cell_data ) ) { $cell_data = (string) (float) $cell_data; // Type-cast strings to float and back. } $cell_data = \TablePress\PhpOffice\PhpSpreadsheet\Style\NumberFormat::toFormattedString( $cell_data, $format, array( $this, 'format_color' ), ); if ( strlen( $cell_data ) > 1 && '=' === $cell_data[0] ) { if ( 'xlsx' === $detected_format && $style->getQuotePrefix() ) { // Prepend a ' to quoted/escaped formulas (so that they are shown as text). This is currently not supported (at least) for the XLS format. $cell_data = "'{$cell_data}"; } else { // Bail early, to not add inline HTML styling around formulas, as they won't work anymore then. $row_data[] = $cell_data; continue; } } $font = $style->getFont(); if ( $font->getSuperscript() ) { $cell_data = "{$cell_data}"; } if ( $font->getSubscript() ) { $cell_data = "{$cell_data}"; } if ( $font->getStrikethrough() ) { $cell_data = "{$cell_data}"; } if ( $font->getUnderline() !== \TablePress\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_NONE && ! $cell_has_hyperlink ) { $cell_data = "{$cell_data}"; } if ( $font->getBold() ) { $cell_data = "{$cell_data}"; } if ( $font->getItalic() ) { $cell_data = "{$cell_data}"; } $color = $font->getColor()->getRGB(); if ( '' !== $color && '000000' !== $color && ! $cell_has_hyperlink ) { // Don't add the span if the color is black, as that's the default, or if it's in a hyperlink. $color_css = esc_attr( "color:#{$color};" ); $cell_data = "{$cell_data}"; } // Convert Hyperlinks to HTML code. if ( $cell_has_hyperlink ) { $url = $worksheet->getHyperlink( $cell_reference )->getUrl(); if ( '' !== $url ) { $title = $worksheet->getHyperlink( $cell_reference )->getTooltip(); if ( '' !== $title ) { $title = ' title="' . esc_attr( $title ) . '"'; } $url = esc_url( $url ); $cell_data = "{$cell_data}"; } } // Add comments. if ( isset( $comments[ $cell_reference ] ) ) { $sanitized_comment = esc_html( $worksheet->getComment( $cell_reference )->getText()->getPlainText() ); if ( '' !== $sanitized_comment ) { $cell_data .= '
' . $sanitized_comment . '
'; } } $row_data[] = $cell_data; } $table['data'][] = $row_data; } // Convert merged cells to trigger words. $merged_cells = $worksheet->getMergeCells(); foreach ( $merged_cells as $merged_cells_range ) { $cells = explode( ':', $merged_cells_range ); $first_cell = \TablePress\PhpOffice\PhpSpreadsheet\Cell\Coordinate::indexesFromString( $cells[0] ); $last_cell = \TablePress\PhpOffice\PhpSpreadsheet\Cell\Coordinate::indexesFromString( $cells[1] ); for ( $row_idx = $first_cell[1]; $row_idx <= $last_cell[1]; $row_idx++ ) { for ( $column_idx = $first_cell[0]; $column_idx <= $last_cell[0]; $column_idx++ ) { if ( $row_idx === $first_cell[1] && $column_idx === $first_cell[0] ) { continue; // Keep value of first cell. } elseif ( $row_idx === $first_cell[1] && $column_idx > $first_cell[0] ) { $table['data'][ $row_idx - 1 ][ $column_idx - 1 ] = '#colspan#'; } elseif ( $row_idx > $first_cell[1] && $column_idx === $first_cell[0] ) { $table['data'][ $row_idx - 1 ][ $column_idx - 1 ] = '#rowspan#'; } else { $table['data'][ $row_idx - 1 ][ $column_idx - 1 ] = '#span#'; } } } } // Save PHP memory. $spreadsheet->disconnectWorksheets(); unset( $comments, $cell_collection, $worksheet, $spreadsheet ); return $table; } catch ( \TablePress\PhpOffice\PhpSpreadsheet\Reader\Exception | \TablePress\PhpOffice\PhpSpreadsheet\Exception $exception ) { return new WP_Error( 'table_import_phpspreadsheet_failed', '', 'Exception: ' . $exception->getMessage() ); } } /** * Parses PHPSpreadsheet RichText elements and converts formatting to HTML tags. * * @param \TablePress\PhpOffice\PhpSpreadsheet\RichText\RichText $value RichText element. * @param bool $cell_has_hyperlink Whether the cell has a hyperlink. * @return string Cell value with HTML formatting. */ protected function parse_rich_text( \TablePress\PhpOffice\PhpSpreadsheet\RichText\RichText $value, bool $cell_has_hyperlink ): string { $cell_data = ''; $elements = $value->getRichTextElements(); foreach ( $elements as $element ) { $element_data = $element->getText(); // Rich text start? if ( $element instanceof \TablePress\PhpOffice\PhpSpreadsheet\RichText\Run ) { $font = $element->getFont(); if ( $font->getSuperscript() ) { $element_data = "{$element_data}"; } if ( $font->getSubscript() ) { $element_data = "{$element_data}"; } if ( $font->getStrikethrough() ) { $element_data = "{$element_data}"; } if ( $font->getUnderline() !== \TablePress\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_NONE ) { $element_data = "{$element_data}"; } if ( $font->getBold() ) { $element_data = "{$element_data}"; } if ( $font->getItalic() ) { $element_data = "{$element_data}"; } $color = $font->getColor()->getRGB(); if ( '' !== $color && '000000' !== $color && ! $cell_has_hyperlink ) { // Don't add the span if the color is black, as that's the default, or if it's in a hyperlink. $color_css = esc_attr( "color:#{$color};" ); $element_data = "{$element_data}"; } } $cell_data .= $element_data; } return $cell_data; } /** * Adds color to formatted string as inline style, e.g. from conditional formatting. * * @param string $value Plain formatted value without color. * @param string $format_code Format code. * @return string Value with color format applied. */ public function format_color( string $value, string $format_code ): string { // Color information, e.g. [Red] is always at the beginning of the format code. $color = ''; if ( 1 === preg_match( '/^\\[[a-zA-Z]+\\]/', $format_code, $matches ) ) { $color = str_replace( array( '[', ']' ), '', $matches[0] ); $color = strtolower( $color ); } if ( '' !== $color ) { $color = esc_attr( "color:{$color};" ); $value = "{$value}"; } return $value; } } // class TablePress_Import_PHPSpreadsheet PK!7vUclass-controller.phpnu[plugin_update_check(); } /** * Check if the plugin was updated and perform necessary actions, like updating the options. * * @since 1.0.0 */ protected function plugin_update_check(): void { // First activation or plugin update. $current_plugin_options_db_version = TablePress::$model_options->get( 'plugin_options_db_version' ); if ( $current_plugin_options_db_version < TablePress::db_version ) { // Allow more PHP execution time for update process. if ( function_exists( 'set_time_limit' ) ) { @set_time_limit( 300 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged } // Add TablePress capabilities to the WP_Roles objects, for new installations and all versions below 12. if ( $current_plugin_options_db_version < 12 ) { TablePress::$model_options->add_access_capabilities(); } if ( 0 === TablePress::$model_options->get( 'first_activation' ) ) { // Save initial set of plugin options, and time of first activation of the plugin, on first activation. TablePress::$model_options->update( array( 'first_activation' => time(), 'plugin_options_db_version' => TablePress::db_version, ) ); } else { // Update Plugin Options Options, if necessary. TablePress::$model_options->merge_plugin_options_defaults(); $updated_options = array( 'plugin_options_db_version' => TablePress::db_version, 'prev_tablepress_version' => TablePress::$model_options->get( 'tablepress_version' ), 'tablepress_version' => TablePress::version, 'message_plugin_update' => true, ); // If used, re-save "Custom CSS" to re-create all files (as TablePress Default CSS might have changed). $custom_css = TablePress::$model_options->get( 'custom_css' ); if ( TablePress::$model_options->get( 'use_custom_css' ) && '' !== $custom_css ) { /** * Load WP file functions to provide filesystem access functions early. */ require_once ABSPATH . 'wp-admin/includes/file.php'; // @phpstan-ignore requireOnce.fileNotFound (This is a WordPress core file that always exists.) /** * Load WP admin template functions to provide `submit_button()` which is necessary for `request_filesystem_credentials()`. */ require_once ABSPATH . 'wp-admin/includes/template.php'; // @phpstan-ignore requireOnce.fileNotFound (This is a WordPress core file that always exists.) $tablepress_css = TablePress::load_class( 'TablePress_CSS', 'class-css.php', 'classes' ); $custom_css_minified = TablePress::$model_options->get( 'custom_css_minified' ); // Update "Custom CSS" to be compatible with DataTables 2, introduced in TablePress 3.0. if ( $current_plugin_options_db_version < 96 ) { $old_custom_css = $custom_css; $custom_css = TablePress::convert_datatables_api_data( $custom_css ); if ( $old_custom_css !== $custom_css ) { $custom_css = $tablepress_css->sanitize_css( $custom_css ); $custom_css_minified = $tablepress_css->minify_css( $custom_css ); $updated_options['custom_css'] = $custom_css; $updated_options['custom_css_minified'] = $custom_css_minified; } unset( $old_custom_css ); } $result = $tablepress_css->save_custom_css_to_file( $custom_css, $custom_css_minified ); // If saving was successful, use "Custom CSS" file. $updated_options['use_custom_css_file'] = $result; // Increase the "Custom CSS" version number for cache busting. if ( $result ) { $updated_options['custom_css_version'] = TablePress::$model_options->get( 'custom_css_version' ) + 1; } } TablePress::$model_options->update( $updated_options ); // Clear table caches. TablePress::$model_table->invalidate_table_output_caches(); // Add mime type field to existing posts with the TablePress Custom Post Type, in TablePress 1.5. if ( $current_plugin_options_db_version < 25 ) { TablePress::$model_table->add_mime_type_to_posts(); } // Add new access capabilities that were introduced in TablePress 2.3.2. if ( $current_plugin_options_db_version < 77 ) { TablePress::$model_options->add_access_capabilities_tp232(); } // Update all tables' "Custom Commands" to be compatible with DataTables 2, introduced in TablePress 3.0. if ( $current_plugin_options_db_version < 96 ) { TablePress::$model_table->update_custom_commands_datatables_tp30(); } } } // Maybe update the table scheme in each existing table, independently from updating the plugin options. if ( TablePress::$model_options->get( 'table_scheme_db_version' ) < TablePress::table_scheme_version ) { TablePress::$model_table->merge_table_options_defaults(); TablePress::$model_options->update( 'table_scheme_db_version', TablePress::table_scheme_version ); } /* * Update User Options, if necessary. * User Options are not saved in DB until first change occurs. */ if ( is_user_logged_in() && TablePress::$model_options->get( 'user_options_db_version' ) < TablePress::db_version ) { TablePress::$model_options->merge_user_options_defaults(); $updated_options = array( 'user_options_db_version' => TablePress::db_version, 'message_superseded_extensions' => true, ); TablePress::$model_options->update( $updated_options ); } } } // class TablePress_Controller PK!h_OOclass-model.phpnu[ */ public array $export_formats = array(); /** * Delimiters for the CSV export. * * @since 1.0.0 * @var array */ public array $csv_delimiters = array(); /** * Whether ZIP archive support is available in the PHP installation on the server. * * @since 1.0.0 */ public bool $zip_support_available = false; /** * Initialize the Export class. * * @since 1.0.0 */ public function __construct() { // Initiate here, because function call not possible outside a class method. $this->export_formats = array( 'csv' => __( 'CSV - Character-Separated Values', 'tablepress' ), 'html' => __( 'HTML - Hypertext Markup Language', 'tablepress' ), 'json' => __( 'JSON - JavaScript Object Notation', 'tablepress' ), ); $this->csv_delimiters = array( ';' => __( '; (semicolon)', 'tablepress' ), ',' => __( ', (comma)', 'tablepress' ), 'tab' => __( '\t (tabulator)', 'tablepress' ), ); if ( class_exists( 'ZipArchive', false ) ) { $this->zip_support_available = true; } } /** * Export a table. * * @since 1.0.0 * * @param array $table Table to be exported. * @param string $export_format Format for the export ('csv', 'html', 'json'). * @param string $csv_delimiter Delimiter for CSV export. * @return string Exported table (only data for CSV and HTML, full tables (including options) for JSON). */ public function export_table( array $table, string $export_format, string $csv_delimiter ): string { switch ( $export_format ) { case 'csv': $output = ''; if ( 'tab' === $csv_delimiter ) { $csv_delimiter = "\t"; } foreach ( $table['data'] as $row_idx => $row ) { $csv_row = array(); foreach ( $row as $column_idx => $cell_content ) { $csv_row[] = $this->csv_wrap_and_escape( $cell_content, $csv_delimiter ); } $output .= implode( $csv_delimiter, $csv_row ); $output .= "\n"; } break; case 'html': $num_rows = count( $table['data'] ); $last_row_idx = $num_rows - 1; $thead = ''; $tfoot = ''; $tbody = array(); foreach ( $table['data'] as $row_idx => $row ) { // Table head rows, but only if there's at least one additional row. if ( $row_idx < $table['options']['table_head'] && $num_rows > $table['options']['table_head'] ) { $thead = $this->html_render_row( $row, 'th' ); continue; } // Table foot rows, but only if there's at least one additional row. if ( $row_idx > $last_row_idx - $table['options']['table_foot'] && $num_rows > $table['options']['table_foot'] ) { $tfoot = $this->html_render_row( $row, 'th' ); continue; } // Neither first nor last row (with respective head/foot enabled), so render as body row. $tbody[] = $this->html_render_row( $row, 'td' ); } // , , and tags. if ( ! empty( $thead ) ) { $thead = "\t\n{$thead}\t\n"; } if ( ! empty( $tfoot ) ) { $tfoot = "\t\n{$tfoot}\t\n"; } $tbody = "\t\n" . implode( '', $tbody ) . "\t\n"; $output = "\n" . $thead . $tfoot . $tbody . "
\n"; break; case 'json': $output = wp_json_encode( $table, TABLEPRESS_JSON_OPTIONS ); if ( false === $output ) { $output = ''; } break; default: $output = ''; } return $output; } /** * Wrap and escape a cell for CSV export. * * @since 1.0.0 * * @param string $cell_content Content of a cell. * @param string $delimiter CSV delimiter character. * @return string Wrapped string for CSV export. */ protected function csv_wrap_and_escape( string $cell_content, string $delimiter ): string { // Return early if the cell is empty. No escaping or wrapping is needed then. if ( '' === $cell_content ) { return $cell_content; } // Escape potentially dangerous functions that could be used for CSV injection attacks in external spreadsheet software. $active_content_triggers = array( '=', '+', '-', '@' ); if ( in_array( $cell_content[0], $active_content_triggers, true ) ) { $functions_to_escape = array( 'cmd|', 'rundll32', 'DDE(', 'IMPORTXML(', 'IMPORTFEED(', 'IMPORTHTML(', 'IMPORTRANGE(', 'IMPORTDATA(', 'IMAGE(', 'HYPERLINK(', 'WEBSERVICE(', ); foreach ( $functions_to_escape as $function ) { if ( false !== stripos( $cell_content, $function ) ) { $cell_content = "'" . $cell_content; // Prepend a ' to indicate that the cell format is a text string. break; } } } // Escape CSV delimiter for RegExp (e.g. '|'). $delimiter = preg_quote( $delimiter, '#' ); if ( 1 === preg_match( '#' . $delimiter . '|"|\n|\r#i', $cell_content ) || str_starts_with( $cell_content, ' ' ) || str_ends_with( $cell_content, ' ' ) ) { // Escape single " as double "". $cell_content = str_replace( '"', '""', $cell_content ); // Wrap string in "". $cell_content = '"' . $cell_content . '"'; } return $cell_content; } /** * Generate the HTML of a row. * * @since 1.0.0 * * @param string[] $row Cells of the row to be rendered. * @param string $tag HTML tag to use for the cells (td or th). * @return string HTML code for the row. */ protected function html_render_row( array $row, string $tag ): string { $output = "\t\t\n"; array_walk( $row, array( $this, 'html_wrap_and_escape' ), $tag ); $output .= implode( '', $row ); $output .= "\t\t\n"; return $output; } /** * Wrap and escape a cell for HTML export. * * @since 1.0.0 * * @param string $cell_content Content of a cell. * @param int $column_idx Column index, or -1 if omitted. Unused, but defined to be able to use function as callback in array_walk(). * @param string $html_tag HTML tag that shall be used for the cell. */ protected function html_wrap_and_escape( string &$cell_content, int $column_idx, string $html_tag ): void { /* * Replace any & with & that is not already an encoded entity (from function htmlentities2 in WP 2.8). * A complete htmlentities2() or htmlspecialchars() would encode tags, which we don't want. */ $cell_content = (string) preg_replace( '/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,4};)/', '&', $cell_content ); $cell_content = "\t\t\t<{$html_tag}>{$cell_content}\n"; } } // class TablePress_Export PK!@class-import-file.phpnu[ $properties Array of file properties. */ public function __construct( array $properties ) { foreach ( $properties as $property => $value ) { if ( property_exists( $this, $property ) ) { $this->$property = $value; } } } } // class File PK!zb class-wp_user_option.phpnu[ID, $this->option_name, false ); } } } // class TablePress_WP_User_Option PK!O[%%class-import-legacy.phpnu[|false */ protected $imported_table = false; /** * Initialize the Import class. * * @since 1.0.0 */ public function __construct() { if ( class_exists( 'DOMDocument', false ) && function_exists( 'simplexml_import_dom' ) && function_exists( 'libxml_use_internal_errors' ) ) { $this->html_import_support_available = true; } if ( $this->html_import_support_available ) { $this->import_formats[] = 'html'; } } /** * Import a table. * * @since 1.0.0 * * @param string $format Import format. * @param string $data Data to import. * @return array|false Table array on success, false on error. */ public function import_table( string $format, string $data ) /* : array|false */ { /** * Filters the data that is to be imported. * * @since 1.8.1 * * @param string $data Data to import. * @param string $format Import format. */ $this->import_data = apply_filters( 'tablepress_import_table_data', $data, $format ); if ( ! in_array( $format, array( 'xlsx', 'xls' ), true ) ) { $this->fix_table_encoding(); } switch ( $format ) { case 'csv': $this->import_csv(); break; case 'html': $this->import_html(); break; case 'json': $this->import_json(); break; case 'xlsx': $this->import_xlsx(); break; case 'xls': $this->import_xls(); break; default: return false; } if ( false === $this->imported_table ) { return false; } // Make sure that cells are stored as strings. array_walk_recursive( $this->imported_table['data'], static function ( /* string|int|float|bool|null */ &$cell_content, int $col_idx ): void { $cell_content = (string) $cell_content; }, ); return $this->imported_table; } /** * Import CSV data. * * @since 1.0.0 */ protected function import_csv(): void { $csv_parser = TablePress::load_class( 'CSV_Parser', 'csv-parser.class.php', 'libraries' ); $csv_parser->load_data( $this->import_data ); $delimiter = $csv_parser->find_delimiter(); $data = $csv_parser->parse( $delimiter ); $this->pad_array_to_max_cols( $data ); $this->normalize_line_endings( $data ); $this->imported_table = array( 'data' => $data ); } /** * Import HTML data. * * @since 1.0.0 */ protected function import_html(): void { if ( ! $this->html_import_support_available ) { return; } TablePress::load_file( 'html-parser.class.php', 'libraries' ); $table = HTML_Parser::parse( $this->import_data ); if ( is_wp_error( $table ) ) { $this->imported_table = false; return; } $this->pad_array_to_max_cols( $table['data'] ); $this->normalize_line_endings( $table['data'] ); $this->imported_table = $table; } /** * Import JSON data. * * @since 1.0.0 */ protected function import_json(): void { $json_table = json_decode( $this->import_data, true ); // Check if JSON could be decoded. if ( is_null( $json_table ) ) { $json_error = json_last_error_msg(); $output = '' . __( 'The imported file contains errors:', 'tablepress' ) . "

JSON error: {$json_error}
"; wp_die( $output, 'Import Error', array( 'response' => 200, 'back_link' => true ) ); } // Specifically cast to an array again. $json_table = (array) $json_table; if ( isset( $json_table['data'] ) ) { // JSON data contained a full export. $table = $json_table; } else { // JSON data contained only the data of a table, but no options. $table = array( 'data' => array() ); foreach ( $json_table as $row ) { $table['data'][] = array_values( (array) $row ); } } $this->pad_array_to_max_cols( $table['data'] ); $this->imported_table = $table; } /** * Import Microsoft Excel 97-2003 data. * * @since 1.1.0 */ protected function import_xls(): void { $excel_reader = TablePress::load_class( 'Spreadsheet_Excel_Reader', 'excel-reader.class.php', 'libraries', $this->import_data ); // Loop through Excel file and retrieve value and colspan/rowspan properties for each cell. $sheet = 0; // 0 means first sheet of the Workbook $table = array(); $num_rows = $excel_reader->rowcount( $sheet ); $num_columns = $excel_reader->colcount( $sheet ); for ( $row = 1; $row <= $num_rows; $row++ ) { $table_row = array(); for ( $column = 1; $column <= $num_columns; $column++ ) { $cell = array(); $cell['rowspan'] = $excel_reader->rowspan( $row, $column, $sheet ); $cell['colspan'] = $excel_reader->colspan( $row, $column, $sheet ); $cell['val'] = $excel_reader->val( $row, $column, $sheet ); $table_row[] = $cell; } $table[] = $table_row; } // Transform colspan/rowspan properties to TablePress equivalent (cell content). foreach ( $table as $row_idx => $row ) { foreach ( $row as $col_idx => $cell ) { if ( 1 === $cell['rowspan'] && 1 === $cell['colspan'] ) { continue; } if ( 1 < $cell['colspan'] ) { for ( $i = 1; $i < $cell['colspan']; $i++ ) { $table[ $row_idx ][ $col_idx + $i ]['val'] = '#colspan#'; } } if ( 1 < $cell['rowspan'] ) { for ( $i = 1; $i < $cell['rowspan']; $i++ ) { $table[ $row_idx + $i ][ $col_idx ]['val'] = '#rowspan#'; } } if ( 1 < $cell['rowspan'] && 1 < $cell['colspan'] ) { for ( $i = 1; $i < $cell['rowspan']; $i++ ) { for ( $j = 1; $j < $cell['colspan']; $j++ ) { $table[ $row_idx + $i ][ $col_idx + $j ]['val'] = '#span#'; } } } } } // Flatten value property to two-dimensional array. foreach ( $table as &$row ) { foreach ( $row as &$cell ) { $cell = $cell['val']; } unset( $cell ); // Unset use-by-reference parameter of foreach loop. } unset( $row ); // Unset use-by-reference parameter of foreach loop. $this->imported_table = array( 'data' => $table ); } /** * Import Microsoft Excel 2007-2019 data. * * @since 1.1.0 */ protected function import_xlsx(): void { TablePress::load_file( 'simplexlsx.class.php', 'libraries' ); $xlsx_file = \Shuchkin\SimpleXLSX::parse( $this->import_data, true ); if ( ! $xlsx_file ) { $output = '' . __( 'The imported file contains errors:', 'tablepress' ) . '

' . \Shuchkin\SimpleXLSX::parseError() . '
'; wp_die( $output, 'Import Error', array( 'response' => 200, 'back_link' => true ) ); } $this->imported_table = array( 'data' => $xlsx_file->rows() ); } /** * Fixes the encoding to UTF-8 for the entire string that is to be imported. * * @since 1.0.0 * * @link http://stevephillips.me/blog/dealing-php-and-character-encoding */ protected function fix_table_encoding(): void { // Check and remove possible UTF-8 Byte-Order Mark (BOM). $bom = pack( 'CCC', 0xef, 0xbb, 0xbf ); if ( str_starts_with( $this->import_data, $bom ) ) { $this->import_data = substr( $this->import_data, 3 ); // If data has a BOM, it's UTF-8, so further checks unnecessary. return; } // Require the iconv() function for the following checks. if ( ! function_exists( 'iconv' ) ) { return; } // Check for possible UTF-16 BOMs ("little endian" and "big endian") and try to convert the data to UTF-8. if ( str_starts_with( $this->import_data, "\xFF\xFE" ) || str_starts_with( $this->import_data, "\xFE\xFF" ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $data = @iconv( 'UTF-16', 'UTF-8', $this->import_data ); if ( false !== $data ) { $this->import_data = $data; return; } } // Detect the character encoding and convert to UTF-8, if it's different. if ( function_exists( 'mb_detect_encoding' ) ) { $current_encoding = mb_detect_encoding( $this->import_data, 'ASCII, UTF-8, ISO-8859-1' ); if ( 'UTF-8' !== $current_encoding ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $data = @iconv( $current_encoding, 'UTF-8', $this->import_data ); // @phpstan-ignore argument.type if ( false !== $data ) { $this->import_data = $data; return; } } } } /** * Replaces Windows line breaks \r\n with Unix line breaks \n. * * This function uses call by reference to save PHP memory on large arrays. * * @since 2.0.0 * * @param array> $an_array Array in which Windows line breaks should be replaced by Unix line breaks. */ protected function normalize_line_endings( array &$an_array ): void { array_walk_recursive( $an_array, static function ( string &$cell_content, int $col_idx ): void { $cell_content = str_replace( "\r\n", "\n", $cell_content ); }, ); } } // class TablePress_Import_Legacy PK! index.phpnu[ */ public static array $modules = array(); /** * Start-up TablePress (run on WordPress "init") and load the controller for the current state. * * @since 1.0.0 */ public static function run(): void { /** * Fires before TablePress is loaded. * * The `tablepress_loaded` action hook might be a better choice in most situations, as TablePress options will then be available. * * @since 1.0.0 */ do_action( 'tablepress_run' ); /** * Filters the string that is used as the [table] Shortcode. * * @since 1.0.0 * * @param string $shortcode The [table] Shortcode string. */ self::$shortcode = apply_filters( 'tablepress_table_shortcode', self::$shortcode ); /** * Filters the string that is used as the [table-info] Shortcode. * * @since 1.0.0 * * @param string $shortcode_info The [table-info] Shortcode string. */ self::$shortcode_info = apply_filters( 'tablepress_table_info_shortcode', self::$shortcode_info ); // Load modals for table and options, to be accessible from everywhere via `TablePress::$model_options` and `TablePress::$model_table`. self::$model_options = self::load_model( 'options' ); self::$model_table = self::load_model( 'table' ); // Exit early, i.e. before a controller is loaded, if TablePress functionality is likely not needed. $exit_early = false; if ( ( isset( $_SERVER['SCRIPT_FILENAME'] ) && 'wp-login.php' === basename( $_SERVER['SCRIPT_FILENAME'] ) ) // Detect the WordPress Login screen. || ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) || wp_doing_cron() ) { $exit_early = true; } /** * Filters whether TablePress should exit early, e.g. during wp-login.php, XML-RPC, and WP-Cron requests. * * @since 2.0.0 * * @param bool $exit_early Whether TablePress should exit early. */ if ( apply_filters( 'tablepress_exit_early', $exit_early ) ) { return; } if ( is_admin() ) { $controller = 'admin'; if ( wp_doing_ajax() ) { $controller = 'admin_ajax'; } self::load_controller( $controller ); } // Load the frontend controller in all scenarios, so that Shortcode render functions are always available. self::$controller = self::load_controller( 'frontend' ); // Add filters and actions for the integration into the WP WXR exporter and importer. add_action( 'wp_import_insert_post', array( TablePress::$model_table, 'add_table_id_on_wp_import' ), 10, 4 ); // phpcs:ignore Squiz.Classes.SelfMemberReference.NotUsed add_filter( 'wp_import_post_meta', array( TablePress::$model_table, 'prevent_table_id_post_meta_import_on_wp_import' ), 10, 3 ); // phpcs:ignore Squiz.Classes.SelfMemberReference.NotUsed add_filter( 'wxr_export_skip_postmeta', array( TablePress::$model_table, 'add_table_id_to_wp_export' ), 10, 3 ); // phpcs:ignore Squiz.Classes.SelfMemberReference.NotUsed /** * Fires after TablePress is loaded. * * The `tablepress_run` action hook can be used if code has to run before TablePress is loaded. * * @since 2.0.0 */ do_action( 'tablepress_loaded' ); } /** * Load a file with require_once(), after running it through a filter. * * @since 1.0.0 * * @param string $file Name of the PHP file. * @param string $folder Name of the folder with the file. */ public static function load_file( string $file, string $folder ): void { $full_path = TABLEPRESS_ABSPATH . $folder . '/' . $file; /** * Filters the full path of a file that shall be loaded. * * @since 1.0.0 * * @param string $full_path Full path of the file that shall be loaded. * @param string $file File name of the file that shall be loaded. * @param string $folder Folder name of the file that shall be loaded. */ $full_path = apply_filters( 'tablepress_load_file_full_path', $full_path, $file, $folder ); if ( $full_path ) { require_once $full_path; } } /** * Create a new instance of the $class_name, which is stored in $file in the $folder subfolder * of the plugin's directory. * * @since 1.0.0 * * @param string $class_name Name of the class. * @param string $file Name of the PHP file with the class. * @param string $folder Name of the folder with $class_name's $file. * @param mixed[]|string|null $params Optional. Parameters that are passed to the constructor of $class_name. * @return object Initialized instance of the class. */ public static function load_class( string $class_name, string $file, string $folder, /* ?array|string */ $params = null ): object { /** * Filters name of the class that shall be loaded. * * @since 1.0.0 * * @param string $class_name Name of the class that shall be loaded. */ $class_name = apply_filters( 'tablepress_load_class_name', $class_name ); if ( ! class_exists( $class_name, false ) ) { self::load_file( $file, $folder ); } $the_class = new $class_name( $params ); return $the_class; } /** * Create a new instance of the $model, which is stored in the "models" subfolder. * * @since 1.0.0 * * @param string $model Name of the model. * @return object Instance of the initialized model. */ public static function load_model( string $model ): object { // Model Base Class. self::load_file( 'class-model.php', 'classes' ); // Make first letter uppercase for a better looking naming pattern. $ucmodel = ucfirst( $model ); $the_model = self::load_class( "TablePress_{$ucmodel}_Model", "model-{$model}.php", 'models' ); return $the_model; } /** * Create a new instance of the $view, which is stored in the "views" subfolder, and set it up with $data. * * @since 1.0.0 * * @param string $view Name of the view to load. * @param array $data Optional. Parameters/PHP variables that shall be available to the view. * @return object Instance of the initialized view, already set up, just needs to be rendered. */ public static function load_view( string $view, array $data = array() ): object { // View Base Class. self::load_file( 'class-view.php', 'classes' ); // Make first letter uppercase for a better looking naming pattern. $ucview = ucfirst( $view ); $the_view = self::load_class( "TablePress_{$ucview}_View", "view-{$view}.php", 'views' ); $the_view->setup( $view, $data ); return $the_view; } /** * Create a new instance of the $controller, which is stored in the "controllers" subfolder. * * @since 1.0.0 * * @param string $controller Name of the controller. * @return object Instance of the initialized controller. */ public static function load_controller( string $controller ): object { // Controller Base Class. self::load_file( 'class-controller.php', 'classes' ); // Make first letter uppercase for a better looking naming pattern. $uccontroller = ucfirst( $controller ); $the_controller = self::load_class( "TablePress_{$uccontroller}_Controller", "controller-{$controller}.php", 'controllers' ); return $the_controller; } /** * Generate the complete nonce string, from the nonce base, the action and an item, e.g. tablepress_delete_table_3. * * @since 1.0.0 * * @param string $action Action for which the nonce is needed. * @param string|false $item Optional. Item for which the action will be performed, like "table". false if no item should be used in the nonce. * @return string The resulting nonce string. */ public static function nonce( string $action, /* string|false */ $item = false ): string { $nonce = "tablepress_{$action}"; if ( $item ) { $nonce .= "_{$item}"; } return $nonce; } /** * Check whether a nonce string is valid. * * @since 1.0.0 * * @param string $action Action for which the nonce should be checked. * @param string|false $item Optional. Item for which the action should be performed, like "table". false if no item should be used in the nonce. * @param string $query_arg Optional. Name of the nonce query string argument in $_POST. * @param bool $ajax Whether the nonce comes from an AJAX request. */ public static function check_nonce( string $action, /* string|false */ $item = false, string $query_arg = '_wpnonce', bool $ajax = false ): void { $nonce_action = self::nonce( $action, $item ); if ( $ajax ) { check_ajax_referer( $nonce_action, $query_arg ); } else { check_admin_referer( $nonce_action, $query_arg ); } } /** * Calculate the column index (number) of a column header string (example: A is 1, AA is 27, ...). * * For the opposite, @see number_to_letter(). * * @since 1.0.0 * * @param string $column Column string. * @return int Column number, 1-based. */ public static function letter_to_number( string $column ): int { $column = (string) preg_replace( '/[^A-Za-z]/', '', $column ); $column = strtoupper( $column ); $count = strlen( $column ); $number = 0; for ( $i = 0; $i < $count; $i++ ) { $number += ( ord( $column[ $count - 1 - $i ] ) - 64 ) * 26 ** $i; } return $number; } /** * "Calculate" the column header string of a column index (example: 2 is B, AB is 28, ...). * * For the opposite, @see letter_to_number(). * * @since 1.0.0 * * @param int $number Column number, 1-based. * @return string Column string. */ public static function number_to_letter( int $number ): string { $column = ''; while ( $number > 0 ) { $column = chr( 65 + ( ( $number - 1 ) % 26 ) ) . $column; $number = intdiv( $number - 1, 26 ); } return $column; } /** * Get a nice looking date and time string from the mySQL format of datetime strings for output. * * @since 1.0.0 * * @param string $datetime_string DateTime string, often in mySQL format.. * @param string $separator_or_format Optional. Separator between date and time, or format string. * @return string Nice looking string with the date and time. */ public static function format_datetime( string $datetime_string, string $separator_or_format = ' ' ): string { $timezone = wp_timezone(); $datetime = date_create( $datetime_string, $timezone ); if ( false === $datetime ) { return $datetime_string; } $timestamp = $datetime->getTimestamp(); switch ( $separator_or_format ) { case ' ': case '
': case '
': case '
': $date = wp_date( get_option( 'date_format' ), $timestamp, $timezone ); $time = wp_date( get_option( 'time_format' ), $timestamp, $timezone ); $output = "{$date}{$separator_or_format}{$time}"; break; default: $output = (string) wp_date( $separator_or_format, $timestamp, $timezone ); break; } return $output; } /** * Get the name from a WP user ID (used to store information on last editor of a table). * * @since 1.0.0 * * @param int $user_id WP user ID. * @return string Nickname of the WP user with the $user_id. */ public static function get_user_display_name( int $user_id ): string { $user = get_userdata( $user_id ); return $user->display_name ?? sprintf( '%s', __( 'unknown', 'tablepress' ) ); } /** * Sanitizes a CSS class to ensure it only contains valid characters. * * Strips the string down to A-Z, a-z, 0-9, :, _, -. * This is an extension to WP's `sanitize_html_class()`, to also allow `:` which are used in some CSS frameworks. * * @since 1.11.0 * * @param string $css_class The CSS class name to be sanitized. * @return string The sanitized CSS class. */ public static function sanitize_css_class( string $css_class ): string { // Strip out any %-encoded octets. $sanitized_css_class = (string) preg_replace( '|%[a-fA-F0-9][a-fA-F0-9]|', '', $css_class ); // Limit to A-Z, a-z, 0-9, ':', '_', and '-'. $sanitized_css_class = (string) preg_replace( '/[^A-Za-z0-9:_-]/', '', $sanitized_css_class ); return $sanitized_css_class; } /** * Extracts the top-level keys from a JavaScript object string. * * This function is used to extract the keys of the "Custom Commands" JavaScript object string, to check for overrides. * It covers most cases, like normal object properties with and without quotes, shorthand properties, and shorthand methods, * and also ignores single-line and multi-line comments. * It does not cover all possible JavaScript syntax (like template literals, special characters, ...), * but should be sufficient for the use case. * * @since 3.0.0 * * @param string $js_object_string A JavaScript object as a string. * @return string[] Array of top-level keys of the object. */ public static function extract_keys_from_js_object_string( string $js_object_string ): array { $object_keys = array(); $length = strlen( $js_object_string ); $depth = 0; $key_expected = true; $in_quotes = false; $quote_char = ''; $in_function_declaration = false; $in_single_line_comment = false; $in_multi_line_comment = false; $object_key = ''; for ( $i = 0; $i < $length; $i++ ) { $char = $js_object_string[ $i ]; // Skip parsing single-line comments. if ( $in_single_line_comment ) { if ( "\n" === $char ) { $in_single_line_comment = false; } continue; } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found if ( '/' === $char && $i + 1 < $length && '/' === $js_object_string[ $i + 1 ] ) { $in_single_line_comment = true; ++$i; // Skip the second '/'. continue; } } // Skip parsing multi-line comments. if ( $in_multi_line_comment ) { if ( '*' === $char && $i + 1 < $length && '/' === $js_object_string[ $i + 1 ] ) { $in_multi_line_comment = false; ++$i; // Skip the '/' that ends the multi-line comment. } continue; } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found if ( '/' === $char && $i + 1 < $length && '*' === $js_object_string[ $i + 1 ] ) { $in_multi_line_comment = true; ++$i; // Skip the '*'. continue; } } // Skip parsing while inside a quoted string. if ( $in_quotes ) { if ( $quote_char === $char ) { $in_quotes = false; } continue; } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found if ( '"' === $char || "'" === $char ) { $in_quotes = true; $quote_char = $char; continue; } } /* * Skip parsing while inside a `function abc( ... )` declaration string. * The `$key_expected` check limits search the "function" string to object values. * The check for the plain `f` reduces expensive `substr()` calls. */ if ( ! $key_expected ) { if ( $in_function_declaration ) { if ( ')' === $char ) { $in_function_declaration = false; } continue; } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found if ( 'f' === $char && 'function' === substr( $js_object_string, $i, 8 ) ) { $in_function_declaration = true; $i += 7; // Skip the rest of the "function" string. continue; } } } // Handle object depth, so that most parsing can be limited to the top level. if ( '{' === $char || '[' === $char ) { ++$depth; } // Extract only keys at the top level. if ( 1 === $depth ) { if ( $key_expected ) { if ( ':' === $char ) { // Check for normal keys, with value after :. // Go backwards to find the start of the key. $j = $i - 1; while ( $j >= 0 && preg_match( '/\s/', $js_object_string[ $j ] ) ) { --$j; } $key_end = $j; // Position of the last character of the key (potentially with quote). if ( '"' === $js_object_string[ $j ] || "'" === $js_object_string[ $j ] ) { // Quoted key. $quote_char = $js_object_string[ $j ]; --$j; while ( $j >= 0 && $quote_char !== $js_object_string[ $j ] ) { --$j; } $key_start = $j + 1; } else { // Unquoted key. while ( $j >= 0 && preg_match( '/[\w]/', $js_object_string[ $j ] ) ) { --$j; } $key_start = $j + 1; } $object_key = substr( $js_object_string, $key_start, $key_end - $key_start + 1 ); $object_key = trim( $object_key, "\"'" ); if ( '' !== $object_key && ! in_array( $object_key, $object_keys, true ) ) { $object_keys[] = $object_key; } $key_expected = false; } elseif ( ( ',' === $char || '}' === $char ) ) { // The `}` case is for the last key. // Check for shorthand properties (which must be unquoted). // Go backwards to find the start of the shorthand key. $j = $i - 1; while ( $j >= 0 && preg_match( '/\s/', $js_object_string[ $j ] ) ) { --$j; } $key_end = $j; // Position of the last character of the key (without a quote). while ( $j >= 0 && preg_match( '/[\w]/', $js_object_string[ $j ] ) ) { --$j; } $key_start = $j + 1; $object_key = substr( $js_object_string, $key_start, $key_end - $key_start + 1 ); if ( '' !== $object_key && ! in_array( $object_key, $object_keys, true ) ) { $object_keys[] = $object_key; } } elseif ( '(' === $char ) { // Detect shorthand method definitions. // Go back to find the start of the method name. $j = $i - 1; while ( $j >= 0 && preg_match( '/\s/', $js_object_string[ $j ] ) ) { --$j; } $key_end = $j; while ( $j >= 0 && preg_match( '/[\w]/', $js_object_string[ $j ] ) ) { --$j; } $key_start = $j + 1; $object_key = substr( $js_object_string, $key_start, $key_end - $key_start + 1 ); if ( '' !== $object_key && ! in_array( $object_key, $object_keys, true ) ) { $object_keys[] = $object_key; } } } // Reset the "key expected" flag after a comma or closing brace. if ( ',' === $char || '}' === $char ) { $key_expected = true; } } // Handle object depth. if ( '}' === $char || ']' === $char ) { --$depth; } } return $object_keys; } /** * Converts old DataTables 1.x CSS classes and parameters to the DataTables 2 variants. * * This function is used to modernize "Custom CSS" and "Custom Commands" for compatibility with DataTables 2.x. * It probably does not catch all possible cases. * * @since 3.0.0 * * @param string $code Code that contains DataTables 1.x CSS classes and parameters. * @return string Updated code with DataTables 2.x CSS classes and parameters. */ public static function convert_datatables_api_data( string $code ): string { /** * Mappings for DataTables 1.x CSS class or parameter to DataTables 2 variants. * As this array is used in `strtr()`, it's pre-sorted for descending string length of the array keys. */ static $datatables_api_data_mappings = array( // CSS classes. '.tablepress thead .sorting:hover' => '.tablepress thead .dt-orderable-asc:hover,.tablepress thead .dt-orderable-desc:hover', '.tablepress thead .sorting_desc' => '.tablepress thead .dt-ordering-desc', '.dataTables_filter label input' => '.dt-container .dt-search input', '.tablepress thead .sorting_asc' => '.tablepress thead .dt-ordering-asc', '.dataTables_scrollFootInner' => '.dt-scroll-footInner', '.dataTables_scrollHeadInner' => '.dt-scroll-headInner', '.tablepress thead .sorting' => '.tablepress thead .dt-orderable-asc,.tablepress thead .dt-orderable-desc', '.dataTables_processing' => '.dt-processing', '.dataTables_scrollBody' => '.dt-scroll-body', '.dataTables_scrollFoot' => '.dt-scroll-foot', '.dataTables_scrollHead' => '.dt-scroll-head', '.dataTables_paginate' => '.dt-paging', '.tablepress .even td' => '.tablepress>:where(tbody.row-striping)>:nth-child(odd)>*', '.dataTables_wrapper' => '.dt-container', '.tablepress .odd td' => '.tablepress>:where(tbody.row-striping)>:nth-child(even)>*', '.dataTables_filter' => '.dt-search', '.dataTables_length' => '.dt-length', '.dataTables_scroll' => '.dt-scroll', '.dataTables_empty' => '.dt-empty', '.dataTables_info' => '.dt-info', '.paginate_button' => '.dt-paging-button', // DataTables API functions. '$.fn.dataTable.' => 'DataTable.', ); $code = strtr( $code, $datatables_api_data_mappings ); // HTML ID mappings, which were removed. if ( str_contains( $code, '#tablepress-' ) ) { $code = (string) preg_replace( array( '/#tablepress-([A-Za-z1-9_-]|[A-Za-z0-9_-]{2,})_paginate/', '/#tablepress-([A-Za-z1-9_-]|[A-Za-z0-9_-]{2,})_filter/', '/#tablepress-([A-Za-z1-9_-]|[A-Za-z0-9_-]{2,})_length/', '/#tablepress-([A-Za-z1-9_-]|[A-Za-z0-9_-]{2,})_info/', ), array( '#tablepress-$1_wrapper .dt-paging', '#tablepress-$1_wrapper .dt-search', '#tablepress-$1_wrapper .dt-length', '#tablepress-$1_wrapper .dt-info', ), $code, ); } return $code; } /** * Retrieves all information of a WP_Error object as a string. * * @since 1.4.0 * * @param WP_Error $wp_error A WP_Error object. * @return string All error codes, messages, and data of the WP_Error. */ public static function get_wp_error_string( WP_Error $wp_error ): string { $error_strings = array(); $error_codes = $wp_error->get_error_codes(); // Reverse order to get latest errors first. $error_codes = array_reverse( $error_codes ); foreach ( $error_codes as $error_code ) { $error_strings[ $error_code ] = $error_code; $error_messages = $wp_error->get_error_messages( $error_code ); $error_messages = implode( ', ', $error_messages ); if ( ! empty( $error_messages ) ) { $error_strings[ $error_code ] .= " ({$error_messages})"; } $error_data = $wp_error->get_error_data( $error_code ); if ( is_string( $error_data ) ) { $error_strings[ $error_code ] .= " [{$error_data}]"; } elseif ( is_array( $error_data ) ) { foreach ( $error_data as $key => $value ) { $error_data[ $key ] = "{$key}: {$value}"; } $error_data = implode( ', ', $error_data ); $error_strings[ $error_code ] .= " [{$error_data}]"; } } return implode( ";\n", $error_strings ); } /** * Generate the action URL, to be used as a link within the plugin (e.g. in the submenu navigation or List of Tables). * * @since 1.0.0 * * @param array $params Optional. Parameters to form the query string of the URL. * @param bool $add_nonce Optional. Whether the URL shall be nonced by WordPress. * @param string $target Optional. Target File, e.g. "admin-post.php" for POST requests. * @return string The URL for the given parameters (already run through esc_url() with $add_nonce === true!). */ public static function url( array $params = array(), bool $add_nonce = false, string $target = '' ): string { // Default action is "list", if no action given. if ( ! isset( $params['action'] ) ) { $params['action'] = 'list'; } $nonce_action = $params['action']; if ( '' !== $target ) { $params['action'] = "tablepress_{$params['action']}"; } else { $params['page'] = 'tablepress'; // Top-level parent page needs special treatment for better action strings. if ( self::$controller->is_top_level_page ) { $target = 'admin.php'; if ( ! in_array( $params['action'], array( 'list', 'edit' ), true ) ) { $params['page'] = "tablepress_{$params['action']}"; } if ( ! in_array( $params['action'], array( 'edit' ), true ) ) { $params['action'] = false; } } else { $target = self::$controller->parent_page; } } // $default_params also determines the order of the values in the query string. $default_params = array( 'page' => false, 'action' => false, 'item' => false, ); $params = array_merge( $default_params, $params ); $url = add_query_arg( $params, admin_url( $target ) ); if ( $add_nonce ) { $url = wp_nonce_url( $url, self::nonce( $nonce_action, $params['item'] ) ); // wp_nonce_url() does esc_html(). } return $url; } /** * Create a redirect URL from the $target_parameters and redirect the user. * * @since 1.0.0 * * @param array $params Optional. Parameters from which the target URL is constructed. * @param bool $add_nonce Optional. Whether the URL shall be nonced by WordPress. */ public static function redirect( array $params = array(), bool $add_nonce = false ): void { $redirect = self::url( $params ); if ( $add_nonce ) { if ( ! isset( $params['item'] ) ) { $params['item'] = false; } // Don't use wp_nonce_url(), as that uses esc_html(). $redirect = add_query_arg( '_wpnonce', wp_create_nonce( self::nonce( $params['action'], $params['item'] ) ), $redirect ); } wp_redirect( $redirect ); exit; } /** * Determines the editor that the site uses, so that certain text and input fields referring to Shortcodes can be displayed or not. * * @since 3.1.0 * * @return string The editor that the site uses, either "block", "elementor", or "other". */ public static function site_used_editor(): string { if ( is_plugin_active( 'elementor/elementor.php' ) ) { return 'elementor'; } // Checking for Elementor is not needed anymore in this condition. $site_uses_block_editor = use_block_editor_for_post_type( 'post' ) && ! is_plugin_active( 'classic-editor/classic-editor.php' ) && ! is_plugin_active( 'classic-editor-addon/classic-editor-addon.php' ) && ! is_plugin_active( 'siteorigin-panels/siteorigin-panels.php' ) && ! is_plugin_active( 'beaver-builder-lite-version/fl-builder.php' ); /** * Filters the outcome of the check whether the site uses the block editor. * * This can be used when certain conditions (e.g. new site builders) are not (yet) accounted for. * * @since 2.0.1 * * @param bool $site_uses_block_editor True if the site uses the block editor, false otherwise. */ $site_uses_block_editor = (bool) apply_filters( 'tablepress_site_uses_block_editor', $site_uses_block_editor ); if ( $site_uses_block_editor ) { return 'block'; } return 'other'; } /** * Initializes the list of TablePress premium modules. * * @since 2.1.0 */ public static function init_modules(): void { if ( ! empty( self::$modules ) ) { return; } self::$modules = array( 'advanced-access-rights' => array( 'name' => __( 'Advanced Access Rights', 'tablepress' ), 'description' => __( 'Restrict access to individual tables for individual users.', 'tablepress' ), 'category' => 'backend', 'class' => 'TablePress_Module_Advanced_Access_Rights', 'incompatible_classes' => array( 'TablePress_Advanced_Access_Rights_Controller' ), 'minimum_plan' => 'max', 'default_active' => false, ), 'automatic-periodic-table-import' => array( 'name' => __( 'Automatic Periodic Table Import', 'tablepress' ), 'description' => __( 'Periodically update tables from a configured import source.', 'tablepress' ), 'category' => 'backend', 'class' => 'TablePress_Module_Automatic_Periodic_Table_Import', 'incompatible_classes' => array( 'TablePress_Table_Auto_Update' ), 'minimum_plan' => 'max', 'default_active' => true, ), 'automatic-table-export' => array( 'name' => __( 'Automatic Table Export', 'tablepress' ), 'description' => __( 'Export and save tables to files on the server after they were modified.', 'tablepress' ), 'category' => 'backend', 'class' => 'TablePress_Module_Automatic_Table_Export', 'incompatible_classes' => array(), 'minimum_plan' => 'pro', 'default_active' => false, ), 'cell-highlighting' => array( 'name' => __( 'Cell Highlighting', 'tablepress' ), 'description' => __( 'Add CSS classes to cells for highlighting based on their content.', 'tablepress' ), 'category' => 'frontend', 'class' => 'TablePress_Module_Cell_Highlighting', 'incompatible_classes' => array( 'TablePress_Cell_Highlighting' ), 'minimum_plan' => 'pro', 'default_active' => false, ), 'column-order' => array( 'name' => __( 'Column Order', 'tablepress' ), 'description' => __( 'Order the columns in different ways when a table is shown.', 'tablepress' ), 'category' => 'data-management', 'class' => 'TablePress_Module_Column_Order', 'incompatible_classes' => array( 'TablePress_Column_Order' ), 'minimum_plan' => 'pro', 'default_active' => false, ), 'datatables-advanced-loading' => array( 'name' => __( 'Advanced Loading', 'tablepress' ), 'description' => __( 'Load the table data from a JSON array for faster loading.', 'tablepress' ), 'category' => 'backend', 'class' => 'TablePress_Module_DataTables_Advanced_Loading', 'incompatible_classes' => array( 'TablePress_DataTables_Advanced_Loading' ), 'minimum_plan' => 'max', 'default_active' => false, ), 'datatables-alphabetsearch' => array( 'name' => __( 'Alphabet Search', 'tablepress' ), 'description' => __( 'Show Alphabet buttons above the table to filter rows by their first letter.', 'tablepress' ), 'category' => 'search-filter', 'class' => 'TablePress_Module_DataTables_Alphabetsearch', 'incompatible_classes' => array(), 'minimum_plan' => 'pro', 'default_active' => false, ), 'datatables-auto-filter' => array( 'name' => __( 'Automatic Filter', 'tablepress' ), 'description' => __( 'Pre-filter a table when it is shown.', 'tablepress' ), 'category' => 'search-filter', 'class' => 'TablePress_Module_DataTables_Auto_Filter', 'incompatible_classes' => array( 'TablePress_DataTables_Auto_Filter' ), 'minimum_plan' => 'pro', 'default_active' => false, ), 'datatables-buttons' => array( 'name' => __( 'User Action Buttons', 'tablepress' ), 'description' => __( 'Add buttons for downloading, copying, printing, and changing column visibility of tables.', 'tablepress' ), 'category' => 'frontend', 'class' => 'TablePress_Module_DataTables_Buttons', 'incompatible_classes' => array( 'TablePress_DataTables_Buttons' ), 'minimum_plan' => 'pro', 'default_active' => true, ), 'datatables-columnfilterwidgets' => array( 'name' => __( 'Column Filter Dropdowns', 'tablepress' ), 'description' => __( 'Add a search dropdown for each column above the table.', 'tablepress' ), 'category' => 'search-filter', 'class' => 'TablePress_Module_DataTables_ColumnFilterWidgets', 'incompatible_classes' => array(), 'minimum_plan' => 'pro', 'default_active' => true, ), 'datatables-column-filter' => array( 'name' => __( 'Individual Column Filtering', 'tablepress' ), 'description' => __( 'Add a search field for each column to the table head or foot row.', 'tablepress' ), 'category' => 'search-filter', 'class' => 'TablePress_Module_DataTables_Column_Filter', 'incompatible_classes' => array(), 'minimum_plan' => 'pro', 'default_active' => false, ), 'datatables-counter-column' => array( 'name' => __( 'Index Column', 'tablepress' ), 'description' => __( 'Make the first column an index or counter column with the row position.', 'tablepress' ), 'category' => 'frontend', 'class' => 'TablePress_Module_DataTables_Counter_Column', 'incompatible_classes' => array(), 'minimum_plan' => 'pro', 'default_active' => false, ), 'datatables-fixedheader-fixedcolumns' => array( 'name' => __( 'Fixed Rows and Columns', 'tablepress' ), 'description' => __( 'Fix the header and footer row and the first and last column when scrolling the table.', 'tablepress' ), 'category' => 'frontend', 'class' => 'TablePress_Module_DataTables_FixedHeader_FixedColumns', 'incompatible_classes' => array( 'TablePress_DataTables_FixedHeader', 'TablePress_DataTables_FixedColumns', ), 'minimum_plan' => 'pro', 'default_active' => true, ), 'datatables-layout' => array( 'name' => __( 'Table Layout', 'tablepress' ), 'description' => __( 'Customize the layout and position of features around a table.', 'tablepress' ), 'category' => 'frontend', 'class' => 'TablePress_Module_DataTables_Layout', 'incompatible_classes' => array(), 'minimum_plan' => 'pro', 'default_active' => true, ), 'datatables-fuzzysearch' => array( 'name' => __( 'Fuzzy Search', 'tablepress' ), 'description' => __( 'Let the search account for spelling mistakes and typos and find similar matches.', 'tablepress' ), 'category' => 'search-filter', 'class' => 'TablePress_Module_DataTables_FuzzySearch', 'incompatible_classes' => array(), 'minimum_plan' => 'max', 'default_active' => false, ), 'datatables-inverted-filter' => array( 'name' => __( 'Inverted Filtering', 'tablepress' ), 'description' => __( 'Turn the filtering into a search and hide the table if no search term is entered.', 'tablepress' ), 'category' => 'search-filter', 'class' => 'TablePress_Module_DataTables_Inverted_Filter', 'incompatible_classes' => array(), 'minimum_plan' => 'max', 'default_active' => false, ), 'datatables-pagination' => array( 'name' => __( 'Advanced Pagination Settings', 'tablepress' ), 'description' => __( 'Customize the pagination settings of the table.', 'tablepress' ), 'category' => 'frontend', 'class' => 'TablePress_Module_DataTables_Pagination', 'incompatible_classes' => array(), 'minimum_plan' => 'pro', 'default_active' => false, ), 'datatables-rowgroup' => array( 'name' => __( 'Row Grouping', 'tablepress' ), 'description' => __( 'Group table rows by a common keyword, category, or title.', 'tablepress' ), 'category' => 'frontend', 'class' => 'TablePress_Module_DataTables_RowGroup', 'incompatible_classes' => array( 'TablePress_DataTables_RowGroup' ), 'minimum_plan' => 'pro', 'default_active' => false, ), 'datatables-searchbuilder' => array( 'name' => __( 'Custom Search Builder', 'tablepress' ), 'description' => __( 'Show a search builder interface for filtering from groups and using conditions.', 'tablepress' ), 'category' => 'search-filter', 'class' => 'TablePress_Module_DataTables_SearchBuilder', 'incompatible_classes' => array(), 'minimum_plan' => 'max', 'default_active' => false, ), 'datatables-searchhighlight' => array( 'name' => __( 'Search Highlighting', 'tablepress' ), 'description' => __( 'Highlight found search terms in the table.', 'tablepress' ), 'category' => 'search-filter', 'class' => 'TablePress_Module_DataTables_SearchHighlight', 'incompatible_classes' => array(), 'minimum_plan' => 'pro', 'default_active' => false, ), 'datatables-searchpanes' => array( 'name' => __( 'Search Panes', 'tablepress' ), 'description' => __( 'Show panes for filtering the columns.', 'tablepress' ), 'category' => 'search-filter', 'class' => 'TablePress_Module_DataTables_SearchPanes', 'incompatible_classes' => array(), 'minimum_plan' => 'pro', 'default_active' => false, ), 'datatables-serverside-processing' => array( 'name' => __( 'Server-side Processing', 'tablepress' ), 'description' => __( 'Process sorting, filtering, and pagination on the server for faster loading of large tables.', 'tablepress' ), 'category' => 'backend', 'class' => 'TablePress_Module_DataTables_ServerSide_Processing', 'incompatible_classes' => array(), 'minimum_plan' => 'max', 'default_active' => true, ), 'default-style-customizer' => array( 'name' => __( 'Default Style Customizer', 'tablepress' ), 'description' => __( 'Change the default styling of your tables in the visual style customizer.', 'tablepress' ), 'category' => 'frontend', 'class' => 'TablePress_Module_Default_Style_Customizer', 'incompatible_classes' => array(), 'minimum_plan' => 'pro', 'default_active' => true, ), 'email-notifications' => array( 'name' => __( 'Email Notifications', 'tablepress' ), 'description' => __( 'Get email notifications when certain actions are performed on tables.', 'tablepress' ), 'category' => 'backend', 'class' => 'TablePress_Module_Email_Notifications', 'incompatible_classes' => array(), 'minimum_plan' => 'max', 'default_active' => false, ), 'responsive-tables' => array( 'name' => __( 'Responsive Tables', 'tablepress' ), 'description' => __( 'Make your tables look good on different screen sizes.', 'tablepress' ), 'category' => 'frontend', 'class' => 'TablePress_Module_Responsive_Tables', 'incompatible_classes' => array( 'TablePress_Responsive_Tables' ), 'minimum_plan' => 'pro', 'default_active' => true, ), 'rest-api' => array( 'name' => __( 'REST API', 'tablepress' ), 'description' => __( 'Read table data via the WordPress REST API, e.g. in external apps.', 'tablepress' ), 'category' => 'backend', 'class' => 'TablePress_Module_REST_API', 'incompatible_classes' => array( 'TablePress_REST_API_Controller' ), 'minimum_plan' => 'max', 'default_active' => false, ), 'row-filtering' => array( 'name' => __( 'Row Filtering', 'tablepress' ), 'description' => __( 'Show only table rows that contain defined keywords.', 'tablepress' ), 'category' => 'data-management', 'class' => 'TablePress_Module_Row_Filtering', 'incompatible_classes' => array( 'TablePress_Row_Filter' ), 'minimum_plan' => 'pro', 'default_active' => true, ), 'row-highlighting' => array( 'name' => __( 'Row Highlighting', 'tablepress' ), 'description' => __( 'Add CSS classes to rows for highlighting based on their content.', 'tablepress' ), 'category' => 'frontend', 'class' => 'TablePress_Module_Row_Highlighting', 'incompatible_classes' => array( 'TablePress_Row_Highlighting' ), 'minimum_plan' => 'pro', 'default_active' => false, ), 'row-order' => array( 'name' => __( 'Row Order', 'tablepress' ), 'description' => __( 'Order the rows in different ways when a table is shown.', 'tablepress' ), 'category' => 'data-management', 'class' => 'TablePress_Module_Row_Order', 'incompatible_classes' => array( 'TablePress_Row_Order' ), 'minimum_plan' => 'pro', 'default_active' => false, ), ); } } // class TablePress PK!~bRbRclass-view.phpnu[ */ protected array $data = array(); /** * Number of screen columns for post boxes. * * @since 1.0.0 */ protected int $screen_columns = 0; /** * User action for this screen. * * @since 1.0.0 */ protected string $action = ''; /** * Instance of the Admin Page Helper Class, with necessary functions. * * @since 1.0.0 */ protected \TablePress_Admin_Page $admin_page; /** * List of text boxes (similar to post boxes, but just with text and without extra functionality). * * @since 1.0.0 * @var array>> */ protected array $textboxes = array(); /** * List of messages that are to be displayed as boxes below the page title. * * @since 1.0.0 * @var string[] */ protected array $header_messages = array(); /** * Whether there are post boxes registered for this screen, * is automatically set to true, when a meta box is added. * * @since 1.0.0 */ protected bool $has_meta_boxes = false; /** * List of WP feature pointers for this view. * * @since 1.0.0 * @var string[] */ protected array $wp_pointers = array(); /** * Initializes the View class, by setting the correct screen columns and adding help texts. * * @since 1.0.0 */ public function __construct() { $screen = get_current_screen(); if ( 0 !== $this->screen_columns ) { $screen->add_option( 'layout_columns', array( 'max' => $this->screen_columns ) ); // @phpstan-ignore method.nonObject } // Enable two column layout. add_filter( "get_user_option_screen_layout_{$screen->id}", array( $this, 'set_current_screen_layout_columns' ) ); // @phpstan-ignore property.nonObject $common_content = '

' . sprintf( __( 'More information about TablePress can be found on the plugin website or on its page in the WordPress Plugin Directory.', 'tablepress' ), 'https://tablepress.org/', 'https://wordpress.org/plugins/tablepress/' ) . '

'; $common_content .= '

' . sprintf( __( 'For technical information, please see the Documentation.', 'tablepress' ), 'https://tablepress.org/documentation/' ) . ' ' . sprintf( __( 'Common questions are answered in the FAQ.', 'tablepress' ), 'https://tablepress.org/faq/' ) . '

'; if ( tb_tp_fs()->is_free_plan() ) { $common_content .= '

' . sprintf( __( 'Support is provided through the WordPress Support Forums.', 'tablepress' ), 'https://tablepress.org/support/', 'https://wordpress.org/tags/tablepress' ) . ' ' . sprintf( __( 'Before asking for support, please carefully read the Frequently Asked Questions, where you will find answers to the most common questions, and search through the forums.', 'tablepress' ), 'https://tablepress.org/faq/' ) . '

'; $common_content .= '

' . sprintf( __( 'More great features for you and your site’s visitors and priority email support are available with a Premium license plan of TablePress. Go check them out!', 'tablepress' ), 'https://tablepress.org/premium/?utm_source=plugin&utm_medium=textlink&utm_content=help-tab' ) . '

'; } $screen->add_help_tab( array( // @phpstan-ignore method.nonObject 'id' => 'tablepress-help', // This should be unique for the screen. 'title' => __( 'TablePress Help', 'tablepress' ), 'content' => '

' . $this->help_tab_content() . '

' . $common_content, ) ); // "Sidebar" in the help tab. $screen->set_help_sidebar( // @phpstan-ignore method.nonObject '

' . __( 'For more information:', 'tablepress' ) . '

' . '

TablePress Website

' . '

TablePress FAQ

' . '

TablePress Documentation

' . '

TablePress Support

' ); } /** * Changes the value of the user option "screen_layout_{$screen->id}" through a filter. * * @since 1.0.0 * * @param int|false $result Current value of the user option. * @return int New value for the user option. */ public function set_current_screen_layout_columns( /* int|false */ $result ): int { if ( false === $result ) { // The user option does not yet exist. $result = $this->screen_columns; } elseif ( $result > $this->screen_columns ) { // The value of the user option is bigger than what is possible on this screen (e.g. because the number of columns was reduced in an update). $result = $this->screen_columns; } return $result; } /** * Sets up the view with data and do things that are necessary for all views. * * @since 1.0.0 * * @param string $action Action for this view. * @param array $data Data for this view. */ public function setup( /* string */ $action, array $data ) /* : void */ { // Don't use type hints (except array $data) in method declaration, as the method is extended in some TablePress Extensions which are no longer updated. $this->action = $action; $this->data = $data; // Set page title. $GLOBALS['title'] = sprintf( __( '%1$s ‹ %2$s', 'tablepress' ), $this->data['view_actions'][ $this->action ]['page_title'], 'TablePress' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited // Admin page helpers, like script/style loading, could be moved to view. $this->admin_page = TablePress::load_class( 'TablePress_Admin_Page', 'class-admin-page-helper.php', 'classes' ); $this->admin_page->enqueue_style( 'common', array( 'wp-components' ) ); // RTL styles for the admin interface. if ( is_rtl() ) { $this->admin_page->enqueue_style( 'common-rtl', array( 'tablepress-common' ) ); } $this->admin_page->enqueue_script( 'common', array( 'jquery-core', 'postbox' ) ); $this->admin_page->add_admin_footer_text(); // Initialize WP feature pointers for TablePress. $this->_init_wp_pointers(); // Necessary fields for all views. $this->add_text_box( 'default_nonce_fields', array( $this, 'default_nonce_fields' ), 'header', false ); $this->add_text_box( 'action_nonce_field', array( $this, 'action_nonce_field' ), 'header', false ); $this->add_text_box( 'action_field', array( $this, 'action_field' ), 'header', false ); } /** * Registers a header message for the view. * * @since 1.0.0 * * @param string $text Text for the header message. * @param string $css_class Optional. Additional CSS class for the header message. * @param string $title Optional. Text for the header title. */ protected function add_header_message( string $text, string $css_class = 'is-success', string $title = '' ): void { if ( ! str_contains( $css_class, 'not-dismissible' ) ) { $css_class .= ' is-dismissible'; } if ( '' !== $title ) { $title = "

{$title}

"; } // Wrap the message text in HTML

tags if it does not already start with one (potentially with attributes), indicating custom message HTML. if ( '' !== $text && ! str_starts_with( $text, 'header_messages[] = "

{$title}{$text}
\n"; } /** * Processes header action messages, i.e. check if a message should be added to the page. * * @since 1.0.0 * * @param array $action_messages Action messages for the screen. */ protected function process_action_messages( array $action_messages ): void { if ( $this->data['message'] && isset( $action_messages[ $this->data['message'] ] ) ) { $class = ( str_starts_with( $this->data['message'], 'error' ) ) ? 'is-error' : 'is-success'; if ( '' !== $this->data['error_details'] ) { $this->data['error_details'] = '

' . sprintf( __( 'Error code: %s', 'tablepress' ), '' . esc_html( $this->data['error_details'] ) . '' ); } $this->add_header_message( "{$action_messages[ $this->data['message'] ]}{$this->data['error_details']}", $class ); } } /** * Registers a text box for the view. * * @since 1.0.0 * * @param string $id Unique HTML ID for the text box container (only visible with $wrap = true). * @param callable $callback Callback that prints the contents of the text box. * @param string $context Optional. Context/position of the text box (normal, side, additional, header, submit). * @param bool $wrap Whether the content of the text box shall be wrapped in a

container. */ protected function add_text_box( string $id, callable $callback, string $context = 'normal', bool $wrap = false ): void { if ( ! isset( $this->textboxes[ $context ] ) ) { $this->textboxes[ $context ] = array(); } $long_id = "tablepress_{$this->action}-{$id}"; $this->textboxes[ $context ][ $id ] = array( 'id' => $long_id, 'callback' => $callback, 'context' => $context, 'wrap' => $wrap, ); } /** * Registers a post meta box for the view, that is drag/droppable with WordPress functionality. * * @since 1.0.0 * * @param string $id Unique ID for the meta box. * @param string $title Title for the meta box. * @param callable $callback Callback that prints the contents of the post meta box. * @param 'normal'|'side'|'additional' $context Optional. Context/position of the post meta box (normal, side, additional). * @param 'core'|'default'|'high'|'low' $priority Optional. Order of the post meta box for the $context position (high, default, low). * @param mixed[]|null $callback_args Optional. Additional data for the callback function (e.g. useful when in different class). */ protected function add_meta_box( string $id, string $title, callable $callback, string $context = 'normal', string $priority = 'default', ?array $callback_args = null ): void { $this->has_meta_boxes = true; add_meta_box( "tablepress_{$this->action}-{$id}", $title, $callback, null, $context, $priority, $callback_args ); } /** * Renders all text boxes for the given context. * * @since 1.0.0 * * @param string $context Context (normal, side, additional, header, submit) for which registered text boxes shall be rendered. */ protected function do_text_boxes( string $context ): void { if ( empty( $this->textboxes[ $context ] ) ) { return; } foreach ( $this->textboxes[ $context ] as $box ) { if ( $box['wrap'] ) { echo "
\n"; } call_user_func( $box['callback'], $this->data, $box ); if ( $box['wrap'] ) { echo "
\n"; } } } /** * Renders all post meta boxes for the given context, if there are post meta boxes. * * @since 1.0.0 * * @param string $context Context (normal, side, additional) for which registered post meta boxes shall be rendered. */ protected function do_meta_boxes( string $context ): void { if ( $this->has_meta_boxes ) { do_meta_boxes( get_current_screen(), $context, $this->data ); // @phpstan-ignore argument.type } } /** * Prints hidden fields with nonces for post meta box AJAX handling, if there are post meta boxes on the screen. * * The check is possible as this function is executed after post meta boxes have to be registered. * * @since 1.0.0 * * @param array $data Data for this screen. * @param array $box Information about the text box. */ protected function default_nonce_fields( array $data, array $box ): void { if ( ! $this->has_meta_boxes ) { return; } wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false ); echo "\n"; wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false ); echo "\n"; } /** * Prints hidden field with a nonce for the screen's action, to be transmitted in HTTP requests. * * @since 1.0.0 * * @param array $data Data for this screen. * @param array $box Information about the text box. */ protected function action_nonce_field( array $data, array $box ): void { wp_nonce_field( TablePress::nonce( $this->action ) ); echo "\n"; } /** * Prints hidden field with the screen action. * * @since 1.0.0 * * @param array $data Data for this screen. * @param array $box Information about the text box. */ protected function action_field( array $data, array $box ): void { echo "action}\">\n"; } /** * Renders the current view. * * @since 1.0.0 */ public function render(): void { ?>
print_nav_tab_menu(); ?>

header_messages as $message ) { echo $message; } // "Import" screen has file upload. $enctype = ( 'import' === $this->action ) ? ' enctype="multipart/form-data"' : ''; ?>
id="tablepress-page-form"> do_text_boxes( 'header' ); $hide_if_no_js = ( in_array( $this->action, array( 'export', 'import' ), true ) ) ? ' class="hide-if-no-js"' : ''; ?>
>
do_text_boxes( 'normal' ); $this->do_meta_boxes( 'normal' ); $this->do_text_boxes( 'additional' ); $this->do_meta_boxes( 'additional' ); // Print all submit buttons. $this->do_text_boxes( 'submit' ); ?>
do_text_boxes( 'side' ); $this->do_meta_boxes( 'side' ); ?>

<?php esc_attr_e( 'TablePress plugin logo', 'tablepress' ); ?>

is_free_plan() ) : ?>
$data Data for this screen. * @param array $box Information about the text box. */ public function textbox_no_javascript( array $data, array $box ): void { ?>


the instructions on how to enable JavaScript in your browser.', 'tablepress' ); ?>

'list' ) ) ) . '">' . __( 'Back to the List of Tables', 'tablepress' ) . ''; ?>

$data Data for this screen. * @param array $box Information about the text box. */ protected function textbox_submit_button( array $data, array $box ): void { ?>

$data Information about the text box. */ protected function print_script_data_json( string $variable, array $data ): void { echo "\n"; } /** * Returns the content for the help tab for this screen. * * Has to be implemented for every view that is visible in the WP Dashboard! * * @since 1.0.0 * * @return string Help tab content for the view. */ protected function help_tab_content(): string { // Has to be implemented for every view that is visible in the WP Dashboard! return ''; } /** * Initializes the WP feature pointers for TablePress. * * @since 1.0.0 */ protected function _init_wp_pointers(): void { // Check if there are WP pointers for this view. if ( empty( $this->wp_pointers ) ) { return; } // Get dismissed pointers. $dismissed = explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ); $pointers_on_page = false; foreach ( array_diff( $this->wp_pointers, $dismissed ) as $pointer ) { // Bind pointer print function. add_action( "admin_footer-{$GLOBALS['hook_suffix']}", array( $this, 'wp_pointer_' . $pointer ) ); // @phpstan-ignore argument.type $pointers_on_page = true; } if ( $pointers_on_page ) { wp_enqueue_style( 'wp-pointer' ); wp_enqueue_script( 'wp-pointer' ); } } } // class TablePress_View PK!ǨSC|C|class-import.phpnu[ */ protected array $import_config = array(); /** * Whether ZIP archive support is available (which it always is, as PclZip is used as a fallback). * * @since 1.0.0 * @deprecated 2.3.0 ZIP support is now always available, either through `ZipArchive` or through `PclZip`. */ public bool $zip_support_available = true; /** * List of table names/IDs for use when replacing/appending existing tables (except for the JSON format). * * @since 2.0.0 * @var array */ protected array $table_names_ids = array(); /** * Runs the import process for a given import configuration. * * @since 2.0.0 * * @param array $import_config Import configuration. * @return array{tables: array>, errors: File[]}|WP_Error List of imported tables on success, WP_Error on failure. */ public function run( array $import_config ) /* : array|WP_Error */ { // Unziping can use a lot of memory and execution time, but not this much hopefully. wp_raise_memory_limit( 'admin' ); if ( function_exists( 'set_time_limit' ) ) { @set_time_limit( 300 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged } $this->import_config = $import_config; $import_files = $this->get_files_to_import(); if ( is_wp_error( $import_files ) ) { return $import_files; } $import_files = $this->convert_zip_files( $import_files ); if ( in_array( $this->import_config['type'], array( 'replace', 'append' ), true ) ) { $this->table_names_ids = $this->get_list_of_table_names(); } return $this->import_files( $import_files ); } /** * Extracts the files that shall be imported from the import configuration. * * @since 2.0.0 * * @return File[]|WP_Error Array of files that shall be imported or WP_Error on failure. */ protected function get_files_to_import() /* : array|WP_Error */ { $import_files = array(); switch ( $this->import_config['source'] ) { case 'file-upload': foreach ( $this->import_config['file-upload']['error'] as $key => $error ) { $file = new File( array( 'location' => $this->import_config['file-upload']['tmp_name'][ $key ], 'name' => $this->import_config['file-upload']['name'][ $key ], ) ); if ( UPLOAD_ERR_OK !== $error ) { @unlink( $this->import_config['file-upload']['tmp_name'][ $key ] ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $file->error = new WP_Error( 'table_import_file-upload_error', '', $error ); } $import_files[] = $file; } break; case 'url': $host = wp_parse_url( $this->import_config['url'], PHP_URL_HOST ); if ( empty( $host ) ) { return new WP_Error( 'table_import_url_host_invalid', '', $this->import_config['url'] ); } // Check the IP address of the host against a blocklist of hosts which should not be accessible, e.g. for security considerations. $ip = gethostbyname( $host ); // If no IP address can be found, this will return the host name, which will then be checked against the blocklist. $blocked_ips = array( '169.254.169.254', // Meta-data API for various cloud providers. '169.254.170.2', // AWS task metadata endpoint. '192.0.0.192', // Oracle Cloud endpoint. '100.100.100.200', // Alibaba Cloud endpoint. ); if ( in_array( $ip, $blocked_ips, true ) ) { return new WP_Error( 'table_import_url_host_blocked', '', array( 'url' => $this->import_config['url'], 'ip' => $ip ) ); } /** * Load WP file functions to be sure that `download_url()` exists, in particular during Cron requests. */ require_once ABSPATH . 'wp-admin/includes/file.php'; // @phpstan-ignore requireOnce.fileNotFound (This is a WordPress core file that always exists.) // Download URL to local file. $location = download_url( $this->import_config['url'] ); if ( is_wp_error( $location ) ) { $error = new WP_Error( 'table_import_url_download_failed', '', $this->import_config['url'] ); $error->merge_from( $location ); return $error; } $import_files[] = new File( array( 'location' => $location, 'name' => $this->import_config['url'], ) ); break; case 'server': if ( ABSPATH === $this->import_config['server'] ) { return new WP_Error( 'table_import_server_invalid', '', $this->import_config['server'] ); } if ( ! is_readable( $this->import_config['server'] ) ) { return new WP_Error( 'table_import_server_not_readable', '', $this->import_config['server'] ); } $import_files[] = new File( array( 'location' => $this->import_config['server'], 'name' => pathinfo( $this->import_config['server'], PATHINFO_BASENAME ), 'keep_file' => true, // Files on the server must not be deleted. ) ); break; case 'form-field': $location = wp_tempnam(); $num_written_bytes = file_put_contents( $location, $this->import_config['form-field'] ); if ( false === $num_written_bytes || 0 === $num_written_bytes ) { @unlink( $location ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged return new WP_Error( 'table_import_form-field_temp_file_not_written' ); } $import_files[] = new File( array( 'location' => $location, 'name' => __( 'Imported from Manual Input', 'tablepress' ), ) ); break; default: return new WP_Error( 'table_import_invalid_source', '', $this->import_config['source'] ); } return $import_files; } /** * Replaces ZIP archives in the import files with a list of their contents. * * ZIP files are removed from the list and their contents are added to the end of the list. * * @since 2.0.0 * * @param File[] $import_files Files that shall be imported, including ZIP archives. * @return File[] Files that shall be imported, with all ZIP archives recursively replaced by their contents. */ protected function convert_zip_files( array $import_files ): array { foreach ( $import_files as $key => &$file ) { // $file has to be used by reference, so that $key points to the correct element, due to array modification with `unset()` and `array_push()`. // Skip files that already have an error. if ( is_wp_error( $file->error ) ) { continue; } $file->extension = strtolower( pathinfo( $file->name, PATHINFO_EXTENSION ) ); if ( function_exists( 'mime_content_type' ) ) { $mime_type = mime_content_type( $file->location ); if ( false !== $mime_type ) { $file->mime_type = $mime_type; } } // Detect ZIP files from their file extension or MIME type. if ( 'zip' === $file->extension || 'application/zip' === $file->mime_type ) { $extracted_files = $this->extract_zip_file( $file ); if ( is_wp_error( $extracted_files ) ) { $file->error = $extracted_files; $this->maybe_unlink_file( $file ); continue; } if ( empty( $extracted_files ) ) { $file->error = new WP_Error( 'table_import_zip_file_empty', '', $file->name ); $this->maybe_unlink_file( $file ); continue; } /* * Remove the ZIP file from the list and instead append its contents. * Appending ensures recursiveness, as the appended files will be checked again. */ unset( $import_files[ $key ] ); array_push( $import_files, ...$extracted_files ); $this->maybe_unlink_file( $file ); } } unset( $file ); // Unset use-by-reference parameter of foreach loop. $import_files = array_merge( $import_files ); // Re-index. return $import_files; } /** * Extracts the files of a ZIP file and returns a list of files and their location. * * Depending on availability, either the PHP's ZipArchive class or WordPress' PclZip class is used. * * @since 2.0.0 * * @param File $zip_file File data of a ZIP file (likely in a temporary folder). * @return File[]|WP_Error List of files to import that were extracted from the ZIP file or WP_Error on failure. */ protected function extract_zip_file( File $zip_file ) /* : array|WP_Error */ { if ( class_exists( 'ZipArchive', false ) ) { $ziparchive_result = $this->extract_zip_file_ziparchive( $zip_file ); if ( is_array( $ziparchive_result ) ) { return $ziparchive_result; } } else { $ziparchive_result = new WP_Error( 'table_import_error_zip_open', '', array( 'ziparchive_error' => 'Class ZipArchive not available' ) ); } // Fall through to PclZip if ZipArchive is not available or encountered an error opening the file. $pclzip_result = $this->extract_zip_file_pclzip( $zip_file ); if ( is_wp_error( $pclzip_result ) ) { // Append the WP_Error from ZipArchive, to have all error information available. $pclzip_result->merge_from( $ziparchive_result ); } return $pclzip_result; } /** * Extracts the files of a ZIP file using the PHP ZipArchive class. * * The ZIP file is extracted to a temporary folder and a list of files and their location is returned. * * @since 2.3.0 * * @param File $zip_file File data of a ZIP file (likely in a temporary folder). * @return File[]|WP_Error List of files to import that were extracted from the ZIP file or WP_Error on failure. */ protected function extract_zip_file_ziparchive( File $zip_file ) /* : array|WP_Error */ { $archive = new ZipArchive(); $archive_opened = $archive->open( $zip_file->location, ZipArchive::CHECKCONS ); // If the ZIP file can't be opened with ZipArchive::CHECKCONS, try again without. if ( true !== $archive_opened ) { $archive_opened = $archive->open( $zip_file->location ); } // If the ZIP file can't even be opened without ZipArchive::CHECKCONS, bail. if ( true !== $archive_opened ) { return new WP_Error( 'table_import_error_zip_open', '', array( 'ziparchive_error' => $archive_opened ) ); } $files = array(); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase for ( $file_idx = 0; $file_idx < $archive->numFiles; $file_idx++ ) { $file_name = $archive->getNameIndex( $file_idx ); if ( false === $file_name ) { $files[] = new File( array( 'error' => new WP_Error( 'table_import_error_zip_stat', '', array( 'ziparchive_file_index' => $file_idx ) ), ) ); continue; } // Skip directories. if ( str_ends_with( $file_name, '/' ) ) { continue; } // Skip the __MACOSX directory that macOS adds to archives. if ( str_starts_with( $file_name, '__MACOSX/' ) ) { continue; } // Don't extract invalid files. if ( 0 !== validate_file( $file_name ) ) { continue; } $file_data = $archive->getFromIndex( $file_idx ); if ( false === $file_data ) { $files[] = new File( array( 'name' => $file_name, 'error' => new WP_Error( 'table_import_error_zip_get_data', '', array( 'ziparchive_file_index' => $file_idx, 'ziparchive_file_name' => $file_name ) ), ) ); continue; } $location = wp_tempnam(); $num_written_bytes = file_put_contents( $location, $file_data ); if ( false === $num_written_bytes || 0 === $num_written_bytes ) { @unlink( $location ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $files[] = new File( array( 'name' => $file_name, 'error' => new WP_Error( 'table_import_error_zip_write_temp_data', '', array( 'ziparchive_file_index' => $file_idx, 'ziparchive_file_name' => $file_name ) ), ) ); continue; } $files[] = new File( array( 'location' => $location, 'name' => $file_name, ) ); } $archive->close(); return $files; } /** * Extracts the files of a ZIP file using WordPress' PclZip class. * * The ZIP file is extracted to a temporary folder and a list of files and their location is returned. * * @since 2.3.0 * * @param File $zip_file File data of a ZIP file (likely in a temporary folder). * @return File[]|WP_Error List of files to import that were extracted from the ZIP file or WP_Error on failure. */ protected function extract_zip_file_pclzip( File $zip_file ) /* : array|WP_Error */ { mbstring_binary_safe_encoding(); require_once ABSPATH . 'wp-admin/includes/class-pclzip.php'; // @phpstan-ignore requireOnce.fileNotFound (This is a WordPress core file that always exists.) $archive = new PclZip( $zip_file->location ); $archive_files = $archive->extract( PCLZIP_OPT_EXTRACT_AS_STRING ); // @phpstan-ignore arguments.count (PclZip::extract() uses `func_get_args()` to handle optional arguments.) reset_mbstring_encoding(); // If the ZIP file can't be opened, bail. if ( ! is_array( $archive_files ) ) { return new WP_Error( 'table_import_error_zip_open', '', array( 'pclzip_error' => $archive->errorInfo( true ) ) ); } $files = array(); foreach ( $archive_files as $file ) { // Skip directories. if ( $file['folder'] ) { continue; } // Skip the __MACOSX directory that macOS adds to archives. if ( str_starts_with( $file['filename'], '__MACOSX/' ) ) { continue; } // Don't extract invalid files. if ( 0 !== validate_file( $file['filename'] ) ) { continue; } $location = wp_tempnam(); $num_written_bytes = file_put_contents( $location, $file['content'] ); if ( false === $num_written_bytes || 0 === $num_written_bytes ) { @unlink( $location ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $files[] = new File( array( 'name' => $file['filename'], 'error' => new WP_Error( 'table_import_error_zip_write_temp_data', '', array( 'ziparchive_file_index' => $file['index'], 'ziparchive_file_name' => $file['filename'] ) ), ) ); continue; } $files[] = new File( array( 'location' => $location, 'name' => $file['filename'], ) ); } return $files; } /** * Deletes a file unless the `keep_file` property is set to `true`. * * @since 2.0.0 * * @param File $file File that should maybe be deleted. */ protected function maybe_unlink_file( File $file ): void { if ( ! $file->keep_file && file_exists( $file->location ) ) { @unlink( $file->location ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged } } /** * Prepares a list of table names/IDs for use when replacing/appending existing tables (except for the JSON format). * * @since 2.0.0 * * @return array List of table names and IDs. */ protected function get_list_of_table_names(): array { $existing_tables = array(); // Load all table IDs and names for a comparison with the file name. $table_ids = TablePress::$model_table->load_all( false ); foreach ( $table_ids as $table_id ) { // Load table, without table data, options, and visibility settings. $table = TablePress::$model_table->load( $table_id, false, false ); if ( ! is_wp_error( $table ) ) { $existing_tables[ (string) $table['name'] ][] = $table_id; // Attention: The table name is not unique! } } return $existing_tables; } /** * Checks whether the requirements for the PHPSpreadsheet import class are fulfilled or if the legacy import class should be used. * * @since 2.0.0 * * @return bool Whether the legacy import class should be used. */ protected function should_use_legacy_import_class(): bool { // Allow overriding in the import config (coming e.g. from the import form UI). if ( $this->import_config['legacy_import'] ) { return true; } /** * Filters whether the Legacy Table Import class shall be used. * * @since 2.0.0 * * @param bool $use_legacy_class Whether to use the legacy table import class. Default false. */ if ( apply_filters( 'tablepress_use_legacy_table_import_class', false ) ) { return true; } // Use the legacy import class, if the requirements for PHPSpreadsheet are not fulfilled. $phpspreadsheet_requirements_fulfilled = extension_loaded( 'mbstring' ) && class_exists( 'ZipArchive', false ) && class_exists( 'DOMDocument', false ) && function_exists( 'simplexml_load_string' ) && ( function_exists( 'libxml_disable_entity_loader' ) || PHP_VERSION_ID >= 80000 ); // This function is only needed for older versions of PHP. if ( ! $phpspreadsheet_requirements_fulfilled ) { return true; } return false; } /** * Imports all found/extracted/configured files into TablePress. * * @since 2.0.0 * * @param File[] $import_files Files that shall be imported. * @return array{tables: array>, errors: File[]} Imported tables and files that caused errors. */ protected function import_files( array $import_files ): array { $tables = array(); $errors = array(); $use_legacy_import_class = $this->should_use_legacy_import_class(); // Load Import Base Class. TablePress::load_file( 'class-import-base.php', 'classes' ); // Choose the Table Import library based on the PHP version and the filter hook value. if ( $use_legacy_import_class ) { // @phpstan-ignore assign.propertyType (The `load_class()` method returns `object` and not a specific type.) $this->importer = TablePress::load_class( 'TablePress_Import_Legacy', 'class-import-legacy.php', 'classes' ); } else { // @phpstan-ignore assign.propertyType (The `load_class()` method returns `object` and not a specific type.) $this->importer = TablePress::load_class( 'TablePress_Import_PHPSpreadsheet', 'class-import-phpspreadsheet.php', 'classes' ); } // If there is more than one valid import file, ignore the chosen existing table for replacing/appending. if ( in_array( $this->import_config['type'], array( 'replace', 'append' ), true ) && '' !== $this->import_config['existing_table'] ) { $valid_import_files = 0; foreach ( $import_files as $file ) { if ( ! is_wp_error( $file->error ) ) { ++$valid_import_files; if ( $valid_import_files > 1 ) { $this->import_config['existing_table'] = ''; break; } } } } // Loop through all import files and import them. foreach ( $import_files as $file ) { if ( is_wp_error( $file->error ) ) { $errors[] = $file; continue; } // Use import method depending on chosen import class. if ( $use_legacy_import_class ) { $table = $this->load_table_from_file_legacy( $file ); } else { $table = $this->load_table_from_file_phpspreadsheet( $file ); } $this->maybe_unlink_file( $file ); if ( is_wp_error( $table ) ) { $file->error = $table; $errors[] = $file; continue; } $table = $this->save_imported_table( $table, $file ); if ( is_wp_error( $table ) ) { $file->error = $table; $errors[] = $file; continue; } $tables[] = $table; } return array( 'tables' => $tables, 'errors' => $errors, ); } /** * Loads a table from a file via the legacy import class. * * @since 2.0.0 * * @param File $file File with the table data. * @return array|WP_Error Loaded table on success (either with all properties or just 'data'), WP_Error on failure. */ protected function load_table_from_file_legacy( File $file ) /* : array|WP_Error */ { // Guess the import format from the file extension. switch ( $file->extension ) { case 'xlsx': // Excel (OfficeOpenXML) Spreadsheet. case 'xlsm': // Excel (OfficeOpenXML) Macro Spreadsheet (macros will be discarded). case 'xltx': // Excel (OfficeOpenXML) Template. case 'xltm': // Excel (OfficeOpenXML) Macro Template (macros will be discarded). $format = 'xlsx'; break; case 'xls': // Excel (BIFF) Spreadsheet. case 'xlt': // Excel (BIFF) Template. $format = 'xls'; break; case 'htm': case 'html': $format = 'html'; break; case 'csv': case 'tsv': $format = 'csv'; break; case 'json': $format = 'json'; break; default: // If no format was found, try finding the format from the first character below. $format = ''; } $data = file_get_contents( $file->location ); if ( false === $data ) { return new WP_Error( 'table_import_legacy_data_read', '', $file->location ); } if ( '' === $data ) { return new WP_Error( 'table_import_legacy_data_empty', '', $file->location ); } // If no format could be determined from the file extension, try guessing from the file content. if ( '' === $format ) { $data = trim( $data ); $first_character = $data[0]; $last_character = $data[-1]; if ( '<' === $first_character && '>' === $last_character ) { $format = 'html'; } elseif ( ( '[' === $first_character && ']' === $last_character ) || ( '{' === $first_character && '}' === $last_character ) ) { $json_table = json_decode( $data, true ); if ( ! is_null( $json_table ) ) { $format = 'json'; } } } // Fall back to CSV if no file format could be determined. if ( '' === $format ) { $format = 'csv'; } if ( ! in_array( $format, $this->importer->import_formats, true ) ) { // @phpstan-ignore property.notFound (`$this->importer` is an instance of `TablePress_Import_Legacy` which has the property `import_formats`.) return new WP_Error( 'table_import_legacy_unknown_format', '', $file->name ); } $table = $this->importer->import_table( $format, $data ); if ( false === $table ) { return new WP_Error( 'table_import_legacy_importer_failed', '', array( 'file_name' => $file->name, 'file_format' => $format ) ); } return $table; } /** * Loads a table from a file via the PHPSpreadsheet import class. * * @since 2.0.0 * * @param File $file File with the table data. * @return array|WP_Error Loaded table on success (either with all properties or just 'data'), WP_Error on failure. */ protected function load_table_from_file_phpspreadsheet( File $file ) /* : array|WP_Error */ { // Convert File object to array, as those are not yet used outside of this class. return $this->importer->import_table( $file ); // @phpstan-ignore return.type (This is an instance of TablePress_Import_PHPSpreadsheet which does not return false.) } /** * Imports a loaded table into TablePress. * * @since 2.0.0 * * @param array $table The table to be imported, either with properties or just the $table['data'] property set. * @param File $file File with the table data. * @return array|WP_Error Imported table on success, WP_Error on failure. */ protected function save_imported_table( array $table, File $file ) /* : array|WP_Error */ { // If name and description are imported from a new table, use those. if ( ! isset( $table['name'] ) ) { $table['name'] = $file->name; } if ( ! isset( $table['description'] ) ) { $table['description'] = $file->name; } $import_type = $this->import_config['type']; $existing_table_id = $this->import_config['existing_table']; // If no existing table ID has been set (or if we are importing multiple tables), try to find a potential existing table from the table ID in the import data or by comparing the file name with the table name. if ( in_array( $import_type, array( 'replace', 'append' ), true ) && '' === $existing_table_id ) { if ( isset( $table['id'] ) ) { // If the table already contained a table ID (e.g. for the JSON format), use that. $existing_table_id = $table['id']; } elseif ( isset( $this->table_names_ids[ $file->name ] ) && 1 === count( $this->table_names_ids[ $file->name ] ) ) { // Use the replace/append ID of tables where the table name matches the file name, but only if there was exactly one file name match. $existing_table_id = $this->table_names_ids[ $file->name ][0]; } } // If the table that is to be replaced or appended to does not exist, add the new table instead. if ( ! TablePress::$model_table->table_exists( $existing_table_id ) ) { $existing_table_id = ''; $import_type = 'add'; } $table = $this->import_tablepress_table( $table, $import_type, $existing_table_id ); return $table; } /** * Imports a table by either replacing or appending to an existing table or by adding it as a new table. * * @since 1.0.0 * * @param array $imported_table The table to be imported, either with properties or just the `name`, `description`, and `data` property set. * @param string $import_type What to do with the imported data: "add", "replace", "append". * @param string $existing_table_id Empty string if table shall be added as a new table, ID of the table to be replaced or appended to otherwise. * @return array|WP_Error Table on success, WP_Error on error. */ protected function import_tablepress_table( array $imported_table, string $import_type, string $existing_table_id ) /* : array|WP_Error */ { // Full JSON format table can contain a table ID, try to keep that, by later changing the imported table ID to this. $table_id_in_import = $imported_table['id'] ?? ''; // To be able to replace or append to a table, the user must be able to edit the table, or it must be a request via the Automatic Periodic Table Import module. if ( in_array( $import_type, array( 'replace', 'append' ), true ) && ! ( current_user_can( 'tablepress_edit_table', $existing_table_id ) || doing_action( 'tablepress_automatic_periodic_table_import_action' ) ) ) { return new WP_Error( 'table_import_replace_append_capability_check_failed', '', $existing_table_id ); } switch ( $import_type ) { case 'add': $existing_table = TablePress::$model_table->get_table_template(); // Import visibility information if it exists, usually only for the JSON format. if ( isset( $imported_table['visibility'] ) ) { $existing_table['visibility'] = $imported_table['visibility']; } break; case 'replace': // Load table, without table data, but with options and visibility settings. $existing_table = TablePress::$model_table->load( $existing_table_id, false, true ); if ( is_wp_error( $existing_table ) ) { $error = new WP_Error( 'table_import_replace_table_load', '', $existing_table_id ); $error->merge_from( $existing_table ); return $error; } // Don't change name and description when a table is replaced. $imported_table['name'] = $existing_table['name']; $imported_table['description'] = $existing_table['description']; // Replace visibility information if it exists. if ( isset( $imported_table['visibility'] ) ) { $existing_table['visibility'] = $imported_table['visibility']; } break; case 'append': // Load table, with table data, options, and visibility settings. $existing_table = TablePress::$model_table->load( $existing_table_id, true, true ); if ( is_wp_error( $existing_table ) ) { $error = new WP_Error( 'table_import_append_table_load', '', $existing_table_id ); $error->merge_from( $existing_table ); return $error; } if ( isset( $existing_table['is_corrupted'] ) && $existing_table['is_corrupted'] ) { return new WP_Error( 'table_import_append_table_load_corrupted', '', $existing_table_id ); } // Don't change name and description when a table is appended to. $imported_table['name'] = $existing_table['name']; $imported_table['description'] = $existing_table['description']; // Actual appending:. $imported_table['data'] = array_merge( $existing_table['data'], $imported_table['data'] ); $this->importer->pad_array_to_max_cols( $imported_table['data'] ); // Append visibility information for rows. if ( isset( $imported_table['visibility']['rows'] ) ) { $existing_table['visibility']['rows'] = array_merge( $existing_table['visibility']['rows'], $imported_table['visibility']['rows'] ); } // When appending, do not overwrite options, e.g. coming from a JSON file. unset( $imported_table['options'] ); break; default: return new WP_Error( 'table_import_import_type_invalid', '', $import_type ); } // Merge new or existing table with information from the imported table. $imported_table['id'] = $existing_table['id']; // Will be false for new table or the existing table ID. // Cut visibility array (if the imported table is smaller), and pad correctly if imported table is bigger than existing table (or new template). $num_rows = count( $imported_table['data'] ); $num_columns = count( $imported_table['data'][0] ); $imported_table['visibility'] = array( 'rows' => array_pad( array_slice( $existing_table['visibility']['rows'], 0, $num_rows ), $num_rows, 1 ), 'columns' => array_pad( array_slice( $existing_table['visibility']['columns'], 0, $num_columns ), $num_columns, 1 ), ); // Check if the new table data is valid and consistent. $table = TablePress::$model_table->prepare_table( $existing_table, $imported_table, false ); if ( is_wp_error( $table ) ) { $error = new WP_Error( 'table_import_table_prepare', '', $imported_table['id'] ); $error->merge_from( $table ); return $error; } // DataTables Custom Commands can only be edit by trusted users. if ( ! current_user_can( 'unfiltered_html' ) ) { $table['options']['datatables_custom_commands'] = $existing_table['options']['datatables_custom_commands']; } // Replace existing table or add new table. if ( in_array( $import_type, array( 'replace', 'append' ), true ) ) { // Replace existing table with imported/appended table. $table_id = TablePress::$model_table->save( $table ); } else { // Add the imported table (and get its first ID). $table_id = TablePress::$model_table->add( $table ); } if ( is_wp_error( $table_id ) ) { $error = new WP_Error( 'table_import_table_save_or_add', '', $table['id'] ); $error->merge_from( $table_id ); return $error; } // Try to use ID from imported file (e.g. in full JSON format table). if ( '' !== $table_id_in_import && $table_id !== $table_id_in_import && current_user_can( 'tablepress_edit_table_id', $table_id ) ) { $id_changed = TablePress::$model_table->change_table_id( $table_id, $table_id_in_import ); if ( ! is_wp_error( $id_changed ) ) { $table_id = $table_id_in_import; } } $table['id'] = $table_id; return $table; } /** * Imports a table in legacy versions of the Table Auto Update Extension. * * This method is deprecated and is only left for backward compatibility reasons. Do not use this in new code! * * @since 1.0.0 * @deprecated 2.0.0 Use `run()` instead. * * @param string $format Import format. * @param string $data Data to import. * @return array|WP_Error|false Table array on success, WP_Error or false on error. */ public function import_table( string $format, string $data ) /* : array|false */ { TablePress::load_file( 'class-import-base.php', 'classes' ); $importer = TablePress::load_class( 'TablePress_Import_Legacy', 'class-import-legacy.php', 'classes' ); return $importer->import_table( $format, $data ); } } // class TablePress_Import PK!i9!! class-elementor-widget-table.phpnu[> Widget stack. */ public function get_stack( $with_common_controls = true ) { $stack = parent::get_stack( $with_common_controls ); // @phpstan-ignore staticMethod.notFound (Elementor methods are not in the stubs.) unset( $stack['tabs']['advanced'] ); return $stack; } /** * Gets the widget upsale data. * * @since 3.1.0 * * @return array Widget upsale data. */ protected function get_upsale_data(): array { return array( 'condition' => tb_tp_fs()->is_free_plan(), 'image' => plugins_url( 'admin/img/tablepress.svg', TABLEPRESS__FILE__ ), 'image_alt' => esc_attr__( 'Upgrade to TablePress Pro!', 'tablepress' ), 'title' => esc_html__( 'Upgrade to TablePress Pro!', 'tablepress' ), 'description' => esc_html__( 'Check out the TablePress premium versions and give your tables super powers!', 'tablepress' ), 'upgrade_url' => 'https://tablepress.org/premium/?utm_source=plugin&utm_medium=button&utm_content=elementor-widget', 'upgrade_text' => esc_html__( 'Upgrade Now', 'tablepress' ), ); } /** * Gets whether the widget requires an inner wrapper. * * This is used to determine whether to optimize the DOM size. * * @since 3.1.0 * * @return bool Whether to optimize the DOM size. */ public function has_widget_inner_wrapper(): bool { return false; } /** * Gets whether the element returns dynamic content. * * This is used to determine whether to cache the element output or not. * * @since 3.1.0 * * @return bool Whether to cache the element output. */ protected function is_dynamic_content(): bool { return true; } /** * Registers the widget controls. * * Adds input fields to allow the user to customize the widget settings. * * @since 3.1.0 */ protected function register_controls(): void { $this->start_controls_section( // @phpstan-ignore method.notFound (Elementor methods are not in the stubs.) 'table', array( 'label' => esc_html__( 'Table', 'tablepress' ), 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, // @phpstan-ignore classConstant.notFound (Elementor constants are not in the stubs.) ), ); $tables = array(); // Load all table IDs without priming the post meta cache, as table options/visibility are not needed. $table_ids = \TablePress::$model_table->load_all( false ); foreach ( $table_ids as $table_id ) { // Load table, without table data, options, and visibility settings. $table = \TablePress::$model_table->load( $table_id, false, false ); // Skip tables that could not be loaded. if ( is_wp_error( $table ) ) { continue; } if ( '' === trim( $table['name'] ) ) { $table['name'] = __( '(no name)', 'tablepress' ); } $tables[ $table_id ] = esc_html( sprintf( __( 'ID %1$s: %2$s', 'tablepress' ), $table_id, $table['name'] ) ); } /** * Filters the list of table IDs and names that is passed to the block editor, and is then used in the dropdown of the TablePress table block. * * @since 2.0.0 * * @param array $tables List of table names, the table ID is the array key. */ $tables = apply_filters( 'tablepress_block_editor_tables_list', $tables ); $this->add_control( // @phpstan-ignore method.notFound 'table_id', array( 'label' => esc_html__( 'Table:', 'tablepress' ), 'show_label' => false, 'label_block' => true, 'description' => esc_html__( 'Select the TablePress table that you want to embed.', 'tablepress' ) . ( current_user_can( 'tablepress_list_tables' ) ? sprintf( ' %2$s', esc_url( \TablePress::url( array( 'action' => 'list' ) ) ), esc_html__( 'Manage your tables.', 'tablepress' ) ) : '' ), 'type' => \Elementor\Controls_Manager::SELECT2, // @phpstan-ignore classConstant.notFound (Elementor constants are not in the stubs.) 'ai' => array( 'active' => false ), 'options' => $tables, ), ); $this->end_controls_section(); // @phpstan-ignore method.notFound (Elementor methods are not in the stubs.) $this->start_controls_section( // @phpstan-ignore method.notFound (Elementor methods are not in the stubs.) 'advanced', array( 'label' => esc_html__( 'Advanced', 'tablepress' ), 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, // @phpstan-ignore classConstant.notFound (Elementor constants are not in the stubs.) 'condition' => array( 'id!' => '', ), ), ); $this->add_control( // @phpstan-ignore method.notFound (Elementor methods are not in the stubs.) 'parameters', array( 'label' => esc_html__( 'Configuration parameters:', 'tablepress' ), 'label_block' => true, 'description' => esc_html( __( 'These additional parameters can be used to modify specific table features.', 'tablepress' ) . ' ' . __( 'See the TablePress Documentation for more information.', 'tablepress' ) ), 'type' => \Elementor\Controls_Manager::TEXT, // @phpstan-ignore classConstant.notFound (Elementor constants are not in the stubs.) 'input_type' => 'text', 'placeholder' => '', 'ai' => array( 'active' => false ), ), ); $this->end_controls_section(); // @phpstan-ignore method.notFound (Elementor methods are not in the stubs.) } /** * Render oEmbed widget output on the frontend. * * Written in PHP and used to generate the final HTML. * * @since 3.1.0 */ protected function render(): void { $settings = $this->get_settings_for_display(); // @phpstan-ignore method.notFound (Elementor methods are not in the stubs.) // Don't return anything if no table was selected. if ( empty( $settings['table_id'] ) ) { /* * In TablePress 3.1 (before 3.1.1), the widget control was named "id" instead of "table_id", which however caused problems. * To ensure that tables will continue to be shown, if the widget was created with 3.1, the "table_id" is set to the "id" value, if only that exists. */ if ( empty( $settings['id'] ) ) { return; } else { $settings['table_id'] = $settings['id']; } } if ( '' !== trim( $settings['parameters'] ) ) { $render_attributes = shortcode_parse_atts( $settings['parameters'] ); } else { $render_attributes = array(); } $render_attributes['id'] = $settings['table_id']; /* * It would be nice to print only the Shortcode, for better data portability, e.g. if a site switches away from Elementor. * However, the editor will then only render the Shortcode itself, which is not very helpful. * Due to this, the table HTML code is rendered. * echo '[' . \TablePress::$shortcode . " id={$settings['table_id']} {$settings['parameters']} /]"; */ echo \TablePress::$controller->shortcode_table( $render_attributes ); } } // class TablePressTableWidget PK!Gclass-admin-page-helper.phpnu[ $script_data Optional. JS data that is printed to the page before the script is included. The array key will be used as the name, the value will be JSON encoded. */ public function enqueue_script( string $name, array $dependencies = array(), array $script_data = array() ): void { $js_file = "admin/js/build/{$name}.js"; $js_url = plugins_url( $js_file, TABLEPRESS__FILE__ ); $version = TablePress::version; // Load dependencies and version from the auto-generated asset PHP file. $script_asset_path = TABLEPRESS_ABSPATH . "admin/js/build/{$name}.asset.php"; if ( file_exists( $script_asset_path ) ) { $script_asset = require $script_asset_path; if ( isset( $script_asset['dependencies'] ) ) { $dependencies = array_merge( $dependencies, $script_asset['dependencies'] ); } if ( isset( $script_asset['version'] ) ) { $version = $script_asset['version']; } } /* * Register the `react-jsx-runtime` polyfill, if it is not already registered. * This is needed as a polyfill for WP < 6.6, and can be removed once WP 6.6 is the minimum requirement for TablePress. */ if ( ! wp_script_is( 'react-jsx-runtime', 'registered' ) ) { wp_register_script( 'react-jsx-runtime', plugins_url( 'admin/js/react-jsx-runtime.min.js', TABLEPRESS__FILE__ ), array( 'react' ), TablePress::version, true ); } /** * Filters the dependencies of a TablePress script file. * * @since 2.0.0 * * @param string[] $dependencies List of the dependencies that the $name script relies on. * @param string $name Name of the JS script, without extension. */ $dependencies = apply_filters( 'tablepress_admin_page_script_dependencies', $dependencies, $name ); wp_enqueue_script( "tablepress-{$name}", $js_url, $dependencies, $version, true ); // Load JavaScript translation files, for all scripts that rely on `wp-i18n`. if ( in_array( 'wp-i18n', $dependencies, true ) ) { wp_set_script_translations( "tablepress-{$name}", 'tablepress' ); } if ( ! empty( $script_data ) ) { foreach ( $script_data as $var_name => $var_data ) { $var_data = wp_json_encode( $var_data, JSON_FORCE_OBJECT | JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ); wp_add_inline_script( "tablepress-{$name}", "const tablepress_{$var_name} = {$var_data};", 'before' ); } } } /** * Register a filter hook on the admin footer. * * @since 1.0.0 */ public function add_admin_footer_text(): void { // Show admin footer message (only on TablePress admin screens). add_filter( 'admin_footer_text', array( $this, '_admin_footer_text' ) ); } /** * Adds a TablePress "Thank You" message to the admin footer content. * * @since 1.0.0 * * @param string $content Current admin footer content. * @return string New admin footer content. */ public function _admin_footer_text( /* string */ $content ): string { // Don't use a type hint in the method declaration as many WordPress plugins use the `admin_footer_text` filter in the wrong way. // Protect against other plugins not returning a string in their filter callbacks. if ( ! is_string( $content ) ) { // @phpstan-ignore function.alreadyNarrowedType (The `is_string()` check is needed as the input is coming from a filter hook.) $content = ''; } $content .= ' • ' . sprintf( __( 'Thank you for using TablePress.', 'tablepress' ), 'https://tablepress.org/' ); if ( tb_tp_fs()->is_free_plan() ) { $content .= ' ' . sprintf( __( 'Take a look at the Premium features!', 'tablepress' ), 'https://tablepress.org/premium/?utm_source=plugin&utm_medium=textlink&utm_content=admin-footer' ); } return $content; } /** * Print the JavaScript code for a WP feature pointer. * * @since 1.0.0 * * @param string $pointer_id The pointer ID. * @param string $selector The HTML elements, on which the pointer should be attached. * @param array $args Arguments to be passed to the pointer JS (see wp-pointer.js). */ public function print_wp_pointer_js( string $pointer_id, string $selector, array $args ): void { if ( empty( $pointer_id ) || empty( $selector ) || empty( $args['content'] ) ) { return; } /* * Print JS code for the feature pointers, extended with event handling for opened/closed "Screen Options", so that pointers can * be repositioned. 210 ms is slightly slower than jQuery's "fast" value, to allow all elements to reach their original position. */ ?> > $table_data Table data in which formulas shall be evaluated. * @param string $table_id ID of the passed table. * @return array> Table data with evaluated formulas. */ public function evaluate_table_data( array $table_data, string $table_id ): array { // Choose the Table Evaluate library based on the PHP version and the filter hook value. if ( $this->_should_use_legacy_evaluate_class() ) { $evaluate_class = TablePress::load_class( 'TablePress_Evaluate_Legacy', 'class-evaluate-legacy.php', 'classes' ); } else { $evaluate_class = TablePress::load_class( 'TablePress_Evaluate_PHPSpreadsheet', 'class-evaluate-phpspreadsheet.php', 'classes' ); } return $evaluate_class->evaluate_table_data( $table_data, $table_id ); } } // class TablePress_Evaluate PK!__class-meta-boxes.phpnu[ID; $settings_tabs_field = new settings_tabs_field(); $post_grid_settings_tab = array(); $post_grid_settings_tab[] = array( 'id' => 'layout_builder', 'title' => sprintf(__('%s Layout editor', 'post-grid'), ''), 'priority' => 4, 'active' => true, ); $post_grid_settings_tab[] = array( 'id' => 'custom_scripts', 'title' => sprintf(__('%s Custom scripts', 'post-grid'), ''), 'priority' => 5, 'active' => false, ); $post_grid_settings_tab = apply_filters('post_grid_layout_metabox_navs', $post_grid_settings_tab); $tabs_sorted = array(); foreach ($post_grid_settings_tab as $page_key => $tab) $tabs_sorted[$page_key] = isset($tab['priority']) ? $tab['priority'] : 0; array_multisort($tabs_sorted, SORT_ASC, $post_grid_settings_tab); wp_enqueue_script('jquery'); wp_enqueue_script('jquery-ui-sortable'); wp_enqueue_script('jquery-ui-core'); wp_enqueue_script('jquery-ui-accordion'); wp_enqueue_script('wp-color-picker'); wp_enqueue_style('wp-color-picker'); wp_enqueue_style('jquery-ui'); wp_enqueue_style('font-awesome-5'); wp_enqueue_style('settings-tabs'); wp_enqueue_script('settings-tabs'); wp_enqueue_style('post-grid-output', post_grid_plugin_url . 'dist/output.css', [], time(), 'all'); ?>
  • data_visible="" class="tab-nav " data-id="">
ID; $post_grid_meta_options = get_post_meta($post_id, 'post_grid_meta_options', true); $grid_type = $post_types = !empty($post_grid_meta_options['grid_type']) ? $post_grid_meta_options['grid_type'] : 'grid'; $masonry_enable = $post_types = !empty($post_grid_meta_options['masonry_enable']) ? $post_grid_meta_options['masonry_enable'] : 'no'; //$grid_type = ($masonry_enable == 'yes') ? 'masonry' : $grid_type; $current_tab = isset($post_grid_meta_options['current_tab']) ? $post_grid_meta_options['current_tab'] : 'query_post'; $settings_tabs_field = new settings_tabs_field(); $settings_tabs = array(); $settings_tabs[] = array( 'id' => 'shortcode', 'title' => sprintf(__('%s Shortcode', 'post-grid'), ''), 'priority' => 5, 'active' => ($current_tab == 'shortcode') ? true : false, ); $settings_tabs[] = array( 'id' => 'general', 'title' => sprintf(__('%s General', 'post-grid'), ''), 'priority' => 10, 'active' => ($current_tab == 'general') ? true : false, ); $settings_tabs[] = array( 'id' => 'query_post', 'title' => sprintf(__('%s Query Post', 'post-grid'), ''), 'priority' => 15, 'active' => ($current_tab == 'query_post') ? true : false, ); // $settings_tabs[] = array( // 'id' => 'skin_layout', // 'title' => sprintf(__('%s Skin & Layout (Old)','post-grid'), ''), // 'priority' => 20, // 'active' => ($current_tab == 'skin_layout') ? true : false, // ); $settings_tabs[] = array( 'id' => 'grid_settings', 'title' => sprintf(__('%s Grid settings', 'post-grid'), ''), 'priority' => 30, 'active' => ($current_tab == 'grid_settings') ? true : false, 'data_visible' => 'grid ', 'hidden' => ((($grid_type == 'slider') ? true : false) || (($grid_type == 'masonry') ? true : false) || (($grid_type == 'justified') ? true : false) || (($grid_type == 'filterable') ? true : false) || (($grid_type == 'tiles') ? true : false)), ); $settings_tabs[] = array( 'id' => 'layouts', 'title' => sprintf(__('%s Layouts', 'post-grid'), ''), 'priority' => 35, 'active' => ($current_tab == 'layouts') ? true : false, ); $settings_tabs[] = array( 'id' => 'item_style', 'title' => sprintf(__('%s Item style', 'post-grid'), ''), 'priority' => 38, 'active' => ($current_tab == 'item_style') ? true : false, ); $settings_tabs[] = array( 'id' => 'masonry', 'title' => sprintf(__('%s Masonry', 'post-grid'), ''), 'priority' => 40, 'active' => ($current_tab == 'masonry') ? true : false, 'data_visible' => 'masonry', 'hidden' => ((($grid_type == 'grid') ? true : false) || (($grid_type == 'tiles') ? true : false) || (($grid_type == 'slider') ? true : false) || (($grid_type == 'justified') ? true : false) || (($grid_type == 'filterable') ? true : false) || (($grid_type == 'glossary') ? true : false)), ); $settings_tabs[] = array( 'id' => 'justified', 'title' => sprintf(__('%s Justified', 'post-grid'), ''), 'priority' => 40, 'active' => ($current_tab == 'justified') ? true : false, 'data_visible' => 'justified', 'hidden' => ((($grid_type == 'grid') ? true : false) || (($grid_type == 'tiles') ? true : false) || (($grid_type == 'slider') ? true : false) || (($grid_type == 'masonry') ? true : false) || (($grid_type == 'filterable') ? true : false) || (($grid_type == 'glossary') ? true : false)), ); $settings_tabs[] = array( 'id' => 'tiles', 'title' => sprintf(__('%s Tiles', 'post-grid'), ''), 'priority' => 40, 'active' => ($current_tab == 'tiles') ? true : false, 'data_visible' => 'tiles', 'hidden' => ((($grid_type == 'grid') ? true : false) || (($grid_type == 'slider') ? true : false) || (($grid_type == 'masonry') ? true : false) || (($grid_type == 'filterable') ? true : false)) || (($grid_type == 'justified') ? true : false) || (($grid_type == 'glossary') ? true : false), ); $settings_tabs[] = array( 'id' => 'pagination', 'title' => sprintf(__('%s Pagination', 'post-grid'), ''), 'priority' => 45, 'active' => ($current_tab == 'pagination') ? true : false, 'data_visible' => ' grid justified masonry tiles glossary timeline filterable collapsible', 'hidden' => ($grid_type == 'slider') ? true : false, ); $settings_tabs[] = array( 'id' => 'custom_scripts', 'title' => sprintf(__('%s Custom Scripts', 'post-grid'), ''), 'priority' => 50, 'active' => ($current_tab == 'custom_scripts') ? true : false, ); $settings_tabs[] = array( 'id' => 'search', 'title' => sprintf(__('%s Search', 'post-grid'), ''), 'priority' => 55, 'active' => ($current_tab == 'search') ? true : false, ); $settings_tabs = apply_filters('post_grid_metabox_tabs', $settings_tabs); $tabs_sorted = array(); foreach ($settings_tabs as $page_key => $tab) $tabs_sorted[$page_key] = isset($tab['priority']) ? $tab['priority'] : 0; array_multisort($tabs_sorted, SORT_ASC, $settings_tabs); wp_enqueue_style('post-grid-output', post_grid_plugin_url . 'dist/output.css', [], time(), 'all'); ?>
'Grid', 'thumb' => post_grid_plugin_url . 'assets/images/grid.png',); $view_types_args['masonry'] = array('name' => 'Masonry', 'thumb' => post_grid_plugin_url . 'assets/images/masonry.png',); $view_types_args['justified'] = array('name' => 'Justified', 'thumb' => post_grid_plugin_url . 'assets/images/justified.png',); $view_types_args['tiles'] = array('name' => 'Tiles', 'thumb' => post_grid_plugin_url . 'assets/images/tiles.png',); $view_types_args['filterable'] = array('name' => 'Filterable', 'disabled' => true, 'pro_msg' => 'Pro', 'thumb' => post_grid_plugin_url . 'assets/images/filterable.png',); $view_types_args['glossary'] = array('name' => 'Glossary', 'disabled' => true, 'pro_msg' => 'Pro', 'thumb' => post_grid_plugin_url . 'assets/images/glossary.png',); $view_types_args['slider'] = array('name' => 'Carousel', 'disabled' => true, 'pro_msg' => 'Pro', 'thumb' => post_grid_plugin_url . 'assets/images/carousel.png',); $view_types_args = apply_filters('post_grid_view_types', $view_types_args); ?>
'grid_type', 'parent' => 'post_grid_meta_options', 'title' => __('View Type', 'post-grid'), 'details' => '', 'type' => 'radio_image', 'value' => $grid_type, 'default' => '', 'width' => '100px', 'lazy_load_img' => post_grid_plugin_url . 'assets/images/loading.gif', 'args' => $view_types_args, ); $settings_tabs_field->generate_field($args); ?>
  • data_visible="" class="tab-nav " data-id="">
  • Post Grid Version:

Try Pro

Buy Pro

If you are looking some extra feature you may try our premium version.


Documentation

Documentation

Before asking, submitting reviews please take a look on our documentation, may help your issue fast.


Video Tutorials

Go to YouTube

Looking for support?

Create Support Ticket

Its free and you can ask any question about our plugins and get support fast.


Provide your feedback

Submit Reviews Ask wordpress.org

We spent thousand+ hours to development on this plugin, please submit your reviews wisely.

If you have any issue with this plugin please submit our forums or contact our support first.

Your feedback and reviews are most important things to keep our development on track. If you have time please submit us five star reviews.

ID; $post_grid_post_settings = get_post_meta($post_id, 'post_grid_post_settings', true); $post_grid_settings_tab = array(); $current_tab = isset($post_grid_post_settings['current_tab']) ? $post_grid_post_settings['current_tab'] : 'options'; $post_grid_settings_tab[] = array( 'id' => 'options', 'title' => sprintf(__('%s Options', 'post-grid'), ''), 'priority' => 1, 'active' => ($current_tab == 'options') ? true : false, ); $post_grid_settings_tabs = apply_filters('post_grid_post_options_tabs', $post_grid_settings_tab); $tabs_sorted = array(); foreach ($post_grid_settings_tabs as $page_key => $tab) $tabs_sorted[$page_key] = isset($tab['priority']) ? $tab['priority'] : 0; array_multisort($tabs_sorted, SORT_ASC, $post_grid_settings_tabs); $settings_tabs_field = new settings_tabs_field(); $settings_tabs_field->admin_scripts(); ?>
  • data_visible="" class="tab-nav " data-id="">
"", ), $atts ); $post_id = isset($atts['id']) ? (int) $atts['id'] : ''; $post_id = str_replace('"', "", $post_id); $post_id = str_replace("'", "", $post_id); $post_id = str_replace("'", "", $post_id); $post_id = str_replace(""", "", $post_id); $post_data = get_post($post_id); $post_content = isset($post_data->post_content) ? $post_data->post_content : ""; //var_dump($post_content); //echo "
"; $post_content = ($post_content); //var_dump($post_content); $PostGridData = (array) json_decode($post_content, true); $globalOptions = isset($PostGridData["globalOptions"]) ? $PostGridData["globalOptions"] : []; $viewType = isset($globalOptions["viewType"]) ? $globalOptions["viewType"] : "viewGrid"; //var_dump($viewType); ob_start(); do_action("post_grid_builder_" . $viewType, $post_id, $PostGridData); if ($viewType == "viewGrid") { //wp_enqueue_script('post_grid_front_scripts'); // wp_enqueue_style('post_grid_animate'); } return ob_get_clean(); } public function post_grid_new_display($atts, $content = null) { $atts = shortcode_atts( array( 'id' => "", ), $atts ); $atts = apply_filters('post_grid_atts', $atts); $grid_id = $atts['id']; ob_start(); if (empty($grid_id)) { echo 'Please provide valid post grid id, ex: [post_grid id="123"]'; return; } //include( post_grid_plugin_dir . 'templates/post-grid.php'); do_action('post_grid_main', $atts); wp_enqueue_script('post-grid-shortcode-scripts'); wp_localize_script('post-grid-shortcode-scripts', 'post_grid_ajax', array( 'post_grid_ajaxurl' => admin_url('admin-ajax.php'), '_wpnonce' => wp_create_nonce('post_grid_ajax_nonce'), )); return ob_get_clean(); } } new class_post_grid_shortcodes(); PK!RDRDclass-functions.phpnu[ 'post_grid_template'); $the_query = new \WP_Query($args); if ($the_query->have_posts()) { while ($the_query->have_posts()) { $the_query->the_post(); $post_id = get_the_ID(); $post_title = get_the_title(); $posts[$post_id] = $post_title; } wp_reset_postdata(); } return $posts; } function get_query_orderby() { $args['ID'] = __('ID', 'post-grid'); $args['author'] = __('Author', 'post-grid'); $args['title'] = __('Title', 'post-grid'); $args['name'] = __('Name', 'post-grid'); $args['type'] = __('Type', 'post-grid'); $args['date'] = __('Date', 'post-grid'); $args['post_date'] = __('post_date', 'post-grid'); $args['modified'] = __('modified', 'post-grid'); $args['parent'] = __('Parent', 'post-grid'); $args['rand'] = __('Random', 'post-grid'); $args['comment_count'] = __('Comment count', 'post-grid'); $args['menu_order'] = __('Menu order', 'post-grid'); $args['meta_value'] = __('Meta value', 'post-grid'); $args['meta_value_num'] = __('Meta Value(number)', 'post-grid'); $args['post__in'] = __('post__in', 'post-grid'); $args['post_name__in'] = __('post_name__in', 'post-grid'); // $args['tec_event_start_date'] = __('tec_event_start_date', 'post-grid'); return apply_filters('post_grid_orderby', $args); } function get_post_status() { $args['publish'] = __('Publish', 'post-grid'); $args['pending'] = __('Pending', 'post-grid'); $args['draft'] = __('Draft', 'post-grid'); $args['auto-draft'] = __('Auto draft', 'post-grid'); $args['future'] = __('Future', 'post-grid'); $args['private'] = __('Private', 'post-grid'); $args['inherit'] = __('Inherit', 'post-grid'); $args['trash'] = __('Trash', 'post-grid'); $args['any'] = __('Any', 'post-grid'); // $args['tribe-ea-success'] = __('tribe-ea-success', 'post-grid'); // $args['tribe-ea-failed'] = __('tribe-ea-failed', 'post-grid'); // $args['tribe-ea-schedule'] = __('tribe-ea-schedule', 'post-grid'); // $args['tribe-ea-pending'] = __('tribe-ea-pending', 'post-grid'); // $args['tribe-ea-draft'] = __('tribe-ea-draft', 'post-grid'); return apply_filters('post_grid_post_status', $args); } public function media_source() { $media_source = array( 'featured_image' => array('id' => 'featured_image', 'title' => __('Featured Image', 'post-grid'), 'checked' => 'yes'), 'first_image' => array('id' => 'first_image', 'title' => __('First images from content', 'post-grid'), 'checked' => 'yes'), 'empty_thumb' => array('id' => 'empty_thumb', 'title' => __('Empty thumbnail', 'post-grid'), 'checked' => 'yes'), ); $media_source = apply_filters('post_grid_filter_media_source', $media_source); return $media_source; } public function layout_items() { $layout_items['general'] = array( 'name' => 'General', 'description' => 'Default WordPress items for post.', 'items' => array( 'title' => array( 'name' => 'Title', 'dummy_html' => 'Lorem Ipsum is simply.', 'css' => 'display: block;font-size: 21px;line-height: normal;padding: 5px 10px;text-align: left;', ), 'title_link' => array( 'name' => 'Title with Link', 'dummy_html' => 'Lorem Ipsum is simply', 'css' => 'display: block;font-size: 21px;line-height: normal;padding: 5px 10px;text-align: left;', ), 'content' => array( 'name' => 'Content', 'dummy_html' => 'Lorem', 'css' => 'display: block;font-size: 13px;line-height: normal;padding: 5px 10px;text-align: left;', ), 'read_more' => array( 'name' => 'Read more', 'dummy_html' => 'Read more', 'css' => 'display: block;font-size: 13px;line-height: normal;padding: 5px 10px;text-align: left;', ), 'thumb' => array( 'name' => 'Thumbnail', 'dummy_html' => '', 'css' => 'display: block;font-size: 13px;line-height: normal;padding: 5px 10px;text-align: left;', ), 'thumb_link' => array( 'name' => 'Thumbnail with Link', 'dummy_html' => '', 'css' => 'display: block;font-size: 13px;line-height: normal;padding: 5px 10px;text-align: left;', ), 'excerpt' => array( 'name' => 'Excerpt', 'dummy_html' => 'Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry\'s standard dummy text', 'css' => 'display: block;font-size: 13px;line-height: normal;padding: 5px 10px;text-align: left;', ), 'excerpt_read_more' => array( 'name' => 'Excerpt with Read more', 'dummy_html' => 'Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry\'s standard dummy text Read more', 'css' => 'display: block;font-size: 13px;line-height: normal;padding: 5px 10px;text-align: left;', ), 'post_date' => array( 'name' => 'Post date', 'dummy_html' => '18/06/2015', 'css' => 'display: block;font-size: 13px;line-height: normal;padding: 5px 10px;text-align: left;', ), 'author' => array( 'name' => 'Author', 'dummy_html' => 'PickPlugins', 'css' => 'display: block;font-size: 13px;line-height: normal;padding: 5px 10px;text-align: left;', ), 'author_link' => array( 'name' => 'Author with Link', 'dummy_html' => 'Lorem', 'css' => 'display: block;font-size: 13px;line-height: normal;padding: 5px 10px;text-align: left;', ), 'categories' => array( 'name' => 'Categories', 'dummy_html' => ' ', 'css' => 'display: block;font-size: 13px;line-height: normal;padding: 5px 10px;text-align: left;', ), 'tags' => array( 'name' => 'Tags', 'dummy_html' => ' ', 'css' => 'display: block;font-size: 13px;line-height: normal;padding: 5px 10px;text-align: left;', ), 'comments_count' => array( 'name' => 'Comments Count', 'dummy_html' => '3 Comments', 'css' => 'display: block;font-size: 13px;line-height: normal;padding: 5px 10px;text-align: left;', ), 'comments' => array( 'name' => 'Comments', 'dummy_html' => 'Lorem', 'css' => 'display: block;font-size: 13px;line-height: normal;padding: 5px 10px;text-align: left;', ), 'rating_widget' => array( 'name' => 'Rating-Widget: Star Review System', 'dummy_html' => 'Lorem', 'css' => 'display: block;font-size: 13px;line-height: normal;padding: 5px 10px;text-align: left;', ), 'share_button' => array( 'name' => 'Share button', 'dummy_html' => ' ', 'css' => 'display: block;font-size: 13px;line-height: normal;padding: 5px 10px;text-align: left;', ), 'hr' => array( 'name' => 'Horizontal line', 'dummy_html' => '
', 'css' => 'display: block;font-size: 13px;line-height: normal;padding: 5px 10px;text-align: left;', ), 'five_star' => array( 'name' => 'Five star', 'dummy_html' => 'Star', 'css' => 'display: block;font-size: 13px;line-height: normal;padding: 5px 10px;text-align: left;', ), ), ); $layout_items = apply_filters('post_grid_filter_layout_items', $layout_items); return $layout_items; } public function layout_content_list() { $layout_content_list = array( 'flat' => array( '0' => array( 'key' => 'media', 'custom_class' => '', 'media_source' => array( 'featured_image' => array( 'enable' => 'yes', 'image_size' => 'large', 'link_to' => 'post_link', 'link_target' => '_self', ), 'first_image' => array( 'enable' => 'no', 'link_to' => 'post_link', 'link_target' => '_self', ), 'empty_thumb' => array( 'enable' => 'no', 'link_to' => 'post_link', 'link_target' => '_self', 'default_thumb_src' => 'http://localhost/wp/wp-content/uploads/2020/02/single-1.jpg', ), 'siteorigin_first_image' => array( 'enable' => 'no', 'link_to' => 'none', 'link_target' => '_self', ), ), 'media_height' => array( 'large_type' => 'auto_height', 'large' => '', 'medium_type' => 'auto_height', 'medium' => '', 'small_type' => 'auto_height', 'small' => '', ), 'margin' => '', 'padding' => '', 'css' => 'max-width:100%;height:auto;', 'css_hover' => '', 'name' => 'Title with linked', 'css' => 'display: block;font-size: 21px;line-height: normal;padding: 5px 10px;text-align: left; text-decoration: none;', 'css_hover' => '', ), '1' => array('key' => 'title_link', 'char_limit' => '20', 'name' => 'Title with linked', 'css' => 'display: block;font-size: 21px;line-height: normal;padding: 5px 10px;text-align: left; text-decoration: none;', 'css_hover' => '',), '2' => array('key' => 'excerpt', 'char_limit' => '20', 'name' => 'Excerpt', 'css' => 'display: block;font-size: 14px;padding: 5px 10px;text-align: left;', 'css_hover' => ''), '3' => array('key' => 'read_more', 'name' => 'Read more', 'css' => 'display: block;font-size: 12px;font-weight: bold;padding: 0 10px;text-align: left;text-decoration: none;', 'css_hover' => ''), ), 'flat-center' => array( '0' => array('key' => 'title_link', 'char_limit' => '20', 'name' => 'Title with linked', 'css' => 'display: block;font-size: 21px;line-height: normal;padding: 5px 10px;text-align: center;text-decoration: none;', 'css_hover' => ''), '1' => array('key' => 'excerpt', 'char_limit' => '20', 'name' => 'Excerpt', 'css' => 'display: block;font-size: 14px;padding: 5px 10px;text-align: center;', 'css_hover' => ''), '2' => array('key' => 'read_more', 'name' => 'Read more', 'css' => 'display: block;font-size: 12px;font-weight: bold;padding: 0 10px;text-align: center;', 'css_hover' => ''), ), 'flat-right' => array( '0' => array('key' => 'title_link', 'char_limit' => '20', 'name' => 'Title with linked', 'css' => 'display: block;font-size: 21px;line-height: normal;padding: 5px 10px;text-align: right;text-decoration: none;', 'css_hover' => ''), '1' => array('key' => 'excerpt', 'char_limit' => '20', 'name' => 'Excerpt', 'css' => 'display: block;font-size: 14px;padding: 5px 10px;text-align: right;', 'css_hover' => ''), '2' => array('key' => 'read_more', 'name' => 'Read more', 'css' => 'display: block;font-size: 12px;font-weight: bold;padding: 0 10px;text-align: right;', 'css_hover' => ''), ), 'flat-left' => array( '0' => array('key' => 'title_link', 'char_limit' => '20', 'name' => 'Title with linked', 'css' => 'display: block;font-size: 21px;line-height: normal;padding: 5px 10px;text-align: left;text-decoration: none;', 'css_hover' => ''), '1' => array('key' => 'excerpt', 'char_limit' => '20', 'name' => 'Excerpt', 'css' => 'display: block;font-size: 14px;padding: 5px 10px;text-align: left;', 'css_hover' => ''), '2' => array('key' => 'read_more', 'name' => 'Read more', 'css' => 'display: block;font-size: 12px;font-weight: bold;padding: 0 10px;text-align: left;', 'css_hover' => '') ), 'wc-center-price' => array( '0' => array('key' => 'title_link', 'char_limit' => '20', 'name' => 'Title with linked', 'css' => 'display: block;font-size: 21px;line-height: normal;padding: 5px 10px;text-align: center;text-decoration: none;', 'css_hover' => ''), '1' => array('key' => 'wc_full_price', 'name' => 'Price', 'css' => 'background:#f9b013;color:#fff;display: inline-block;font-size: 20px;line-height:normal;padding: 0 17px;text-align: center;', 'css_hover' => ''), '2' => array('key' => 'excerpt', 'char_limit' => '20', 'name' => 'Excerpt', 'css' => 'display: block;font-size: 14px;padding: 5px 10px;text-align: center;', 'css_hover' => ''), ), 'wc-center-cart' => array( '0' => array('key' => 'title_link', 'char_limit' => '20', 'name' => 'Title with linked', 'css' => 'display: block;font-size: 21px;line-height: normal;padding: 5px 10px;text-align: center;text-decoration: none;', 'css_hover' => ''), '1' => array('key' => 'wc_gallery', 'name' => 'Add to Cart', 'css' => 'color:#555;display: inline-block;font-size: 13px;line-height:normal;padding: 0 17px;text-align: center;', 'css_hover' => ''), '2' => array('key' => 'excerpt', 'char_limit' => '20', 'name' => 'Excerpt', 'css' => 'display: block;font-size: 14px;padding: 5px 10px;text-align: center;', 'css_hover' => ''), ), ); $layout_content_list = apply_filters('post_grid_filter_layout_content_list', $layout_content_list); return $layout_content_list; } public function layout_content($layout) { $layout_content = $this->layout_content_list(); return $layout_content[$layout]; } public function skins() { $skins = array( 'flat' => array( 'slug' => 'flat', 'name' => 'Flat', 'thumb_url' => '', ), 'flip-x' => array( 'slug' => 'flip-x', 'name' => 'Flip-x', 'thumb_url' => '', ), 'spinright' => array( 'slug' => 'spinright', 'name' => 'SpinRight', 'thumb_url' => '', ), 'thumbgoleft' => array( 'slug' => 'thumbgoleft', 'name' => 'ThumbGoLeft', 'thumb_url' => '', ), 'thumbrounded' => array( 'slug' => 'thumbrounded', 'name' => 'ThumbRounded', 'thumb_url' => '', ), 'contentbottom' => array( 'slug' => 'contentbottom', 'name' => 'ContentBottom', 'thumb_url' => '', ), ); $skins = apply_filters('post_grid_filter_skins', $skins); return $skins; } } PK!}rjeeclass-post-types.phpnu[add_cap('publish_post_grids'); $role->add_cap('edit_post_grids'); $role->add_cap('edit_others_post_grids'); $role->add_cap('read_private_post_grids'); $role->add_cap('edit_post_grid'); $role->add_cap('read_post_grid'); $role->add_cap('delete_post_grid', false); $role->add_cap('publish_post_grid_templates'); $role->add_cap('edit_post_grid_templates'); $role->add_cap('edit_others_post_grid_templates'); $role->add_cap('read_private_post_grid_templates'); $role->add_cap('edit_post_grid_template'); $role->add_cap('read_post_grid_template'); $role->add_cap('delete_post_grid_template', false); $role->add_cap('publish_post_grid_layouts'); $role->add_cap('edit_post_grid_layouts'); $role->add_cap('edit_others_post_grid_layouts'); $role->add_cap('read_private_post_grid_layouts'); $role->add_cap('edit_post_grid_layout'); $role->add_cap('read_post_grid_layout'); $role->add_cap('delete_post_grid_layout', false); } public function _posttype_setting() { $post_grid_block_editor = get_option('post_grid_block_editor'); $post_types = isset($post_grid_block_editor['postTypes']) ? $post_grid_block_editor['postTypes'] : []; $taxonomies = isset($post_grid_block_editor['taxonomies']) ? $post_grid_block_editor['taxonomies'] : []; if (empty($post_types)) return; foreach ($post_types as $post_type) { $slug = isset($post_type['slug']) ? $post_type['slug'] : ''; if (empty($slug)) continue; if (post_type_exists($slug)) continue; $plural = isset($post_type['labels']['name']) ? $post_type['labels']['name'] : ''; $singular_name = isset($post_type['labels']['singular_name']) ? $post_type['labels']['singular_name'] : $plural; $menu_name = isset($post_type['labels']['menu_name']) ? $post_type['labels']['menu_name'] : $plural; $add_new = isset($post_type['labels']['add_new']) ? $post_type['labels']['add_new'] : "Add New"; $all_items = isset($post_type['labels']['all_items']) ? $post_type['labels']['all_items'] : "All %s"; $add_new_item = isset($post_type['labels']['add_new_item']) ? $post_type['labels']['add_new_item'] : "Add %s"; $edit = isset($post_type['labels']['edit']) ? $post_type['labels']['edit'] : "Edit"; $edit_item = isset($post_type['labels']['edit_item']) ? $post_type['labels']['edit_item'] : "Edit %s"; $new_item = isset($post_type['labels']['new_item']) ? $post_type['labels']['new_item'] : "New %s"; $view = isset($post_type['labels']['view']) ? $post_type['labels']['view'] : "View %s"; $view_item = isset($post_type['labels']['view_item']) ? $post_type['labels']['view_item'] : "View %s"; $search_items = isset($post_type['labels']['search_items']) ? $post_type['labels']['search_items'] : "Search %s"; $not_found = isset($post_type['labels']['not_found']) ? $post_type['labels']['not_found'] : "No %s found"; $not_found_in_trash = isset($post_type['labels']['not_found_in_trash']) ? $post_type['labels']['not_found_in_trash'] : "No %s found in trash"; $parent = isset($post_type['labels']['parent']) ? $post_type['labels']['parent'] : "Parent %s"; $description = isset($post_type['description']) ? $post_type['description'] : "This is where you can create and manage %s."; $public = isset($post_type['public']) ? $post_type['public'] : true; $show_ui = isset($post_type['show_ui']) ? $post_type['show_ui'] : true; $show_in_rest = isset($post_type['show_in_rest']) ? $post_type['show_in_rest'] : false; $capability_type = isset($post_type['capability_type']) ? $post_type['capability_type'] : "post"; // $publish_posts = isset($post_type['capabilities']['publish_posts']) ? $post_type['labels']['publish_posts'] : "publish_" . $slug . "s"; // $edit_posts = isset($post_type['capabilities']['edit_posts']) ? $post_type['labels']['edit_posts'] : "edit_" . $slug . "s"; // $edit_others_posts = isset($post_type['capabilities']['edit_others_posts']) ? $post_type['labels']['edit_others_posts'] : "edit_others_" . $slug . "s"; // $read_private_posts = isset($post_type['capabilities']['read_private_posts']) ? $post_type['labels']['read_private_posts'] : "read_private_" . $plural; // $edit_post = isset($post_type['capabilities']['edit_post']) ? $post_type['labels']['edit_post'] : "edit_" . $slug; // $delete_post = isset($post_type['capabilities']['delete_post']) ? $post_type['labels']['delete_post'] : "delete_" . $slug; // $read_post = isset($post_type['capabilities']['read_post']) ? $post_type['labels']['read_post'] : "read_" . $slug; $map_meta_cap = isset($post_type['map_meta_cap']) ? $post_type['map_meta_cap'] : true; $publicly_queryable = isset($post_type['publicly_queryable']) ? $post_type['publicly_queryable'] : true; $rewrite = isset($post_type['rewrite']) ? $post_type['rewrite'] : true; $exclude_from_search = isset($post_type['exclude_from_search']) ? $post_type['exclude_from_search'] : false; $hierarchical = isset($post_type['hierarchical']) ? $post_type['hierarchical'] : false; $query_var = isset($post_type['query_var']) ? $post_type['query_var'] : true; $show_in_nav_menus = isset($post_type['show_in_nav_menus']) ? $post_type['show_in_nav_menus'] : true; $menu_icon = isset($post_type['menu_icon']) ? $post_type['menu_icon'] : 'dashicons-grid-view'; $show_in_menu = isset($post_type['show_in_menu']) ? $post_type['show_in_menu'] : $slug; $supports = isset($post_type['supports']) ? $post_type['supports'] : array("title"); // $map_meta_cap = true; // $publicly_queryable = true; // $exclude_from_search = false; // $hierarchical = false; // $query_var = true; // $show_in_nav_menus = true; // $rewrite = true; // $menu_icon = 'dashicons-grid-view'; // $supports = array('title', 'author', 'comments', 'custom-fields'); $post_type_args = []; $post_type_args['labels']['name'] = $plural; $post_type_args['labels']['singular_name'] = $singular_name; $post_type_args['labels']['menu_name'] = $menu_name; $post_type_args['labels']['all_items'] = $all_items; $post_type_args['labels']['add_new'] = $add_new; $post_type_args['labels']['add_new_item'] = $add_new_item; $post_type_args['labels']['edit'] = $edit; $post_type_args['labels']['edit_item'] = $edit_item; $post_type_args['labels']['new_item'] = $new_item; $post_type_args['labels']['view'] = $view; $post_type_args['labels']['view_item'] = $view_item; $post_type_args['labels']['search_items'] = $search_items; $post_type_args['labels']['not_found'] = $not_found; $post_type_args['labels']['not_found_in_trash'] = $not_found_in_trash; $post_type_args['labels']['parent'] = $parent; //$post_type_args['capabilities']['publish_posts'] = $publish_posts; $post_type_args['capability_type'] = $capability_type; //($public); $post_type_args['description'] = $description; $post_type_args['public'] = (bool) $public; $post_type_args['show_ui'] = (bool) $show_ui; $post_type_args['show_in_rest'] = (bool) $show_in_rest; $post_type_args['map_meta_cap'] = (bool) $map_meta_cap; $post_type_args['publicly_queryable'] = (bool) $publicly_queryable; $post_type_args['exclude_from_search'] = (bool) $exclude_from_search; $post_type_args['hierarchical'] = (bool) $hierarchical; $post_type_args['query_var'] = (bool)$query_var; $post_type_args['supports'] = $supports; $post_type_args['show_in_nav_menus'] = (bool)$show_in_nav_menus; $post_type_args['menu_icon'] = $menu_icon; $post_type_args['rewrite'] = $rewrite; if (!empty($show_in_menu)) { //$post_type_args['show_in_menu'] = $show_in_menu; } register_post_type( $slug, apply_filters("post_grid_posttype_{$slug}", $post_type_args) ); } foreach ($taxonomies as $taxonomy) { $slug = isset($taxonomy['slug']) ? $taxonomy['slug'] : ''; if (empty($slug)) continue; if (post_type_exists($slug)) continue; $plural = isset($taxonomy['labels']['name']) ? $taxonomy['labels']['name'] : ''; $singular_name = isset($taxonomy['labels']['singular_name']) ? $taxonomy['labels']['singular_name'] : $plural; $menu_name = isset($taxonomy['labels']['menu_name']) ? $taxonomy['labels']['menu_name'] : $plural; $add_new = isset($taxonomy['labels']['add_new']) ? $taxonomy['labels']['add_new'] : "Add New"; $all_items = isset($taxonomy['labels']['all_items']) ? $taxonomy['labels']['all_items'] : "All %s"; $add_new_item = isset($taxonomy['labels']['add_new_item']) ? $taxonomy['labels']['add_new_item'] : "Add %s"; $edit = isset($taxonomy['labels']['edit']) ? $taxonomy['labels']['edit'] : "Edit"; $edit_item = isset($taxonomy['labels']['edit_item']) ? $taxonomy['labels']['edit_item'] : "Edit %s"; $new_item = isset($taxonomy['labels']['new_item']) ? $taxonomy['labels']['new_item'] : "New %s"; $view = isset($taxonomy['labels']['view']) ? $taxonomy['labels']['view'] : "View %s"; $view_item = isset($taxonomy['labels']['view_item']) ? $taxonomy['labels']['view_item'] : "View %s"; $search_items = isset($taxonomy['labels']['search_items']) ? $taxonomy['labels']['search_items'] : "Search %s"; $not_found = isset($taxonomy['labels']['not_found']) ? $taxonomy['labels']['not_found'] : "No %s found"; $not_found_in_trash = isset($taxonomy['labels']['not_found_in_trash']) ? $taxonomy['labels']['not_found_in_trash'] : "No %s found in trash"; $parent = isset($taxonomy['labels']['parent']) ? $taxonomy['labels']['parent'] : "Parent %s"; $description = isset($taxonomy['description']) ? $taxonomy['description'] : "This is where you can create and manage %s."; $object_types = isset($taxonomy['object_types']) ? $taxonomy['object_types'] : ""; $object_types = explode(",", $object_types); $public = isset($taxonomy['public']) ? $taxonomy['public'] : true; $show_ui = isset($taxonomy['show_ui']) ? $taxonomy['show_ui'] : true; $show_in_rest = isset($taxonomy['show_in_rest']) ? $taxonomy['show_in_rest'] : false; $capability_type = isset($taxonomy['capability_type']) ? $taxonomy['capability_type'] : "post"; // $publish_posts = isset($taxonomy['capabilities']['publish_posts']) ? $taxonomy['labels']['publish_posts'] : "publish_" . $slug . "s"; // $edit_posts = isset($taxonomy['capabilities']['edit_posts']) ? $taxonomy['labels']['edit_posts'] : "edit_" . $slug . "s"; // $edit_others_posts = isset($taxonomy['capabilities']['edit_others_posts']) ? $taxonomy['labels']['edit_others_posts'] : "edit_others_" . $slug . "s"; // $read_private_posts = isset($taxonomy['capabilities']['read_private_posts']) ? $taxonomy['labels']['read_private_posts'] : "read_private_" . $plural; // $edit_post = isset($taxonomy['capabilities']['edit_post']) ? $taxonomy['labels']['edit_post'] : "edit_" . $slug; // $delete_post = isset($taxonomy['capabilities']['delete_post']) ? $taxonomy['labels']['delete_post'] : "delete_" . $slug; // $read_post = isset($taxonomy['capabilities']['read_post']) ? $taxonomy['labels']['read_post'] : "read_" . $slug; $map_meta_cap = isset($taxonomy['map_meta_cap']) ? $taxonomy['map_meta_cap'] : true; $publicly_queryable = isset($taxonomy['publicly_queryable']) ? $taxonomy['publicly_queryable'] : true; $show_admin_column = isset($taxonomy['show_admin_column']) ? $taxonomy['show_admin_column'] : true; $rewrite = isset($taxonomy['rewrite']) ? $taxonomy['rewrite'] : true; $exclude_from_search = isset($taxonomy['exclude_from_search']) ? $taxonomy['exclude_from_search'] : false; $hierarchical = isset($taxonomy['hierarchical']) ? $taxonomy['hierarchical'] : false; $query_var = isset($taxonomy['query_var']) ? $taxonomy['query_var'] : true; $show_in_nav_menus = isset($taxonomy['show_in_nav_menus']) ? $taxonomy['show_in_nav_menus'] : true; $menu_icon = isset($taxonomy['menu_icon']) ? $taxonomy['menu_icon'] : 'dashicons-grid-view'; $show_in_menu = isset($taxonomy['show_in_menu']) ? $taxonomy['show_in_menu'] : $slug; $supports = isset($taxonomy['supports']) ? $taxonomy['supports'] : array("title"); // $map_meta_cap = true; // $publicly_queryable = true; // $exclude_from_search = false; // $hierarchical = false; // $query_var = true; // $show_in_nav_menus = true; // $rewrite = true; // $menu_icon = 'dashicons-grid-view'; // $supports = array('title', 'author', 'comments', 'custom-fields'); $taxonomy_args = []; $taxonomy_args['labels']['name'] = $plural; $taxonomy_args['labels']['singular_name'] = $singular_name; $taxonomy_args['labels']['menu_name'] = $menu_name; $taxonomy_args['labels']['all_items'] = $all_items; $taxonomy_args['labels']['add_new'] = $add_new; $taxonomy_args['labels']['add_new_item'] = $add_new_item; $taxonomy_args['labels']['edit'] = $edit; $taxonomy_args['labels']['edit_item'] = $edit_item; $taxonomy_args['labels']['new_item'] = $new_item; $taxonomy_args['labels']['view'] = $view; $taxonomy_args['labels']['view_item'] = $view_item; $taxonomy_args['labels']['search_items'] = $search_items; $taxonomy_args['labels']['not_found'] = $not_found; $taxonomy_args['labels']['not_found_in_trash'] = $not_found_in_trash; $taxonomy_args['labels']['parent'] = $parent; //$taxonomy_args['capabilities']['publish_posts'] = $publish_posts; //($public); $taxonomy_args['description'] = $description; $taxonomy_args['public'] = (bool) $public; $taxonomy_args['show_ui'] = (bool) $show_ui; $taxonomy_args['show_in_rest'] = (bool) $show_in_rest; $taxonomy_args['map_meta_cap'] = (bool) $map_meta_cap; $taxonomy_args['publicly_queryable'] = (bool) $publicly_queryable; $taxonomy_args['show_admin_column'] = (bool) $show_admin_column; $taxonomy_args['exclude_from_search'] = (bool) $exclude_from_search; $taxonomy_args['hierarchical'] = (bool) $hierarchical; $taxonomy_args['query_var'] = (bool)$query_var; $taxonomy_args['supports'] = $supports; $taxonomy_args['show_in_nav_menus'] = (bool)$show_in_nav_menus; $taxonomy_args['menu_icon'] = $menu_icon; $taxonomy_args['rewrite'] = $rewrite; if (!empty($show_in_menu)) { //$taxonomy_args['show_in_menu'] = $show_in_menu; } register_taxonomy( $slug, $object_types, apply_filters("post_grid_taxonomy_{$slug}", $taxonomy_args) ); } } public function _posttype_post_grid() { if (post_type_exists("post_grid")) return; $singular = __('Post Grid', 'post-grid'); $plural = __('Post Grid', 'post-grid'); $post_grid_settings = get_option('post_grid_settings'); $post_grid_preview = isset($post_grid_settings['post_grid_preview']) ? $post_grid_settings['post_grid_preview'] : 'yes'; register_post_type( "post_grid", apply_filters("post_grid_posttype_post_grid", array( 'labels' => array( 'name' => $plural, 'singular_name' => $singular, 'menu_name' => $singular, 'all_items' => sprintf(__('All %s', 'post-grid'), $plural), 'add_new' => __('Add New', 'post-grid'), 'add_new_item' => sprintf(__('Add %s', 'post-grid'), $singular), 'edit' => __('Edit', 'post-grid'), 'edit_item' => sprintf(__('Edit %s', 'post-grid'), $singular), 'new_item' => sprintf(__('New %s', 'post-grid'), $singular), 'view' => sprintf(__('View %s', 'post-grid'), $singular), 'view_item' => sprintf(__('View %s', 'post-grid'), $singular), 'search_items' => sprintf(__('Search %s', 'post-grid'), $plural), 'not_found' => sprintf(__('No %s found', 'post-grid'), $plural), 'not_found_in_trash' => sprintf(__('No %s found in trash', 'post-grid'), $plural), 'parent' => sprintf(__('Parent %s', 'post-grid'), $singular) ), 'description' => sprintf(__('This is where you can create and manage %s.', 'post-grid'), $plural), 'public' => false, 'show_ui' => true, 'capability_type' => 'post', 'capabilities' => array( 'publish_posts' => 'publish_post_grids', 'edit_posts' => 'edit_post_grids', 'edit_others_posts' => 'edit_others_post_grids', 'read_private_posts' => 'read_private_post_grids', 'edit_post' => 'edit_post_grid', 'delete_post' => 'delete_post_grid', 'read_post' => 'read_post_grid', ), 'show_in_rest' => true, 'map_meta_cap' => true, 'publicly_queryable' => ($post_grid_preview == 'yes') ? true : false, 'exclude_from_search' => false, 'hierarchical' => false, 'query_var' => true, 'supports' => array('title'), 'show_in_nav_menus' => true, 'menu_icon' => 'dashicons-grid-view', //'show_in_menu' => 'post-grid', )) ); } public function _posttype_post_grid_layout() { if (post_type_exists("post_grid_layout")) return; $singular = __('Saved Layout', 'post-grid'); $plural = __('Saved Layouts', 'post-grid'); register_post_type( "post_grid_layout", apply_filters("post_grid_posttype_post_grid_layout", array( 'labels' => array( 'name' => $plural, 'singular_name' => $singular, 'menu_name' => $singular, 'all_items' => sprintf(__('All %s', 'post-grid'), $plural), 'add_new' => __('Add New', 'post-grid'), 'add_new_item' => sprintf(__('Add %s', 'post-grid'), $singular), 'edit' => __('Edit', 'post-grid'), 'edit_item' => sprintf(__('Edit %s', 'post-grid'), $singular), 'new_item' => sprintf(__('New %s', 'post-grid'), $singular), 'view' => sprintf(__('View %s', 'post-grid'), $singular), 'view_item' => sprintf(__('View %s', 'post-grid'), $singular), 'search_items' => sprintf(__('Search %s', 'post-grid'), $plural), 'not_found' => sprintf(__('No %s found', 'post-grid'), $plural), 'not_found_in_trash' => sprintf(__('No %s found in trash', 'post-grid'), $plural), 'parent' => sprintf(__('Parent %s', 'post-grid'), $singular) ), 'description' => sprintf(__('This is where you can create and manage %s.', 'post-grid'), $plural), 'public' => false, 'show_ui' => true, 'capability_type' => 'post', 'capabilities' => array( 'publish_posts' => 'publish_post_grid_layouts', 'edit_posts' => 'edit_post_grid_layouts', 'edit_others_posts' => 'edit_others_post_grid_layouts', 'read_private_posts' => 'read_private_post_grid_layouts', 'edit_post' => 'edit_post_grid_layout', 'delete_post' => 'delete_post_grid_layout', 'read_post' => 'read_post_grid_layout', ), 'map_meta_cap' => true, 'publicly_queryable' => false, 'exclude_from_search' => false, 'hierarchical' => false, 'query_var' => true, 'supports' => array('title'), // 'editor' 'show_in_nav_menus' => false, 'show_in_menu' => 'edit.php?post_type=post_grid', 'menu_icon' => 'dashicons-businessman', 'show_in_rest' => true, )) ); $singular = __('Category', 'post-grid'); $plural = __('Categories', 'post-grid'); register_taxonomy( "layout_cat", apply_filters('register_taxonomy_layout_cat_object_type', array('post_grid_layout')), apply_filters('register_taxonomy_layout_cat_args', array( 'hierarchical' => true, 'show_admin_column' => true, 'update_count_callback' => '_update_post_term_count', 'label' => $plural, 'labels' => array( 'name' => $plural, 'singular_name' => $singular, 'menu_name' => ucwords($plural), 'search_items' => sprintf(__('Search %s', 'post-grid'), $plural), 'all_items' => sprintf(__('All %s', 'post-grid'), $plural), 'parent_item' => sprintf(__('Parent %s', 'post-grid'), $singular), 'parent_item_colon' => sprintf(__('Parent %s:', 'post-grid'), $singular), 'edit_item' => sprintf(__('Edit %s', 'post-grid'), $singular), 'update_item' => sprintf(__('Update %s', 'post-grid'), $singular), 'add_new_item' => sprintf(__('Add New %s', 'post-grid'), $singular), 'new_item_name' => sprintf(__('New %s Name', 'post-grid'), $singular) ), 'show_ui' => true, 'public' => true, 'show_in_rest' => true, 'show_in_menu' => 'post-grid', 'rewrite' => array( 'slug' => 'layout_cat', // This controls the base slug that will display before each term 'with_front' => false, // Don't display the category base before "/locations/" 'hierarchical' => true // This will allow URL's like "/locations/boston/cambridge/" ), )) ); } public function _posttype_saved_template() { if (post_type_exists("post_grid_template")) return; $singular = __('Saved Template', 'post-grid'); $plural = __('Saved Templates', 'post-grid'); register_post_type( "post_grid_template", apply_filters("post_grid_posttype_template", array( 'labels' => array( 'name' => $plural, 'singular_name' => $singular, 'menu_name' => $singular, 'all_items' => sprintf(__('All %s', 'post-grid'), $plural), 'add_new' => __('Add New', 'post-grid'), 'add_new_item' => sprintf(__('Add %s', 'post-grid'), $singular), 'edit' => __('Edit', 'post-grid'), 'edit_item' => sprintf(__('Edit %s', 'post-grid'), $singular), 'new_item' => sprintf(__('New %s', 'post-grid'), $singular), 'view' => sprintf(__('View %s', 'post-grid'), $singular), 'view_item' => sprintf(__('View %s', 'post-grid'), $singular), 'search_items' => sprintf(__('Search %s', 'post-grid'), $plural), 'not_found' => sprintf(__('No %s found', 'post-grid'), $plural), 'not_found_in_trash' => sprintf(__('No %s found in trash', 'post-grid'), $plural), 'parent' => sprintf(__('Parent %s', 'post-grid'), $singular) ), 'description' => sprintf(__('This is where you can create and manage %s.', 'post-grid'), $plural), 'public' => true, 'show_ui' => true, 'capability_type' => 'post', 'capabilities' => array( 'publish_posts' => 'publish_post_grid_templates', 'edit_posts' => 'edit_post_grid_templates', 'edit_others_posts' => 'edit_others_post_grid_templates', 'read_private_posts' => 'read_private_post_grid_templates', 'edit_post' => 'edit_post_grid_template', 'delete_post' => 'delete_post_grid_template', 'read_post' => 'read_post_grid_template', ), 'map_meta_cap' => true, 'publicly_queryable' => false, 'exclude_from_search' => false, 'hierarchical' => false, 'query_var' => true, 'supports' => array('title', 'editor', 'thumbnail', 'author', 'revisions', 'excerpt', 'custom-fields', 'comments'), 'show_in_nav_menus' => false, 'show_in_menu' => 'post-grid', 'menu_icon' => 'dashicons-businessman', 'show_in_rest' => true, )) ); $singular = __('Category', 'post-grid'); $plural = __('Categories', 'post-grid'); register_taxonomy( "template_cat", apply_filters('register_taxonomy_template_cat_object_type', array('post_grid_template')), apply_filters('register_taxonomy_template_cat_args', array( 'hierarchical' => true, 'show_admin_column' => true, 'update_count_callback' => '_update_post_term_count', 'label' => $plural, 'labels' => array( 'name' => $plural, 'singular_name' => $singular, 'menu_name' => ucwords($plural), 'search_items' => sprintf(__('Search %s', 'post-grid'), $plural), 'all_items' => sprintf(__('All %s', 'post-grid'), $plural), 'parent_item' => sprintf(__('Parent %s', 'post-grid'), $singular), 'parent_item_colon' => sprintf(__('Parent %s:', 'post-grid'), $singular), 'edit_item' => sprintf(__('Edit %s', 'post-grid'), $singular), 'update_item' => sprintf(__('Update %s', 'post-grid'), $singular), 'add_new_item' => sprintf(__('Add New %s', 'post-grid'), $singular), 'new_item_name' => sprintf(__('New %s Name', 'post-grid'), $singular) ), 'show_ui' => true, 'public' => true, 'show_in_rest' => true, 'show_in_menu' => 'post-grid', 'rewrite' => array( 'slug' => 'template_cat', // This controls the base slug that will display before each term 'with_front' => false, // Don't display the category base before "/locations/" 'hierarchical' => true // This will allow URL's like "/locations/boston/cambridge/" ), )) ); } } new class_post_grid_post_types(); // function book_setup_post_type() // { // $args = array( // 'public' => true, // 'label' => __('Books', 'textdomain'), // 'menu_icon' => 'dashicons-book', // ); // register_post_type('book', $args); // } // add_action('init', 'book_setup_post_type');PK!9i9 9 class-post-grid-support.phpnu[ __('How to create post grid', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=QaoMrdgkyKw&list=PL0QP7T2SN94bpTVghETSePuVvRROpuEW6&index=9&ab_channel=PickPlugins', 'keywords' => '', ), // array( // 'title' => __('Custom read more text', 'post-grid'), // 'url' => 'https://www.youtube.com/watch?v=LY7IjS7SFNk', // 'keywords' => '', // ), // array( // 'title' => __('Remove read more text', 'post-grid'), // 'url' => 'https://www.youtube.com/watch?v=ZcS2vRcTe4A', // ), array( 'title' => __('Excerpt word count', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=gZ6E3UiKQqk', 'keywords' => '', ), array( 'title' => __('Custom media height', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=TupF2TpHHFA', 'keywords' => '', ), array( 'title' => __('Item custom padding margin', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=HRZpoib1VvI', 'keywords' => '', ), array( 'title' => __('Grid item height', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=ydqlgzfsboQ', 'keywords' => '', ), array( 'title' => __('Column Width or column number', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=ZV8hd1ij5Wo', 'keywords' => '', ), array( 'title' => __('Post title linked', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=oUVZB9F5d4U', 'keywords' => '', ), array( 'title' => __('Featured image linked to post', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=stGOJLwUF-k', 'keywords' => '', ), array( 'title' => __('Query post by categories or terms', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=xYzqtWRg8W4', 'keywords' => '', ), array( 'title' => __('Query post by tags or terms', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=RKb-B_Q72Ak', 'keywords' => '', ), array( 'title' => __('Display search input', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=psJR65Fmc_s', 'keywords' => '', ), array( 'title' => __('Work with layout editor', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=9bQc7q40jMc', 'keywords' => '', ), array( 'title' => __('[ Pro ] Create filterable grid', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=Zg2r7idmEm0', 'keywords' => '', ), array( 'title' => __('[ Pro ] Filterable custom filter type data logic', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=5Dueav6Yoyc', 'keywords' => '', ), array( 'title' => __('[ Pro ] Filterable custom all text', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=JvVkAyoXC3g', 'keywords' => '', ), array( 'title' => __('[ Pro ] Filterable default active filter', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=h2rbyZNhMhU', 'keywords' => '', ), array( 'title' => __('[ Pro ] Filterable custom filter', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=e8phxNKIRsU', 'keywords' => '', ), array( 'title' => __('[ Pro ] Filterable dropdown single filter', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=ZHY8qf-z3H0', 'keywords' => '', ), array( 'title' => __('[ Pro ] Filterable display sort filter', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=21TYNsp2OPI', 'keywords' => '', ), array( 'title' => __('[ Pro ] Filterable multi filter', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=uRcfd_R9YCM', 'keywords' => '', ), array( 'title' => __('[ Pro ] Post grid on archive tags', 'post-grid'), 'url' => 'https://youtu.be/lNyAjva_UXo', 'keywords' => '', ), array( 'title' => __('[ Pro ] Query post by meta field', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=0AIDNJvZGR0', 'keywords' => '', ), array( 'title' => __('[ Pro ] Multi skin', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=YzUs_P3cFCo', 'keywords' => '', ), array( 'title' => __('[ Pro ] Sticky post query', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=nVIOUbVjML4', 'keywords' => '', ), array( 'title' => __('[ Pro ] Masonry layout', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=qYjbv2euNpE', 'keywords' => '', ), array( 'title' => __('[ Pro ] Post query by author', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=KtoGa8NB3ig', 'keywords' => '', ), array( 'title' => __('[ Pro ] Create glossary grid', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=MKL4EZ-WYTs', 'keywords' => '', ), array( 'title' => __('[ Pro ] Post carousel slider', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=A0bZ_luBtQQ', 'keywords' => '', ), array( 'title' => __('[ Pro ] Grid layout type', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=58piQVkDZN4', 'keywords' => '', ), array( 'title' => __('[ Pro ] Thumbnail youtube', 'post-grid'), 'url' => 'https://www.youtube.com/watch?v=Zm5vD15yvNM', 'keywords' => '', ), array( 'title' => __('How to Create a Post Grid?', 'post-grid'), 'url' => 'https://www.pickplugins.com/documentation/post-grid/faq/how-to-create-a-post-grid/', ), array( 'title' => __('How to upgrade to premium?', 'post-grid'), 'url' => 'https://www.pickplugins.com/documentation/post-grid/upgrade-to-premium/', ), array( 'title' => __('Post grid on archive page?', 'post-grid'), 'url' => 'https://www.pickplugins.com/documentation/post-grid/faq/post-grid-for-archive-page/', ), array( 'title' => __('How to display HTML/Shortcode via layout editor?', 'post-grid'), 'url' => 'https://www.pickplugins.com/documentation/post-grid/faq/layout-editor-how-at-add-htmlshortcode/', ), ); return apply_filters('post_grid_video_tutorials', $tutorials); } public function faq() { $faq = array(); return apply_filters('post_grid_faq', $faq); } } PK!;oclass-admin-notices.phpnu[

⚡ Intorducing React Based Modern Builder for Post Grid, Try Now

❌ Hide Notice

id == 'edit-post_grid_layout' || $screen->id == 'post_grid_layout' || $screen->id == 'dashboard' || $screen->id == 'edit-post_grid' || $screen->id == 'post-grid_page_overview' || $screen->id == 'post_grid' || $screen->id == 'edit-post_grid_template' || $screen->id == 'post_grid_template' || $screen->id == 'post-grid_page_post-grid-settings' || $screen->id == 'post-grid_page_import_layouts') : if ($license_status == 'expired' && $license_expired != 'hidden') : $actionurl = $_SERVER['REQUEST_URI']; $actionurl = wp_nonce_url($actionurl, 'license_expired'); $nonce = isset($_REQUEST['_wpnonce']) ? sanitize_text_field($_REQUEST['_wpnonce']) : ''; if (wp_verify_nonce($nonce, 'license_expired')) { $post_grid_notices['license_expired'] = 'hidden'; update_option('post_grid_notices', $post_grid_notices); return; } ?>

Your license for Post Grid plugin has expried, please Renew Grab 25% Off! Hide this

asset_dir_url = isset($args['asset_dir_url']) ? $args['asset_dir_url'] : ''; // $this->textdomain = isset($args['textdomain']) ? $args['textdomain'] : ''; } function admin_scripts() { wp_enqueue_script('jquery'); wp_enqueue_script('jquery-ui-sortable'); wp_enqueue_script('jquery-ui-core'); wp_enqueue_script('jquery-ui-accordion'); wp_enqueue_style('jquery-ui'); wp_enqueue_script('wp-color-picker'); wp_enqueue_style('wp-color-picker'); wp_enqueue_style('select2'); wp_enqueue_script('select2'); wp_enqueue_style('font-awesome-5'); wp_enqueue_style('settings-tabs'); wp_enqueue_script('settings-tabs'); wp_enqueue_script('code-editor'); wp_enqueue_style('code-editor'); wp_enqueue_script('jquery.lazy'); if (function_exists('wp_enqueue_editor')) { wp_enqueue_editor(); } } function field_template($option) { $id = isset($option['id']) ? $option['id'] : ""; $wraper_class = isset($option['wraper_class']) ? $option['wraper_class'] : ""; $conditions = isset($option['conditions']) ? $option['conditions'] : array(); $is_error = isset($option['is_error']) ? $option['is_error'] : false; $error_details = isset($option['error_details']) ? $option['error_details'] : ''; if (!empty($conditions)) : $depends = ''; $field = isset($conditions['field']) ? $conditions['field'] : ''; $cond_value = isset($conditions['value']) ? $conditions['value'] : ''; $type = isset($conditions['type']) ? $conditions['type'] : ''; $pattern = isset($conditions['pattern']) ? $conditions['pattern'] : ''; $modifier = isset($conditions['modifier']) ? $conditions['modifier'] : ''; $like = isset($conditions['like']) ? $conditions['like'] : ''; $strict = isset($conditions['strict']) ? $conditions['strict'] : ''; $empty = isset($conditions['empty']) ? $conditions['empty'] : ''; $sign = isset($conditions['sign']) ? $conditions['sign'] : ''; $min = isset($conditions['min']) ? $conditions['min'] : ''; $max = isset($conditions['max']) ? $conditions['max'] : ''; $depends .= "{'[name=$field]':"; $depends .= '{'; if (!empty($type)) : $depends .= "'type':"; $depends .= "'" . $type . "'"; endif; if (!empty($modifier)) : $depends .= ",'modifier':"; $depends .= "'" . $modifier . "'"; endif; if (!empty($like)) : $depends .= ",'like':"; $depends .= "'" . $like . "'"; endif; if (!empty($strict)) : $depends .= ",'strict':"; $depends .= "'" . $strict . "'"; endif; if (!empty($empty)) : $depends .= ",'empty':"; $depends .= "'" . $empty . "'"; endif; if (!empty($sign)) : $depends .= ",'sign':"; $depends .= "'" . $sign . "'"; endif; if (!empty($min)) : $depends .= ",'min':"; $depends .= "'" . $min . "'"; endif; if (!empty($max)) : $depends .= ",'max':"; $depends .= "'" . $max . "'"; endif; if (!empty($cond_value)) : $depends .= ",'value':"; if (is_array($cond_value)) : $count = count($cond_value); $i = 1; $depends .= "["; foreach ($cond_value as $val) : $depends .= "'" . $val . "'"; if ($i < $count) $depends .= ","; $i++; endforeach; $depends .= "]"; else : $depends .= "["; $depends .= "'" . $cond_value . "'"; $depends .= "]"; endif; endif; $depends .= '}}'; endif; ob_start(); ?>
data-depends="[]" class="setting-field ">
%s
%s

%s

field_select($option); elseif (isset($option['type']) && $option['type'] === 'select2') $this->field_select2($option); elseif (isset($option['type']) && $option['type'] === 'checkbox') $this->field_checkbox($option); elseif (isset($option['type']) && $option['type'] === 'radio') $this->field_radio($option); elseif (isset($option['type']) && $option['type'] === 'radio_image') $this->field_radio_image($option); elseif (isset($option['type']) && $option['type'] === 'textarea') $this->field_textarea($option); elseif (isset($option['type']) && $option['type'] === 'scripts_js') $this->field_scripts_js($option); elseif (isset($option['type']) && $option['type'] === 'scripts_css') $this->field_scripts_css($option); elseif (isset($option['type']) && $option['type'] === 'number') $this->field_number($option); elseif (isset($option['type']) && $option['type'] === 'text') $this->field_text($option); elseif (isset($option['type']) && $option['type'] === 'text_icon') $this->field_text_icon($option); elseif (isset($option['type']) && $option['type'] === 'text_multi') $this->field_text_multi($option); elseif (isset($option['type']) && $option['type'] === 'hidden') $this->field_hidden($option); elseif (isset($option['type']) && $option['type'] === 'range') $this->field_range($option); elseif (isset($option['type']) && $option['type'] === 'colorpicker') $this->field_colorpicker($option); elseif (isset($option['type']) && $option['type'] === 'colorpicker_multi') $this->field_colorpicker_multi($option); elseif (isset($option['type']) && $option['type'] === 'datepicker') $this->field_datepicker($option); elseif (isset($option['type']) && $option['type'] === 'faq') $this->field_faq($option); elseif (isset($option['type']) && $option['type'] === 'addons_grid') $this->field_addons_grid($option); elseif (isset($option['type']) && $option['type'] === 'custom_html') $this->field_custom_html($option); elseif (isset($option['type']) && $option['type'] === 'repeatable') $this->field_repeatable($option); elseif (isset($option['type']) && $option['type'] === 'media') $this->field_media($option); elseif (isset($option['type']) && $option['type'] === 'media_url') $this->field_media_url($option); elseif (isset($option['type']) && $option['type'] === 'option_group') $this->field_option_group($option); elseif (isset($option['type']) && $option['type'] === 'option_group_accordion') $this->field_option_group_accordion($option); elseif (isset($option['type']) && $option['type'] === 'wp_editor') $this->field_wp_editor($option); elseif (isset($option['type']) && $option['type'] === 'textarea_editor') $this->field_textarea_editor($option); elseif (isset($option['type']) && $option['type'] === $type) do_action("settings_tabs_field_$type", $option); } public function field_option_group_accordion($option) { $id = isset($option['id']) ? $option['id'] : ""; $css_id = isset($option['css_id']) ? $option['css_id'] : $id; $sortable = isset($option['sortable']) ? $option['sortable'] : false; $args_index = isset($option['args_index']) ? $option['args_index'] : array(); $args_index_default = isset($option['args_index_default']) ? $option['args_index_default'] : array(); $args_index_hide = isset($option['args_index_hide']) ? $option['args_index_hide'] : array(); $args_index = !empty($args_index) ? $args_index : $args_index_default; $args = isset($option['args']) ? $option['args'] : array(); $field_template = isset($option['field_template']) ? $option['field_template'] : $this->field_template($option); $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $title = isset($option['title']) ? $option['title'] : ""; $group_details = isset($option['details']) ? $option['details'] : ""; if ($is_pro == true) { $group_details = '' . $pro_text . ' ' . $group_details; } ob_start(); ?>
$value ): $group_title = isset($args[$index]['title']) ? $args[$index]['title'] : ''; $is_hide = isset($args_index_hide[$index]) ? $args_index_hide[$index] : false; //$link = $value['link']; $options = isset($args[$index]['options']) ? $args[$index]['options'] : array(); ?>

field_select($option); elseif (isset($option['type']) && $option['type'] === 'select2') $this->field_select2($option); elseif (isset($option['type']) && $option['type'] === 'checkbox') $this->field_checkbox($option); elseif (isset($option['type']) && $option['type'] === 'radio') $this->field_radio($option); elseif (isset($option['type']) && $option['type'] === 'radio_image') $this->field_radio_image($option); elseif (isset($option['type']) && $option['type'] === 'textarea') $this->field_textarea($option); elseif (isset($option['type']) && $option['type'] === 'scripts_js') $this->field_scripts_js($option); elseif (isset($option['type']) && $option['type'] === 'scripts_css') $this->field_scripts_css($option); elseif (isset($option['type']) && $option['type'] === 'number') $this->field_number($option); elseif (isset($option['type']) && $option['type'] === 'text') $this->field_text($option); elseif (isset($option['type']) && $option['type'] === 'text_icon') $this->field_text_icon($option); elseif (isset($option['type']) && $option['type'] === 'text_multi') $this->field_text_multi($option); elseif (isset($option['type']) && $option['type'] === 'hidden') $this->field_hidden($option); elseif (isset($option['type']) && $option['type'] === 'range') $this->field_range($option); elseif (isset($option['type']) && $option['type'] === 'colorpicker') $this->field_colorpicker($option); elseif (isset($option['type']) && $option['type'] === 'colorpicker_multi') $this->field_colorpicker_multi($option); elseif (isset($option['type']) && $option['type'] === 'datepicker') $this->field_datepicker($option); elseif (isset($option['type']) && $option['type'] === 'faq') $this->field_faq($option); elseif (isset($option['type']) && $option['type'] === 'addons_grid') $this->field_addons_grid($option); elseif (isset($option['type']) && $option['type'] === 'custom_html') $this->field_custom_html($option); elseif (isset($option['type']) && $option['type'] === 'repeatable') $this->field_repeatable($option); elseif (isset($option['type']) && $option['type'] === 'media') $this->field_media($option); elseif (isset($option['type']) && $option['type'] === 'media_url') $this->field_media_url($option); endforeach; endif; ?>
field_template($option); $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $title = isset($option['title']) ? $option['title'] : ""; $group_details = isset($option['details']) ? $option['details'] : ""; if ($is_pro == true) { $group_details = '' . $pro_text . ' ' . $group_details; } ob_start(); ?>
field_select($option); elseif (isset($option['type']) && $option['type'] === 'select2') $this->field_select2($option); elseif (isset($option['type']) && $option['type'] === 'checkbox') $this->field_checkbox($option); elseif (isset($option['type']) && $option['type'] === 'radio') $this->field_radio($option); elseif (isset($option['type']) && $option['type'] === 'radio_image') $this->field_radio_image($option); elseif (isset($option['type']) && $option['type'] === 'textarea') $this->field_textarea($option); elseif (isset($option['type']) && $option['type'] === 'scripts_js') $this->field_scripts_js($option); elseif (isset($option['type']) && $option['type'] === 'scripts_css') $this->field_scripts_css($option); elseif (isset($option['type']) && $option['type'] === 'number') $this->field_number($option); elseif (isset($option['type']) && $option['type'] === 'text') $this->field_text($option); elseif (isset($option['type']) && $option['type'] === 'text_icon') $this->field_text_icon($option); elseif (isset($option['type']) && $option['type'] === 'text_multi') $this->field_text_multi($option); elseif (isset($option['type']) && $option['type'] === 'hidden') $this->field_hidden($option); elseif (isset($option['type']) && $option['type'] === 'range') $this->field_range($option); elseif (isset($option['type']) && $option['type'] === 'colorpicker') $this->field_colorpicker($option); elseif (isset($option['type']) && $option['type'] === 'colorpicker_multi') $this->field_colorpicker_multi($option); elseif (isset($option['type']) && $option['type'] === 'datepicker') $this->field_datepicker($option); elseif (isset($option['type']) && $option['type'] === 'faq') $this->field_faq($option); elseif (isset($option['type']) && $option['type'] === 'addons_grid') $this->field_addons_grid($option); elseif (isset($option['type']) && $option['type'] === 'custom_html') $this->field_custom_html($option); elseif (isset($option['type']) && $option['type'] === 'repeatable') $this->field_repeatable($option); elseif (isset($option['type']) && $option['type'] === 'media') $this->field_media($option); elseif (isset($option['type']) && $option['type'] === 'media_url') $this->field_media_url($option); endforeach; endif; ?>
field_template($option); $title = isset($option['title']) ? $option['title'] : ""; $placeholder = isset($option['placeholder']) ? $option['placeholder'] : ""; $details = isset($option['details']) ? $option['details'] : ""; $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $default = isset($option['default']) ? $option['default'] : ''; $value = isset($option['value']) ? $option['value'] : ''; $value = !empty($value) ? $value : $default; $media_url = wp_get_attachment_url($value); $media_type = get_post_mime_type($value); $media_title = !empty($value) ? get_the_title($value) : __('Placeholder.jpg', $this->textdomain); $media_url = !empty($media_url) ? $media_url : $default; $media_url = !empty($media_url) ? $media_url : $placeholder; $media_basename = wp_basename($media_type); $field_name = !empty($field_name) ? $field_name : $id; $field_name = !empty($parent) ? $parent . '[' . $field_name . ']' : $field_name; ob_start(); //wp_enqueue_media(); ?>
textdomain); ?>
textdomain); ?>
field_template($option); $title = isset($option['title']) ? $option['title'] : ""; $details = isset($option['details']) ? $option['details'] : ""; $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $default = isset($option['default']) ? $option['default'] : ''; $value = isset($option['value']) ? $option['value'] : ''; $value = !empty($value) ? $value : $default; $media_url = $value; $media_type = get_post_mime_type($value); $media_title = get_the_title($value); $media_url = !empty($media_url) ? $media_url : ''; $media_url = !empty($media_url) ? $media_url : $placeholder_img; $field_name = !empty($field_name) ? $field_name : $id; $field_name = !empty($parent) ? $parent . '[' . $field_name . ']' : $field_name; wp_enqueue_media(); ob_start(); ?>
textdomain); ?>
textdomain); ?>
'; $limit = isset($option['limit']) ? $option['limit'] : ''; $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $field_template = isset($option['field_template']) ? $option['field_template'] : $this->field_template($option); $title = isset($option['title']) ? $option['title'] : ""; $details = isset($option['details']) ? $option['details'] : ""; $settings_tabs_field = new settings_tabs_field(); ob_start(); ?>
#TIMEINDEX
generate_field($field); ?>
textdomain); ?>
$val) : $title_field_val = !empty($val[$title_field]) ? $val[$title_field] : '#' . $count; ?>
$field) : $fieldId = $field['id']; $field_css_id = isset($field['css_id']) ? str_replace('TIMEINDEX', $index, $field['css_id']) : ''; $title_field_class = ($title_field == $field_index) ? 'title-field' : ''; ?>
generate_field($field); if ($collapsible) : ?>
field_template($option); $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $title = isset($option['title']) ? $option['title'] : ""; $details = isset($option['details']) ? $option['details'] : ""; if ($is_pro == true) { $details = '' . $pro_text . ' ' . $details; } if ($multiple) { $value = isset($option['value']) ? $option['value'] : array(); $field_name = !empty($parent) ? $parent . '[' . $id . '][]' : $id . '[]'; $default = isset($option['default']) ? $option['default'] : array(); } else { $value = isset($option['value']) ? $option['value'] : ''; $field_name = !empty($parent) ? $parent . '[' . $id . ']' : $id; $default = isset($option['default']) ? $option['default'] : ''; } $value = !empty($value) ? $value : $default; ob_start(); ?>
textdomain); ?>

field_template($option); $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; if ($multiple) { $value = isset($option['value']) ? $option['value'] : array(); $field_name = !empty($parent) ? $parent . '[' . $id . '][]' : $id . '[]'; $default = isset($option['default']) ? $option['default'] : array(); } else { $value = isset($option['value']) ? $option['value'] : ''; $field_name = !empty($parent) ? $parent . '[' . $id . ']' : $id; $default = isset($option['default']) ? $option['default'] : ''; } $value = !empty($value) ? $value : $default; //$value = get_post_meta( $post_id, $id, true ); $title = isset($option['title']) ? $option['title'] : ""; $details = isset($option['details']) ? $option['details'] : ""; $attributes_html = ''; foreach ($attributes as $attributeId => $attribute) : $attributes_html = $attributeId . '=' . $attribute . ' '; endforeach; ob_start(); ?> field_template($option); $remove_text = isset($option['remove_text']) ? $option['remove_text'] : ''; $sortable = isset($option['sortable']) ? $option['sortable'] : true; $allow_clone = isset($option['allow_clone']) ? $option['allow_clone'] : false; $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $title = isset($option['title']) ? $option['title'] : ""; $details = isset($option['details']) ? $option['details'] : ""; if ($is_pro == true) { $details = '' . $pro_text . ' ' . $details; } $field_name = !empty($parent) ? $parent . '[' . $id . ']' : $id; ob_start(); ?>
textdomain); ?>
field_template($option); $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $value = !empty($value) ? $value : $default; $title = isset($option['title']) ? $option['title'] : ""; $details = isset($option['details']) ? $option['details'] : ""; if ($is_pro == true) { $details = '' . $pro_text . ' ' . $details; } $field_name = !empty($parent) ? $parent . '[' . $id . ']' : $id; ob_start(); ?> field_template($option); $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $title = isset($option['title']) ? $option['title'] : ""; $details = isset($option['details']) ? $option['details'] : ""; if ($is_pro == true) { $details = '' . $pro_text . ' ' . $details; } $field_name = !empty($parent) ? $parent . '[' . $id . ']' : $id; ob_start(); ?> field_template($option); $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $value = !empty($value) ? $value : $default; $title = isset($option['title']) ? $option['title'] : ""; $details = isset($option['details']) ? $option['details'] : ""; if ($is_pro == true) { $details = '' . $pro_text . ' ' . $details; } $field_name = !empty($parent) ? $parent . '[' . $id . ']' : $id; ob_start(); ?> field_template($option); $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $value = !empty($value) ? $value : $default; $title = isset($option['title']) ? $option['title'] : ""; $details = isset($option['details']) ? $option['details'] : ""; if ($is_pro == true) { $details = '' . $pro_text . ' ' . $details; } $field_name = !empty($parent) ? $parent . '[' . $id . ']' : $id; $editor_settings = isset($option['editor_settings']) ? $option['editor_settings'] : array('textarea_name' => $field_name, 'teeny' => true, 'textarea_rows' => 15,); ob_start(); ?>
field_template($option); $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $title = isset($option['title']) ? $option['title'] : ""; $details = isset($option['details']) ? $option['details'] : ""; $option_value = empty($value) ? $default : $value; $field_name = !empty($parent) ? $parent . '[' . $id . ']' : $id; ob_start(); ?>
field_template($option); $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $value = isset($option['value']) ? $option['value'] : ''; $default = isset($option['default']) ? $option['default'] : ''; $value = !empty($value) ? $value : $default; $args = isset($option['args']) ? $option['args'] : ""; $min = isset($args['min']) ? $args['min'] : ''; $max = isset($args['max']) ? $args['max'] : ''; $step = isset($args['step']) ? $args['step'] : ''; $title = isset($option['title']) ? $option['title'] : ""; $details = isset($option['details']) ? $option['details'] : ""; $field_name = !empty($parent) ? $parent . '[' . $id . ']' : $id; ob_start(); ?>
field_template($option); $placeholder = isset($option['placeholder']) ? $option['placeholder'] : ""; $value = isset($option['value']) ? $option['value'] : ''; $default = isset($option['default']) ? $option['default'] : ''; $value = !empty($value) ? $value : $default; $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $title = isset($option['title']) ? $option['title'] : ""; $details = isset($option['details']) ? $option['details'] : ""; $field_name = !empty($parent) ? $parent . '[' . $id . ']' : $id; if ($is_pro == true) { $details = '' . $pro_text . ' ' . $details; } ob_start(); ?> field_template($option); $placeholder = isset($option['placeholder']) ? $option['placeholder'] : ""; $value = isset($option['value']) ? $option['value'] : ''; $default = isset($option['default']) ? $option['default'] : ''; $value = !empty($value) ? $value : $default; $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $title = isset($option['title']) ? $option['title'] : ""; $details = isset($option['details']) ? $option['details'] : ""; $field_name = !empty($parent) ? $parent . '[' . $id . ']' : $id; if ($is_pro == true) { $details = '' . $pro_text . ' ' . $details; } ob_start(); ?> field_template($option); $placeholder = isset($option['placeholder']) ? $option['placeholder'] : ""; $value = isset($option['value']) ? $option['value'] : ''; $default = isset($option['default']) ? $option['default'] : ''; $value = !empty($value) ? $value : $default; $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $title = isset($option['title']) ? $option['title'] : ""; $details = isset($option['details']) ? $option['details'] : ""; $field_name = !empty($parent) ? $parent . '[' . $id . ']' : $id; $settings = wp_enqueue_code_editor(array('type' => 'text/javascript')); $code_editor = wp_json_encode($settings); ob_start(); ?> field_template($option); $placeholder = isset($option['placeholder']) ? $option['placeholder'] : ""; $value = isset($option['value']) ? $option['value'] : ''; $default = isset($option['default']) ? $option['default'] : ''; $value = !empty($value) ? $value : $default; $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $title = isset($option['title']) ? $option['title'] : ""; $details = isset($option['details']) ? $option['details'] : ""; $settings = wp_enqueue_code_editor(array('type' => 'text/css')); $code_editor = wp_json_encode($settings); $field_name = !empty($parent) ? $parent . '[' . $id . ']' : $id; ?>
$value) : //$checked = ( $key == $option_value ) ? "checked" : ""; $checked = in_array($key, $option_value) ? "checked" : ""; $for = !empty($for) ? $for . '-' . $id . "-" . $key : $id . "-" . $key; ?>

field_template($option); $title = isset($option['title']) ? $option['title'] : ""; $details = isset($option['details']) ? $option['details'] : ""; $for = isset($option['for']) ? $option['for'] : ""; $args = isset($option['args']) ? $option['args'] : array(); $style = isset($option['style']) ? $option['style'] : array(); $style_inline = isset($style['inline']) ? $style['inline'] : true; $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $option_value = isset($option['value']) ? $option['value'] : ''; $default = isset($option['default']) ? $option['default'] : ''; $option_value = !empty($option_value) ? $option_value : $default; $field_name = !empty($parent) ? $parent . '[' . $id . ']' : $id; ob_start(); if (!empty($args)) foreach ($args as $key => $value) : $for = ''; $checked = ($key == $option_value) ? "checked" : ""; $for = !empty($for) ? $for . '-' . $css_id . "-" . $key : $css_id . "-" . $key; ?>
field_template($option); $args = isset($option['args']) ? $option['args'] : array(); //$args = is_array( $args ) ? $args : $this->generate_args_from_string( $args ); $option_value = isset($option['value']) ? $option['value'] : ''; $default = isset($option['default']) ? $option['default'] : ''; $lazy_load_img = isset($option['lazy_load_img']) ? $option['lazy_load_img'] : ''; $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $title = isset($option['title']) ? $option['title'] : ""; $details = isset($option['details']) ? $option['details'] : ""; $width = isset($option['width']) ? $option['width'] : "250px"; $field_name = !empty($parent) ? $parent . '[' . $id . ']' : $id; $option_value = empty($option_value) ? $default : $option_value; ob_start(); ?>
$value) : $name = isset($value['name']) ? $value['name'] : ''; $thumb = isset($value['thumb']) ? $value['thumb'] : ''; $disabled = isset($value['disabled']) ? $value['disabled'] : ''; $pro_msg = isset($value['pro_msg']) ? $value['pro_msg'] : ''; $link = isset($value['link']) ? $value['link'] : ''; $link_text = isset($value['link_text']) ? $value['link_text'] : 'Go'; $checked = ($key == $option_value) ? "checked" : ""; ?>
field_template($option); $placeholder = isset($option['placeholder']) ? $option['placeholder'] : ""; $format = isset($option['format']) ? $option['format'] : ""; $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $value = isset($option['value']) ? $option['value'] : ''; $default = isset($option['default']) ? $option['default'] : ''; $value = !empty($value) ? $value : $default; $title = isset($option['title']) ? $option['title'] : ""; $details = isset($option['details']) ? $option['details'] : ""; $field_name = !empty($parent) ? $parent . '[' . $id . ']' : $id; wp_enqueue_script('jquery-ui-datepicker'); wp_enqueue_style('jquery-ui'); ob_start(); ?> field_template($option); $placeholder = isset($option['placeholder']) ? $option['placeholder'] : ""; $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $value = isset($option['value']) ? $option['value'] : ''; $default = isset($option['default']) ? $option['default'] : ''; $value = !empty($value) ? $value : $default; $title = isset($option['title']) ? $option['title'] : ""; $details = isset($option['details']) ? $option['details'] : ""; $field_name = !empty($parent) ? $parent . '[' . $id . ']' : $id; ob_start(); ?> field_template($option); $args = isset($option['args']) ? $option['args'] : ""; $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $value = isset($option['value']) ? $option['value'] : ''; $default = isset($option['default']) ? $option['default'] : ''; $value = !empty($value) ? $value : $default; $title = isset($option['title']) ? $option['title'] : ""; $details = isset($option['details']) ? $option['details'] : ""; $field_name = !empty($parent) ? $parent . '[' . $id . ']' : $id; ob_start(); if (!empty($args)) : foreach ($args as $arg_key => $arg) : $item_value = isset($value[$arg_key]) ? $value[$arg_key] : $arg; ?>
field_template($option); $html = isset($option['html']) ? $option['html'] : ""; $is_pro = isset($option['is_pro']) ? $option['is_pro'] : false; $pro_text = isset($option['pro_text']) ? $option['pro_text'] : ''; $title = isset($option['title']) ? $option['title'] : ""; $details = isset($option['details']) ? $option['details'] : ""; echo sprintf($field_template, $title, $html, $details); } } } PK!(,,class-settings.phpnu[