$value ) { /* if the value is null, only add the key ( this is used for attributes that have no value, such as 'disabled', 'checked', etc ) */ if ( is_null( $value ) ) { $attributes .= ' ' . $key; } else { $attributes .= ' ' . $key . '="' . esc_attr( $value ) . '"'; } } if ( ! empty( $tag ) ) { $content = '<' . $tag . ( empty( $id ) ? '' : ' id="' . $id . '"' ) . ( empty( $class ) ? '' : ' class="' . $class . '"' ) . $attributes . '>' . $content . ''; } return $content; } /** * Get all the banned post types for the post list/grid. * * @return mixed|void */ public static function get_banned_post_types() { $banned_types = [ 'attachment', 'revision', 'nav_menu_item', 'custom_css', 'customize_changeset', 'oembed_cache', 'project', 'et_pb_layout', 'tcb_lightbox', 'focus_area', 'thrive_optin', 'thrive_ad_group', 'thrive_ad', 'thrive_slideshow', 'thrive_slide_item', 'tve_lead_shortcode', 'tve_lead_2s_lightbox', 'tve_form_type', 'tve_lead_group', 'tve_lead_1c_signup', TCB_CT_POST_TYPE, 'tcb_symbol', 'td_nm_notification', ]; /** * Filter that other plugins can hook to add / remove ban types from post grid */ return apply_filters( 'tcb_post_grid_banned_types', $banned_types ); } /** * Get the image source for the id. * This is used in TTB, don't delete it * * @param $image_id * @param string $size * * @return mixed */ public static function get_image_src( $image_id, $size = 'full' ) { $image_info = wp_get_attachment_image_src( $image_id, $size ); return empty( $image_info ) || empty( $image_info[0] ) ? '' : $image_info[0]; } /** * Get the pagination data that we want to localize. * * @return array */ public static function get_pagination_localized_data() { $localized_data = []; /* Apply a filter in case we want to add more pagination types from elsewhere. */ $all_pagination_types = apply_filters( 'tcb_post_list_pagination_types', TCB_Pagination::$all_types ); foreach ( $all_pagination_types as $type ) { $instance = tcb_pagination( $type ); $localized_data[ $instance->get_type() ] = $instance->get_content(); } /* we need this when we add new post lists to the page and they need a pagination element wrapper */ $localized_data['pagination_wrapper'] = tcb_pagination( TCB_Pagination::NONE )->render(); $localized_data['label_formats'] = array( 'pages' => tcb_template( 'pagination/label-pages.php', null, true ), 'posts' => tcb_template( 'pagination/label-posts.php', null, true ), ); return $localized_data; } /** * Adapt the pagination button component that inherits the button component by disabling some controls and adding new controls. * * @param $components * * @return mixed */ public static function get_pagination_button_config( $components ) { $components['pagination_button'] = $components['button']; unset( $components['button'] ); $all_controls = array_keys( $components['pagination_button']['config'] ); /* disable all the controls except the ones that we want to be enabled */ $disabled_controls = array_diff( $all_controls, [ 'MasterColor', 'icon_side' ] ); /* we have to add this manually */ $disabled_controls = array_merge( $disabled_controls, [ '.tcb-button-link-container' ] ); $components['pagination_button']['disabled_controls'] = array_values( $disabled_controls ); $components['pagination_button']['config']['icon_layout'] = array( 'config' => array( 'name' => __( 'Button Layout', 'thrive-cb' ), 'full-width' => true, 'buttons' => array( array( 'value' => 'text', 'text' => __( 'Text Only', 'thrive-cb' ), ), array( 'value' => 'icon', 'text' => __( 'Icon Only', 'thrive-cb' ), ), array( 'value' => 'text_plus_icon', 'text' => __( 'Icon&Text', 'thrive-cb' ), ), ), /* default is defined here so it can be overwritten by elements that inherit */ 'default' => 'text', ), 'extends' => 'ButtonGroup', ); $components['animation']['disabled_controls'] = [ '.btn-inline.anim-link', '.btn-inline.anim-popup' ]; /* add the root prefix in order to make this more specific than paragraph spacing settings from containers */ $components['layout']['config']['MarginAndPadding']['css_prefix'] = tcb_selection_root() . ' '; $components['layout']['disabled_controls'] = [ 'Display', 'Alignment' ]; $components['scroll'] = [ 'hidden' => true ]; $components['responsive'] = [ 'hidden' => true ]; $components['shared-styles'] = [ 'hidden' => true ]; return $components; } /** * Get the date/time format options for the wordpress date settings, and append a custom setting. * Can return an associative array of key-value pairs, or multiple arrays of name/value. * * @param string $type * @param bool $key_value_pairs * * @return array */ public static function get_post_date_format_options( $type = 'date', $key_value_pairs = false ) { if ( $type === 'time' ) { /** * Filters the default time formats. * * @param string[] Array of default time formats. */ $formats = array_unique( apply_filters( 'time_formats', array( __( 'g:i a', 'thrive-cb' ), 'g:i A', 'H:i' ) ) ); } else { /** * Filters the default date formats. * * @param string[] Array of default date formats. */ $formats = array_unique( apply_filters( 'date_formats', array( __( 'F j, Y', 'thrive-cb' ), 'Y-m-d', 'm/d/Y', 'd/m/Y' ) ) ); } $custom_option_name = __( 'Custom', 'thrive-cb' ); if ( $key_value_pairs ) { foreach ( $formats as $format ) { $options[ $format ] = get_the_time( $format ); } $options['custom'] = $custom_option_name; } else { $options = array_map( function ( $format ) { return array( 'name' => get_the_time( $format ), 'value' => $format, ); }, $formats ); $options[] = [ 'name' => $custom_option_name, 'value' => '', ]; } return $options; } /** * Get some inline shortcodes for the Pagination Label element. * * @return array */ public static function get_pagination_inline_shortcodes() { return array( 'Post list pagination' => array( array( 'option' => __( 'Current page number', 'thrive-cb' ), 'value' => 'tcb_pagination_current_page', ), array( 'option' => __( 'Total number of pages', 'thrive-cb' ), 'value' => 'tcb_pagination_total_pages', ), array( 'option' => __( 'Number of posts on this page', 'thrive-cb' ), 'value' => 'tcb_pagination_current_posts', ), array( 'option' => __( 'Total number of posts', 'thrive-cb' ), 'value' => 'tcb_pagination_total_posts', ), ), ); } /** * Get inline shortcodes for the Post List element. * * @return array */ public static function get_post_list_inline_shortcodes() { $date_format_options = static::get_post_date_format_options( 'date', true ); $date_formats = array_keys( $date_format_options ); $time_format_options = static::get_post_date_format_options( 'time', true ); return array( 'Post' => array( array( 'name' => __( 'Post title', 'thrive-cb' ), 'option' => __( 'Post title', 'thrive-cb' ), 'value' => 'tcb_post_title', 'input' => array( 'link' => array( 'type' => 'checkbox', 'label' => __( 'Link to post title', 'thrive-cb' ), 'value' => true, ), 'target' => array( 'type' => 'checkbox', 'label' => __( 'Open in new tab', 'thrive-cb' ), 'value' => false, 'disable_br' => true, ), 'rel' => array( 'type' => 'checkbox', 'label' => __( 'No follow', 'thrive-cb' ), 'value' => false, ), ), ), array( 'name' => date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ), 'option' => __( 'Post date', 'thrive-cb' ), 'value' => 'tcb_post_published_date', 'input' => array( 'type' => array( 'type' => 'select', 'label' => __( 'Display', 'thrive-cb' ), 'value' => array( 'published' => __( 'Published date', 'thrive-cb' ), 'modified' => __( 'Modified date', 'thrive-cb' ), ), ), 'date-format-select' => array( 'type' => 'select', 'label' => __( 'Date format', 'thrive-cb' ), 'value' => $date_format_options, ), 'date-format' => array( 'type' => 'input', 'label' => __( 'Format string', 'thrive-cb' ), 'value' => $date_formats[0], ), 'show-time' => array( 'type' => 'checkbox', 'label' => __( 'Show time?', 'thrive-cb' ), 'value' => false, ), 'time-format-select' => array( 'type' => 'select', 'label' => __( 'Time format', 'thrive-cb' ), 'value' => $time_format_options, ), 'time-format' => array( 'type' => 'input', 'label' => __( 'Format string', 'thrive-cb' ), 'value' => '', ), 'link' => array( 'type' => 'checkbox', 'label' => __( 'Link to archive', 'thrive-cb' ), 'value' => false, ), 'target' => array( 'type' => 'checkbox', 'label' => __( 'Open in new tab', 'thrive-cb' ), 'value' => true, 'disable_br' => true, ), 'rel' => array( 'type' => 'checkbox', 'label' => __( 'No follow', 'thrive-cb' ), 'value' => false, ), ), ), array( 'name' => __( 'Author name', 'thrive-cb' ), 'option' => __( 'Author name', 'thrive-cb' ), 'value' => 'tcb_post_author_name', 'input' => array( 'link' => array( 'type' => 'checkbox', 'label' => __( 'Link to author profile', 'thrive-cb' ), 'value' => false, ), 'target' => array( 'type' => 'checkbox', 'label' => __( 'Open in new tab', 'thrive-cb' ), 'value' => true, 'disable_br' => true, ), 'rel' => array( 'type' => 'checkbox', 'label' => __( 'No follow', 'thrive-cb' ), 'value' => false, ), ), ), array( 'name' => __( 'Author role', 'thrive-cb' ), 'option' => __( 'Author role', 'thrive-cb' ), 'value' => 'tcb_post_author_role', ), array( 'name' => __( 'Author bio', 'thrive-cb' ), 'option' => __( 'Author bio', 'thrive-cb' ), 'value' => 'tcb_post_author_bio', ), array( 'name' => 24, 'option' => __( 'Number of comments', 'thrive-cb' ), 'value' => 'tcb_post_comments_number', ), ), 'Taxonomies' => array( array( 'name' => __( 'Category-1, Category-2, Category-3', 'thrive-cb' ), 'option' => __( 'List of categories', 'thrive-cb' ), 'value' => 'tcb_post_categories', 'input' => array( 'link' => array( 'type' => 'checkbox', 'label' => __( 'Link to archive', 'thrive-cb' ), 'value' => true, ), 'target' => array( 'type' => 'checkbox', 'label' => __( 'Open in new tab', 'thrive-cb' ), 'value' => false, 'disable_br' => true, ), 'rel' => array( 'type' => 'checkbox', 'label' => __( 'No follow', 'thrive-cb' ), 'value' => false, ), ), ), array( 'name' => __( 'Tag-1, Tag-2, Tag-3', 'thrive-cb' ), 'option' => __( 'List of tags', 'thrive-cb' ), 'value' => 'tcb_post_tags', 'input' => array( 'default' => array( 'type' => 'input', 'label' => __( 'Default value', 'thrive-cb' ), 'value' => '', ), 'link' => array( 'type' => 'checkbox', 'label' => __( 'Link to archive', 'thrive-cb' ), 'value' => true, ), 'target' => array( 'type' => 'checkbox', 'label' => __( 'Open in new tab', 'thrive-cb' ), 'value' => false, 'disable_br' => true, ), 'rel' => array( 'type' => 'checkbox', 'label' => __( 'No follow', 'thrive-cb' ), 'value' => false, ), ), ), ), ); } /** * Return the post formats supported by the current theme. * * @return array|mixed */ public static function get_supported_post_formats() { $post_formats = []; if ( current_theme_supports( 'post-formats' ) ) { $post_formats = get_theme_support( 'post-formats' ); if ( is_array( $post_formats[0] ) ) { $post_formats = $post_formats[0]; } } return $post_formats; } /** * Return the preview URL for this post( symbol or anything that has post meta ) ID along with height/width. * If no URL is found, this can return a placeholder, if one was provided through the parameter. * * @param int $post_id * @param string $sub_path * @param array $placeholder_data * * @return array|mixed */ public static function get_thumb_data( $post_id, $sub_path, $placeholder_data = [] ) { $upload_dir = wp_upload_dir(); $path = $sub_path . '/' . $post_id . '.png'; $thumb_path = static::get_uploads_path( $path, $upload_dir ); $thumb_url = trailingslashit( $upload_dir['baseurl'] ) . $path; /* check if we have preview data in the post meta */ $thumb_data = static::get_thumbnail_data_from_id( $post_id ); /* if the post meta is empty, look inside the file and get the data directly from the it */ if ( empty( $thumb_data['url'] ) ) { if ( file_exists( $thumb_path ) && ini_get( 'allow_url_fopen' ) ) { list( $width, $height ) = getimagesize( $thumb_path ); $thumb_data = [ 'url' => $thumb_url, 'h' => $height, 'w' => $width, ]; } else { /* if no file is found and no placeholder is provided, return all the values set to blank */ if ( empty( $placeholder_data ) ) { $thumb_data = [ 'url' => '', 'h' => '', 'w' => '', ]; } else { /* if a placeholder is provided, use it */ $thumb_data = $placeholder_data; } } } return $thumb_data; } /** * Return the uploads physical path. * Things can be appended to it by providing something in $path. * * @param string $path * @param array $upload_dir * * @return string */ public static function get_uploads_path( $path = '', $upload_dir = [] ) { if ( empty( $upload_dir ) ) { $upload_dir = wp_upload_dir(); } return trailingslashit( $upload_dir['basedir'] ) . $path; } /** * Retrieve the image metadata for the provided post ID. * * @param $post_id * * @return array */ public static function get_thumbnail_data_from_id( $post_id ) { return get_post_meta( $post_id, TCB_THUMBNAIL_META_KEY, true ); } /** * Set the image metadata for the provided post ID. * * @param $post_id * @param $thumb_data */ public static function save_thumbnail_data( $post_id, $thumb_data ) { update_post_meta( $post_id, TCB_THUMBNAIL_META_KEY, $thumb_data ); } /** * Check if we're inside the editor and filter the result. * * @param boolean $ajax_check * * @return mixed|void */ public static function in_editor_render( $ajax_check = false ) { return apply_filters( 'tcb_in_editor_render', is_editor_page_raw( $ajax_check ) ); } /** * Check if we're in a REST request. * * @return bool */ public static function is_rest() { return defined( 'REST_REQUEST' ) && REST_REQUEST; } /** * Returns the content at the given path * A more simpler and general version of tcb_template() * * @param string $full_path * * @return string */ public static function return_part( $full_path ) { $content = ''; if ( file_exists( $full_path ) ) { ob_start(); include $full_path; $content = trim( ob_get_contents() ); ob_end_clean(); } return $content; } /** * parse and combine all font links => ensure a single request is sent out for a font * Combines * * @import url("//fonts.googleapis.com/css?family=Muli:400,800,900,600&subset=latin"); * + * @import url("//fonts.googleapis.com/css?family=Muli:400,900,600&subset=latin"); * into: * @import url("//fonts.googleapis.com/css?family=Muli:400,600,800,900&subset=latin"); * * @param array $imports * @param string $return return type. Can be 'link' or 'import' * * @return array array of unique @import statements */ public static function merge_google_fonts( $imports, $return = 'import' ) { return array_map( function ( $font_data ) use ( $return ) { return TCB_Utils::build_font_string( $font_data, $return ); }, array_values( static::parse_css_imports( $imports ) ) ); } /** * Parses an array of CSS import statements and structures the data based on family and weights * Merges all weights for a family * * @param array $imports * * @return array */ public static function parse_css_imports( $imports ) { $data = []; foreach ( array_unique( $imports ) as $import ) { $font = static::parse_css_import( $import ); if ( ! isset( $data[ $font['family'] ] ) ) { $data[ $font['family'] ] = $font; } else { $data[ $font['family'] ]['weights'] += $font['weights']; } } return $data; } /** * Parses an `@import` statement and generates an array containing useful data about the font (family, weights, subset etc) * !!IMPORTANT: this only treats google fonts!! * * @param string $import import string * * @return array */ public static function parse_css_import( $import ) { $data = [ 'base_url' => '', 'family' => '', 'weights' => [], 'query' => [], ]; $import = str_replace( array( '"', "'", '@import url(', ')', ';' ), '', $import ); $result = parse_url( $import ); if ( $result ) { $data['base_url'] = ( isset( $result['host'] ) ? 'https://' . $result['host'] : '' ) . $result['path']; if ( ! empty( $result['query'] ) ) { parse_str( $result['query'], $query ); list( $family, $weights ) = explode( ':', $query['family'] ); unset( $query['family'] ); $data['family'] = $family; /* hold weights as keys, so it's less expensive to get unique weights */ $data['weights'] = array_flip( array_filter( explode( ',', $weights ), function ( $weight ) { /** * Preserve italic weights */ if ( substr( $weight, - 1 ) === 'i' ) { $weight = substr( $weight, 0, - 1 ); } return filter_var( $weight, FILTER_VALIDATE_INT ); } ) ); $data['query'] = $query; } } return $data; } /** * Builds an @import CSS rule based on $font_data * * @param array $font_data array in the form returned by self::parse_css_import * @param string $return_type Controls the output. Can be 'link'/'url' or 'import' * * @return string */ public static function build_font_string( $font_data, $return_type = 'import' ) { if ( empty( $font_data ) ) { return ''; } $font_data['query'] = array( 'family' => $font_data['family'] . ':' . implode( ',', array_keys( $font_data['weights'] ) ) ) + $font_data['query']; /** * https://web.dev/font-display/ * this ensures font is readable while external gfonts are loaded * */ $font_data['query']['display'] = 'swap'; $result = $font_data['base_url'] . '?' . rawurldecode( http_build_query( $font_data['query'], '', '&' ) ); if ( $return_type === 'import' ) { $result = '@import url("' . $result . '");'; } return $result; } /** * Create a rest nonce for our ajax requests * * @return mixed|void */ public static function create_nonce() { return wp_create_nonce( 'wp_rest' ); } /** * Mark that we started processing custom CSS */ public static function before_custom_css_processing() { $GLOBALS[ TVE_IS_PROCESSING_CUSTOM_CSS ] = true; } /** * Mark that we finished processing custom CSS */ public static function after_custom_css_processing() { $GLOBALS[ TVE_IS_PROCESSING_CUSTOM_CSS ] = false; } /** * Check if we're currently processing CSS. * Useful in shortcode handlers in case you want to see if the shortcode is called from CSS or HTML. * * @return bool */ public static function is_processing_custom_css() { return ! empty( $GLOBALS[ TVE_IS_PROCESSING_CUSTOM_CSS ] ); } /** * If we're in a debug environment, we're not minifying JS files. * The constant should be defined in the wp-config.php file * * @return string */ public static function get_js_suffix() { return tve_dash_is_debug_on() ? '.js' : '.min.js'; } /** * Shortcut function that will output json encoded data or return it as it is based on the second parameter. * * @param mixed $data * @param bool $output if true, it will use WordPress's wp_send_json to output data * * @return mixed */ public static function maybe_send_json( $data, $output = true ) { if ( $output ) { wp_send_json( $data ); } else { return $data; } } /** * Write file using wordpress functionality * * @param $file * @param string $content * @param int $mode * * @return boolean */ public static function write_file( $file, $content = '', $mode = 0644 ) { require_once( ABSPATH . 'wp-admin/includes/file.php' ); /* make sure that the FS_METHOD defaults to 'direct' so we use WP_Filesystem_direct in order to avoid the FTP implementations of put_contents */ defined( 'FS_METHOD' ) || define( 'FS_METHOD', 'direct' ); global $wp_filesystem; return WP_Filesystem() && $wp_filesystem->put_contents( $file, $content, $mode ); } /** * Restore some fields of the $_POST data that have been previously replaced from javascript * Wordfence blocks certain strings in POST - these are replaced from javascript before sending the ajax request to some equivalents * Currently, this method handles: * - ( these are replaced from javascript into <_wafsvg_ and replace_string */ $replace_map = [ '_wafsvg_' => 'svg', ]; $search = array_keys( $replace_map ); $replace = array_values( $replace_map ); foreach ( $field_list as $field ) { if ( ! empty( $_POST[ $field ] ) && is_string( $_POST[ $field ] ) ) { $_POST[ $field ] = str_replace( $search, $replace, $_POST[ $field ] ); } } } /** * @return string[] */ public static function get_api_list_with_tag_support() { $apis_that_support_tags = [ 'activecampaign', 'aweber', 'convertkit', 'drip', 'klicktipp', 'mailchimp', 'sendlane', 'zapier' ]; return apply_filters( 'tcb_lead_generation_apis_with_tag_support', $apis_that_support_tags ); } /** * Get some values from the queried object. * * @return array */ public static function get_filtered_queried_object() { $queried_object = get_queried_object(); $qo = []; if ( $queried_object ) { /* when we're using demo content the $queried_object can get weird, so check stuff before using it */ if ( property_exists( $queried_object, 'data' ) && property_exists( $queried_object->data, 'ID' ) && ! empty( $queried_object->data->ID ) ) { $qo ['ID'] = $queried_object->data->ID; } elseif ( property_exists( $queried_object, 'ID' ) && ! empty( $queried_object->ID ) ) { $qo ['ID'] = $queried_object->ID; } /* only keep the values for the specified keys */ $qo = array_filter( (array) $queried_object, function ( $key ) { return in_array( $key, [ 'post_author', 'taxonomy', 'term_id', 'ID' ] ); }, ARRAY_FILTER_USE_KEY ); } /** * Filters the queried object needed in the frontend localization (used in generating "Edit with TTB" links that will show the same content) * * @param array $qo associative array the should have at least an `ID` key */ return apply_filters( 'tcb_frontend_queried_object', $qo ); } /** * @return string */ public static function get_placeholder_url() { return tve_editor_url( 'admin/assets/images/no-template-preview.jpg' ); } /** * Get placeholder information for the template preview * * @return array */ public static function get_placeholder_data() { return [ 'url' => static::get_placeholder_url(), /* hardcoded sizes taken from 'no-template-preview.jpg' */ 'h' => '120', 'w' => '250', ]; } /** * Used to hide elements on certain post types, called in hide() method from element classes * * @return bool */ public static function should_hide_element_on_blacklisted_post_types() { $blacklisted_post_types = static::get_banned_post_types(); $blacklisted_post_types = array_diff( $blacklisted_post_types, [ 'tcb_symbol' ] ); return in_array( get_post_type( get_the_ID() ), $blacklisted_post_types ); } /** * Get TVA_Order using product ID and user ID. * * @param int $product_id * @param int $user_id * * @return TVA_Order|null */ public static function get_tva_order_by_product_and_user( $product_id, $user_id ) { global $wpdb; $query = $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}tva_orders WHERE user_id = %d AND ID IN (SELECT order_id FROM {$wpdb->prefix}tva_order_items WHERE product_id = %d) AND status = 1", $user_id, $product_id ); $order_id = $wpdb->get_results( $query ); // Check if an order ID was found then return the TVA_Order object. if ( ! empty( $order_id ) && ! empty( $order_id[0] ) ) { return new TVA_Order( (int) $order_id[0]->id ); } return null; } }