'::', '[/]' => ';;', '[<]' => ';:', '[>]' => ':;', '[\[]' => '*;', '[\]]' => ';*', '[\']' => ':*', '[\"]' => '*:', '[ ]' => '**', ]; const ACF_PREFIX = 'acf_'; private $field_types = [ 'link' => [ 'text', 'image', 'email', 'url', 'file', 'page_link', 'link' ], 'image' => [ 'image', 'text' ], 'text' => [ 'text', 'textarea', 'number', 'range', 'email', 'url', 'password', 'true_false', 'date_picker', 'date_time_picker', 'time_picker', 'checkbox' ], 'video' => [ 'file', 'url', 'text' ], 'audio' => [ 'file', 'text' ], 'map' => [ 'map', 'text' ], 'countdown' => [ 'date_time_picker' ], 'number' => [ 'number', 'range' ], 'color' => [ 'color_picker' ], ]; private $postlist_field_types = [ 'link' => [ 'text', 'image', 'email', 'url', 'file', 'page_link', 'link' ], 'image' => [ 'image', 'text' ], 'text' => [ 'text', 'textarea', 'number', 'range', 'email', 'url', 'password', 'true_false', 'date_picker', 'date_time_picker', 'time_picker', 'checkbox' ], 'number' => [ 'number', 'range' ], 'countdown' => [ 'date_time_picker' ], 'video' => [ 'file', 'url', 'text' ], 'audio' => [ 'file', 'text' ], 'color' => [ 'color_picker' ], ]; /* some WooCommerce fields we're here, we've created a new category for them */ public static $whitelisted_fields = []; public static $blacklisted_fields = [ '\_%', 'thrive%', 'tcb%', 'wp%', 'tve%', ]; public static $protected_fields = [ '_', //General protected metadata starts with '_' 'thrive_', //Thrive Architect metadata 'thrv_', 'tve_', 'td_nm_', 'tcb_', 'tcb2_', 'tcm_', //Thrive Comments metadata 'tva_', //Thrive Apprentice metadata 'tu_', //Thrive Ultimate metadata 'tqb_', //Thrive Quiz Builder metadata 'tvo_', //Thrive Ovation metadata '_tho', //Thrive Headline Optimiser metadata 'is_control', //Thrive Optimize metadata 'sections', 'page_content_', //some meta that might come from the clodu 'theme_skin', /** Protected Metadata for other plugins**/ 'total_sales', //WooCommerce metadata 'rank_math_', //Rank Math SEO metadata 'pb_original_content', //PageBuilder meta content breaks editor localization 'export_id', //symbols post meta 'aFhfc_', //hurrytime plugin saves some css as metadata and will break CF functionality ]; private $video_regex = array( '/https?:\/\/(.+)\.(cdn\.(vooplayer|spotlightr)\.com)\/(publish|watch)\/(.+)/' => 'vooplayer', '/^(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|shorts\/|\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/' => 'youtube', '/^(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|list\/|playlist\?list=|playlist\?.+&list=))((\w|-){18})(?:\S+)?$/' => 'youtube', '/(http|https)?:\/\/(www\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/([^\/]*)\/videos\/|)(\d+)(?:|\/\?)\/?([a-zA-Z0-9]+)?/' => 'vimeo', '/https?:\/\/(.+)?(wistia.com|wi.st)\/(?:medias|embed)\/(.+)/' => 'wistia', '/https?:\/\/(.+)?fast.wistia.net\/embed\/(.+?)\/(.+)/' => 'wistia', ); /** * Holds the value of Custom Fields Plugin - External Fields * * @var array */ private $external_fields; /** * @var TCB_Custom_Fields_Shortcode */ private static $instance; /** * Holds the value of the user shortcodes * * @var array */ private $user_shortcodes = []; /** * Singleton implementation for TCB_Custom_Fields_Shortcode * * @return TCB_Custom_Fields_Shortcode */ public static function get_instance() { if ( self::$instance === null ) { self::$instance = new self(); } return self::$instance; } /** * TCB_Custom_Fields_Shortcode constructor. */ private function __construct() { /** * User Shortcodes Configuration */ $this->user_shortcodes = array( 'tcb_username_field' => array( 'name' => __( 'Username', 'thrive-cb' ), 'property' => 'user_login', 'not_logged_value' => __( 'Username', 'thrive-cb' ), ), 'tcb_first_name_field' => array( 'name' => __( 'First Name', 'thrive-cb' ), 'property' => 'user_firstname', 'not_logged_value' => __( 'John', 'thrive-cb' ), ), 'tcb_last_name_field' => array( 'name' => __( 'Last Name', 'thrive-cb' ), 'property' => 'user_lastname', 'not_logged_value' => __( 'Doe', 'thrive-cb' ), ), ); /** * Filters */ add_filter( 'tcb_content_allowed_shortcodes', [ $this, 'allowed_shortcodes' ] ); add_filter( 'tcb_dynamiclink_data', [ $this, 'global_links_shortcodes' ] ); add_filter( 'tcb_inline_shortcodes', [ $this, 'tcb_inline_shortcodes' ], 11 ); add_filter( 'wp_get_attachment_image_attributes', [ $this, 'alter_custom_fields_image_attributes' ] ); /** * Adds shortcodes callbacks */ add_shortcode( static::GLOBAL_SHORTCODE_URL, [ $this, 'global_shortcode_url_link' ] ); add_shortcode( static::GLOBAL_SHORTCODE_DATA, [ $this, 'global_shortcode_url_data' ] ); add_shortcode( 'tcb_custom_field', [ $this, 'render_custom_fields' ] ); add_shortcode( 'tcb_dynamic_field', [ $this, 'render_dynamic_field' ] ); foreach ( $this->user_shortcodes as $key => $name ) { add_shortcode( $key, [ $this, 'render_user_shortcode' ] ); } add_action( 'tcb_get_extra_global_variables', [ $this, 'output_custom_fields_variables' ] ); add_filter( 'tcb_main_frame_localize', [ $this, 'custom_fields_localization' ] ); add_filter( 'tcb_get_post_list_variables', [ $this, 'output_post_list_variables' ], 10, 2 ); /** Added ACF script to solve error thrown by ACF 5.9 update */ add_action( 'tcb_main_frame_enqueue', static function () { wp_enqueue_script( 'acf' ); } ); } /** * @param array $data * * @return array */ public function custom_fields_localization( $data = [] ) { if ( $this->has_external_fields_colors() ) { $external_fields = $this->get_all_external_fields(); $data['colors']['custom_fields'] = $this->prepare_custom_fields_colors( get_the_ID(), $external_fields['color'] ); $data['has_acf_colors'] = 1; } $data['acf_is_active'] = tvd_has_external_fields_plugins() ? 1 : 0; return $data; } /** * Prepares the Custom Fields Colors for TAR * * @param $post_id * @param array $colors * * @return array */ public function prepare_custom_fields_colors( $post_id, $colors = [] ) { $return = []; /** * For Post List Element the $id_suffix is empty because it needs to be propagated along all posts */ $id_suffix = ( empty ( $post_id ) ? '' : '_' . $post_id ); foreach ( $colors as $field_id => $field_value ) { $return[] = array( 'active' => 1, 'custom_name' => 1, 'color' => $field_value['value'], 'id' => sanitize_title( $field_id ) . $id_suffix, 'name' => sanitize_title( $field_value['label'] ), 'label' => $field_value['label'], ); } return $return; } /** * Outputs the post list variables inside the post list dynamic style * * @param string $variables * @param int $post_id * * @return string */ public function output_post_list_variables( $variables, $post_id ) { $post_list_fields = $this->get_all_external_postlist_fields( $post_id ); $variables .= $this->get_custom_fields_variables( $post_list_fields ); return $variables; } /** * Output the custom fields - color variables */ public function output_custom_fields_variables() { $external_fields = $this->get_all_external_fields(); $suffix = '_' . get_the_ID(); echo $this->get_custom_fields_variables( $external_fields, $suffix ); } /** * Used for getting custom fields variables * * Used for PostList Element and general content * * @param array $external_fields * @param string $suffix * * @return string */ private function get_custom_fields_variables( $external_fields = [], $suffix = '' ) { $variables = ''; if ( ! empty( $external_fields['color'] ) && is_array( $external_fields['color'] ) ) { foreach ( $external_fields['color'] as $field_id => $field_value ) { /* fallback for the dynamic color */ $fallback = 'hsl(var(--tcb-main-master-h,0), var(--tcb-main-master-s,0%),var(--tcb-main-master-l,4%))'; if ( empty( $field_value['value'] ) ) { $field_value['value'] = $fallback; } $variables .= TVE_DYNAMIC_COLOR_VAR_CSS_PREFIX . sanitize_title( $field_id ) . $suffix . ':' . $field_value['value'] . ';'; $variables .= TVE_DYNAMIC_COLOR_VAR_CSS_PREFIX . sanitize_title( $field_id ) . $suffix . '-default' . ':' . $fallback . ';'; } } return $variables; } /** * Check if the user has saved colors as external fields * * @return bool */ public function has_external_fields_colors() { if ( ! tvd_has_external_fields_plugins() ) { return false; } $external_fields = $this->get_all_external_fields(); return ! empty( $external_fields['color'] ); } /** * Removes the srcset and sizes from images from editor page * * @param array $attr * * @return array */ public function alter_custom_fields_image_attributes( $attr = [] ) { if ( is_editor_page_raw( true ) && ( isset( $attr['data-d-f'] ) || isset( $attr['data-c-f-id'] ) ) ) { unset( $attr['srcset'], $attr['sizes'] ); } return $attr; } /** * Endpoint for render dynamic fields shortcode * * @param array $args * @param $content * @param $tag * * @return string */ public function render_dynamic_field( $args, $content, $tag ) { if ( TCB_Post_List::is_outside_post_list_render() && isset( $args['post_list'] ) ) { return '[' . $tag . ' ' . implode( ' ', array_map( function ( $k, $v ) { return $k . '="' . htmlspecialchars( $v ) . '"'; }, array_keys( $args ), $args ) ) . ']'; } if ( is_array( $args ) && ! empty( $args['type'] ) && is_string( $args['type'] ) ) { $method_name = 'render_dynamic_field_' . $args['type']; if ( method_exists( $this, $method_name ) ) { return $this->$method_name( $args ); } /** * Allow other custom fields to have callbacks here * Dynamic hook depending on the type * Used in ThriveApprentice for the Verification Page custom field callback * * @param string $return * @param array $args */ return apply_filters( 'tcb_custom_fields_render_' . $args['type'], '', $args ); } } /** * Renders the dynamic filed author * * @return string */ private function render_dynamic_field_author( $args = [] ) { $post_title = the_title_attribute( [ 'echo' => 0 ] ); $placeholder_url = tve_editor_url( self::AUTHOR_IMAGE_PLACEHOLDER_URL ); // check if author archive page. if ( is_author() ) { $post_author = get_queried_object_id(); } else { $post_author = get_post_field( 'post_author', get_the_ID() ); } $args['alt'] = ! empty( $args['alt'] ) ? $args['alt'] : $post_title; $args['title'] = ! empty( $args['title'] ) ? $args['title'] : $post_title; $args['data-classes'] = ! empty( $args['data-classes'] ) ? $args['data-classes'] : 'tve_image'; $args['data-css'] = ! empty( $args['data-css'] ) ? $args['data-css'] : ''; $args['loading'] = ! empty( $args['loading'] ) ? $args['loading'] : ''; /** * Allow vendors to filter author dynamic field * - e.g.: TA course author */ if ( ! get_option( 'show_avatars' ) ) { return sprintf( '%s', $placeholder_url, $args['alt'], $args['data-classes'], $args['title'], $args['data-css'], $args['loading'] ? 'loading="lazy"' : '' ); } return get_avatar( apply_filters( 'tcb_dynamic_field_author', $post_author ), 256, $placeholder_url, $args['alt'], [ 'class' => $args['data-classes'], 'extra_attr' => ' data-d-f="author" title="' . $args['title'] . '" width="500" height="500" data-css="' . $args['data-css'] . '"', 'loading' => $args['loading'], ] ); } /** * Renders the dynamic filed user * * @param array $args * * @return string */ private function render_dynamic_field_user( $args = [] ) { $user_id = tve_get_current_user_id(); if ( ! $user_id ) { return ''; } $args = wp_parse_args( $args, [ 'alt' => '', 'title' => '', 'data-css' => '', 'data-classes' => 'tve_image', ] ); return get_avatar( $user_id, 256, '', $args['alt'], [ 'class' => $args['data-classes'], 'extra_attr' => 'loading="lazy" data-d-f="user" title="' . $args['title'] . '" width="500" height="500" data-css="' . $args['data-css'] . '"', ] ); } /** * Renders the dynamic filed featured image * * @return string */ private function render_dynamic_field_featured( $args = [] ) { $featured_image_url = tve_editor_url( 'editor/css/images/featured_image.png' ); $post_title = the_title_attribute( [ 'echo' => 0 ] ); $args['alt'] = ! empty( $args['alt'] ) ? $args['alt'] : $post_title; $args['title'] = ! empty( $args['title'] ) ? $args['title'] : $post_title; $args['data-css'] = ! empty( $args['data-css'] ) ? $args['data-css'] : ''; $loading = ''; if ( ! empty( $args['loading'] ) ) { $loading = 'loading="lazy"'; } if ( has_post_thumbnail() ) { $thumbnail_id = get_post_thumbnail_id(); $args['data-classes'] = ! empty( $args['data-classes'] ) ? $args['data-classes'] : 'tve_image wp-image-' . $thumbnail_id; $img_attr = [ 'class' => $args['data-classes'], 'title' => $args['title'], 'alt' => $args['alt'], 'data-id' => $thumbnail_id, 'data-d-f' => 'featured', 'data-css' => $args['data-css'], ]; $img_attr['loading'] = $loading ? 'lazy' : false; return wp_get_attachment_image( $thumbnail_id, 'full', false, $img_attr ); } $args['data-classes'] = ! empty( $args['data-classes'] ) ? $args['data-classes'] : 'tve_image'; /** * Allow vendors to filter featured image dynamic field * - e.g.: TA course cover image for course overview post */ $featured_image_url = apply_filters( 'tcb_dynamic_field_featured', $featured_image_url ); return '' . $args['alt'] . ''; } /** * Endpoint for render custom fields shortcode * * @param array $args * * @return mixed */ public function render_custom_fields( $args = [], $params = null ) { if ( is_array( $args ) && ! empty( $args['data-id'] ) && ! empty( $args['data-field-type'] ) && is_string( $args['data-field-type'] ) ) { $method_name = 'render_custom_fields_' . $args['data-field-type']; if ( method_exists( $this, $method_name ) ) { return $this->$method_name( $args, $params ); //TODO: maybe do not send all the args and send only what is needed. Enhanced CF part 3 } } } /** * Returns the custom fields shortcode parameters shortcode parameters * * @param $shortcode_id * @param string $type * * @return array */ private function get_custom_fields_shortcode_params( $shortcode_id, $type = '' ) { $custom_fields = $this->get_all_external_fields(); $params = ! empty( $custom_fields[ $type ][ $shortcode_id ] ) && is_array( $custom_fields[ $type ][ $shortcode_id ] ) ? $custom_fields[ $type ][ $shortcode_id ] : []; return $params; } public function render_custom_fields_map( $args = [], $param = null ) { $params = $this->get_custom_fields_shortcode_params( $args['data-id'], 'map' ); if ( empty( $params ) ) { $params = [ 'latitude' => 0, 'longitude' => 0, 'zoom' => 0, 'hidden' => '', ]; if ( isset( $args['data-placeholder'] ) && is_string( $args['data-placeholder'] ) && preg_match( '/(-?[0-9]+\.[0-9]+),(-?[0-9]+\.[0-9]+)/', $args['data-placeholder'], $match ) ) { $params = array_merge( $params, array( 'latitude' => $match[1], 'longitude' => $match[2], 'zoom' => isset( $args['zoom'] ) ? $args['zoom'] : 0, ) ); } else { $params['hidden'] = 'data-c-f-hidden=1 '; } } else { $params['zoom'] = isset( $args['zoom'] ) ? $args['zoom'] : 0; $params['hidden'] = ''; } return ''; } public function render_custom_fields_audio( $args = [], $param = null ) { $params = empty( $args['in_postlist'] ) ? $this->get_custom_fields_shortcode_params( $args['data-id'], 'audio' ) : $param; if ( empty( $params ) ) { $params = array_merge( $params, [ 'mime_type' => 'audio/mp3', 'id' => '0', 'width' => '100', 'height' => '100', 'title' => 'Placeholder', 'url' => '', 'name' => $args['data-id'], ] ); if ( isset( $args['data-placeholder'] ) ) { $params['url'] = wp_get_attachment_url( $args['data-placeholder'] ); } if ( ! is_editor_page_raw( true ) && ! isset( $args['data-placeholder'] ) ) { return ''; } } $params['extra'] = ''; $params['extra'] .= isset( $args[ 'loop' . '' ] ) && $args['loop'] === '1' ? 'loop="1"' : ''; $params['extra'] .= isset( $args['no_download'] ) && $args['no_download'] === '1' ? 'controlslist="nodownload" ' : ''; $params['extra'] .= isset( $args['autoplay'] ) && $args['autoplay'] === '1' ? 'data-autoplay="1" ' : ''; $params['extra'] .= empty( $args['in_postlist'] ) ? '' : 'data-post-list="1" '; return tcb_template( 'custom-fields-elements/audio.phtml', $params, true ); } private function get_video_params( $args, $params, $set_source = false ) { $params['value'] = empty( $params['value'] ) ? '' : $params['value']; $matches = $this->verify_video_url( $params['value'], true ); $args['data-type'] = $set_source ? $matches : $args['data-type']; switch ( $args['data-type'] ) { case 'youtube': $params['video_id'] = empty( $matches ) ? '' : $matches[1]; //we need something custom for the youtube video used as background from a custom field if ( ! empty( $args['data-is-video-background'] ) ) { $parsed_query = []; $args['data-query'] = 'autoplay=1&mute=1&loop=1&controls=0&playsinline=1'; parse_str( $args['data-query'], $parsed_query ); //we must make sure the playlist and the video id are the same (without a playlist the video won't loop thanks youtube) if ( empty( $parsed_query['amp;playlist'] ) ) { $parsed_query['playlist'] = $params['video_id']; } else { $parsed_query['amp;playlist'] = $params['video_id']; } $args['data-query'] = urldecode( http_build_query( $parsed_query, '', '&' ) ); } $domain = 'youtube'; if ( ! empty( $args['data-cookie'] ) && $args['data-cookie'] === '1' ) { $domain = 'youtube-nocookie'; } $params['embeded_url'] = 'https://www.' . $domain . '.com/embed/' . $params['video_id'] . '?' . $args['data-query']; break; case 'vimeo': if ( ! empty( $args['data-is-video-background'] ) ) { $args['data-query'] = '?loop=1&autoplay=1&background=1&muted=1&autopause=0'; } $params['video_hash'] = empty( $matches[5] ) ? '' : $matches[5]; $params['video_id'] = empty( $matches ) ? '' : $matches[4]; $params['embeded_url'] = 'https://player.vimeo.com/video/' . $params['video_id']; if ( $params['video_hash'] !== '' ) { $params['embeded_url'] = $params['embeded_url'] . '?h=' . $params['video_hash'] . '&' . substr( $args['data-query'], 1, strlen( $args['data-query'] ) - 1 ); } else { $params['embeded_url'] = $params['embeded_url'] . $args['data-query']; } break; case 'wistia': $params['video_id'] = empty( $matches ) ? '' : $matches[ count( $matches ) - 1 ]; $params['embeded_url'] = 'https://fast.wistia.net/embed/iframe/' . $params['video_id']; break; case 'vooplayer': /** * 3 was never working it should have been 4 in order to match the video id * changed to 5 since the links can be watch|publish so the regex match is changed */ $params['video_id'] = empty( $matches ) ? '' : $matches[5]; break; } return $params; } public function render_custom_fields_video( $args = [], $param = null ) { $params = empty( $args['in_postlist'] ) ? $this->get_custom_fields_shortcode_params( $args['data-id'], 'video' ) : $param; if ( ! empty( $params ) ) { if ( empty( $params['extra'] ) ) { $params['extra'] = ''; } $params['value'] = empty( $params['value'] ) ? $params['url'] : $params['value']; $video_provider = ( $this->verify_video_url( $params['value'] ) ); //Update the mime type for external cf videos isset( $video_provider['mime_type'] ) && $params['mime_type'] = $video_provider['mime_type']; $params['extra'] .= empty( $args['in_postlist'] ) ? '' : ' data-post-list="1" '; $params['extra'] .= empty( $args['data-query'] ) ? '' : $args['data-query']; } /** Set placeholder data for (empty links) and (videos with diff source in postlist)*/ if ( empty( $params ) || ( ! empty( $args['data-type'] ) && $video_provider['video_src'] !== $args['data-type'] && ! empty( $args['in_postlist'] ) ) ) { $params = array_merge( $params, [ 'mime_type' => 'video/mp4', 'id' => '0', 'title' => 'Placeholder', 'data-is-placeholder' => 1, //flag needed when we save the page and the background displayed is the placeholder 'url' => '', 'name' => $args['data-id'], ] ); $video_provider['video_src'] = 'external'; if ( isset( $args['data-placeholder'] ) ) { //Check if placeholder is of external type if ( is_numeric( $args['data-placeholder'] ) ) { $params['url'] = wp_get_attachment_url( $args['data-placeholder'] ); $params['extra'] = 'controls="controls"'; $args['data-query'] = ''; } else { $params['value'] = $args['data-placeholder']; $params = $this->get_video_params( $args, $params, true ); $video_provider = ( $this->verify_video_url( $params['value'] ) ); } } if ( ! is_editor_page_raw( true ) && ! isset( $args['data-placeholder'] ) ) { return ''; } } $template = empty( $args['data-is-video-background'] ) ? tcb_template( 'custom-fields-elements/video.phtml', $params, true ) : tcb_template( 'custom-fields-elements/video-background.phtml', $params, true ); $args['data-type'] = empty( $args['data-type'] ) ? 'external' : $video_provider['video_src']; switch ( $args['data-type'] ) { case 'youtube': case 'vimeo': $params = $this->get_video_params( $args, $params ); $template = empty( $args['data-is-video-background'] ) ? $this->get_video_template( 'tcb-responsive-video', $params, $args ) : $this->get_video_template( 'dynamic-source', $params, $args ); break; case 'wistia': $params = $this->get_video_params( $args, $params ); if ( empty( $args['data-is-video-background'] ) ) { $template = $this->get_video_template( 'tcb-responsive-video', $params, $args ); } else { $template = TCB_Utils::wrap_content( '' . '', 'div', '', 'wistia_embed dynamic-source wmode=transparent playButton=false autoPlay=1 controlsVisibleOnLoad=0 fullscreenButton=0 volume=0 wistia_async_' . $params['video_id'], array( 'data-id' => $params['video_id'], 'data-c-f-id' => $args['data-id'], 'style' => 'width: 100%;height: 100%', ) ); } break; case 'vooplayer': $params = $this->get_video_params( $args, $params ); $template = ''; break; case 'external': if ( ! empty( $args['data-is-video-background'] ) ) { if ( empty( $params['data-is-placeholder'] ) ) { $params['data-is-placeholder'] = 0; } $template = TCB_Utils::wrap_content( '', 'video', '', 'tcb-bg-video dynamic-source', array( 'playsinline' => '', 'autoplay' => '', 'muted' => '', 'loop' => '', 'data-is-placeholder' => $params['data-is-placeholder'], 'data-id' => $params['name'], 'data-c-f-id' => $params['name'], 'data-title' => $params['title'], ) ); } break; } return $template; } public function render_custom_fields_image( $args, $param ) { $params = empty( $args['in_postlist'] ) ? $this->get_custom_fields_shortcode_params( $args['data-id'], 'image' ) : $param; if ( empty( $params ) && ! isset( $args['data-placeholder'] ) ) { $params = array( 'alt' => 'Placeholder', 'id' => '0', 'width' => '100', 'height' => '100', 'title' => 'Placeholder', 'url' => tve_editor_url( 'editor/css/images/featured_image.png' ), 'name' => $args['data-id'], 'data-css' => isset( $args['data-css'] ) ? $args['data-css'] : '', ); if ( ! is_editor_page_raw( true ) ) { return ''; } $params['extra'] = empty( $args['in_postlist'] ) ? '' : 'data-post-list="1"'; $params['classes'] = empty( $args['data-classes'] ) ? 'tve_image wp-image-' . $params['id'] . '' : $args['data-classes']; $html = tcb_template( 'custom-fields-elements/image.phtml', $params, true ); } else { if ( empty( $params ) && isset( $args['data-placeholder'] ) ) { $params['id'] = $args['data-placeholder']; $params['alt'] = 'Placeholder'; $params['name'] = $args['data-id']; $params['title'] = 'Placeholder'; } $aux = array( 'class' => empty( $args['data-classes'] ) ? 'tve_image wp-image-' . $params['id'] . '' : $args['data-classes'], 'alt' => esc_attr( $params['alt'] ), 'data-c-f-id' => esc_attr( $params['name'] ), 'title' => esc_attr( $params['title'] ), ); if ( ! empty( $args['in_postlist'] ) ) { $aux['data-post-list'] = esc_attr( 1 ); } if ( ! empty( $args['data-css'] ) ) { $aux['data-css'] = esc_attr( $args['data-css'] ); } $html = wp_get_attachment_image( $params['id'], 'full', false, $aux ); } return $html; } public function render_custom_fields_background( $args, $param ) { if ( empty( $args['data-placeholder'] ) ) { $args['data-placeholder'] = '0'; } if ( empty( $args['in_postlist'] ) ) { $params = $this->get_custom_fields_shortcode_params( $args['data-id'], 'image' ); $dynamic_acf_key = 'dynamic_acf_page'; } else { $params = $param; $dynamic_acf_key = 'dynamic_acf_postlist'; } //if we have no cf image for a certain post if ( empty( $params ) ) { //if a placeholder is not set if ( $args['data-placeholder'] === '0' ) { if ( ! is_editor_page_raw( true ) ) { return ''; } $params['url'] = tve_editor_url( 'editor/css/images/featured_image.png' ); } else { $params['url'] = urldecode( $args['data-placeholder'] ); } } return add_query_arg( array( $dynamic_acf_key => 1, 'id' => $args['data-id'], 'fallback' => urldecode( $args['data-placeholder'] ), ), empty( $params['url'] ) ? '' : $params['url'] ); } public function render_custom_fields_countdown( $args = [], $param = null ) { $params = empty( $args['in_postlist'] ) ? $this->get_custom_fields_shortcode_params( $args['data-id'], 'countdown' ) : $param; $return = []; if ( ! empty( $args['in_postlist'] ) ) { $return[] = [ 'prop' => 'data-post-list', 'value' => '1' ]; } if ( empty( $params ) ) { $params = [ 'date' => '2020-01-01', 'hour' => '00', 'min' => '00', ]; if ( isset( $args['data-placeholder'] ) ) { $aux = explode( ':', $args['data-placeholder'] ); $params = [ 'date' => $aux[0], 'hour' => $aux[1], 'min' => $aux[2], ]; } if ( ! is_editor_page_raw( true ) && ! isset( $args['data-placeholder'] ) ) { $return[] = [ 'prop' => 'data-c-f-hidden', 'value' => 1 ]; return htmlspecialchars( wp_json_encode( $return ), ENT_QUOTES ); } } $return[] = [ 'prop' => 'data-date', 'value' => $params['date'] ]; $return[] = [ 'prop' => 'data-hour', 'value' => $params['hour'] ]; $return[] = [ 'prop' => 'data-min', 'value' => $params['min'] ]; return htmlspecialchars( wp_json_encode( $return ), ENT_QUOTES ); } public function render_custom_fields_number( $args = [], $param = null ) { $params = empty( $args['in_postlist'] ) ? $this->get_custom_fields_shortcode_params( $args['data-id'], 'number' ) : $param; $return = []; if ( ! empty( $args['in_postlist'] ) ) { $return[] = [ 'prop' => 'data-post-list', 'value' => '1' ]; } if ( empty( $params ) ) { $params = [ 'value' => '0', ]; if ( isset( $args['data-placeholder'] ) ) { $params['value'] = $args['data-placeholder']; } if ( ! is_editor_page_raw( true ) && ! isset( $args['data-placeholder'] ) ) { $return[] = [ 'prop' => 'data-c-f-hidden', 'value' => 1 ]; return htmlspecialchars( wp_json_encode( $return ), ENT_QUOTES ); } } if ( ! empty( $args['field-validation'] ) && ! empty( $args['data-attribute'] ) ) { switch ( $args['field-validation'] ) { case 'percentage': if ( ! is_numeric( $params['value'] ) || $params['value'] < 0 ) { $params['value'] = 0; } break; case 'rating': if ( ! is_numeric( $params['value'] ) || $params['value'] < 0 || fmod( $params['value'], 0.5 ) !== 0.0 ) { $params['value'] = 0; } break; default: break; } $return[] = [ 'prop' => $args['data-attribute'], 'value' => $params['value'] ]; } else { return htmlspecialchars( $params['value'], ENT_QUOTES ); } return htmlspecialchars( wp_json_encode( $return ), ENT_QUOTES ); } public function get_video_template( $class, $params, $args ) { return TCB_Utils::wrap_content( '', 'iframe', '', $class, array( 'data-code' => $params['video_id'], 'data-provider' => $args['data-type'], 'src' => $params['embeded_url'], 'data-src' => $params['embeded_url'], 'frameborder' => '0', 'allowfullscreen' => null, 'data-c-f-id' => $args['data-id'], $params['extra'] => null, ) ); } /** * Filter allowed shortcodes for tve_do_wp_shortcodes * * @param $shortcodes * * @return array */ public function allowed_shortcodes( $shortcodes ) { return array_merge( $shortcodes, [ static::GLOBAL_SHORTCODE_URL, static::GLOBAL_SHORTCODE_DATA, 'tcb_custom_field', 'tcb_dynamic_field', ], [] ); } /** * Renders the user shortcodes * * @param $attr * @param $content * @param $tag * * @return void|string */ public function render_user_shortcode( $attr, $content, $tag ) { if ( ! is_editor_page_raw( true ) ) { $current_user = wp_get_current_user(); if ( ! empty( $current_user->ID ) ) { $prop = $this->user_shortcodes[ $tag ]['property']; $return = $current_user->$prop; if ( ! empty( $attr['link_to_profile'] ) ) { $return = sprintf( '%s', get_edit_profile_url( $current_user->ID ), $return ); } } else { $return = $attr['text_not_logged']; } return $return; } } /** * Add global shortcodes to be used in dynamic links * * @param $links * * @return mixed */ public function global_links_shortcodes( $links ) { $global_links = array_values( $this->global_custom_links( get_the_ID() ) ); if ( ! empty( $global_links ) ) { $links['Custom Fields Global'] = [ 'links' => [ $global_links ], 'shortcode' => static::GLOBAL_SHORTCODE_URL, ]; } return $links; } /** * Global data related to the custom fields with links * * Gets all the custom fields for the current post or post with given id, selects only the fields that correspond to a http link * and returns an array of objects with the structure of a dynamic link * * @param null $post_id * * @return array */ public function global_custom_links( $post_id = null ) { $post_id = $post_id === null ? get_the_ID() : intval( $post_id ); $custom = get_post_custom( $post_id ); //Get all the keys that are not protected meta and are links $custom_keys = array_filter( ( array ) get_post_custom_keys( $post_id ), function ( $meta ) use ( $custom ) { return apply_filters( 'is_protected_meta', ! filter_var( $custom[ $meta ][0], FILTER_VALIDATE_URL ), $meta, null ) === false; } ); $items = []; foreach ( $custom_keys as $val ) { $items[ $val ] = [ 'name' => $val, 'label' => $val, 'url' => $custom[ $val ][0], 'show' => true, 'id' => $post_id . '::' . $val, ]; } if ( ! isset( $this->external_fields ) ) { $this->external_fields = $this->get_all_external_fields(); } if ( ! empty( $this->external_fields['link'] ) ) { foreach ( $this->external_fields['link'] as $key => $val ) { $items[ $key ] = array( 'name' => $val['name'], 'label' => $val['label'], 'url' => isset( $val['value']['url'] ) ? $val['value']['url'] : $val['value'], 'show' => true, 'id' => $post_id . '::' . $key, ); } } return $items; } /** * Global data related to the custom fields * * Gets all the custom fields for the current post or post with given id, selects only the fields that do not correspond to a http link * and returns an array of objects with the structure of an inline shortcode * * @param null $post_id * * @return array */ public function global_custom_metadata( $post_id = null ) { $post_id = $post_id === null ? get_the_ID() : intval( $post_id ); $custom = get_post_custom( $post_id ); //Get all the keys that are not protected meta and not links $custom_keys = array_filter( (array) get_post_custom_keys( $post_id ), function ( $meta ) use ( $custom ) { return apply_filters( 'is_protected_meta', filter_var( $custom[ $meta ][0], FILTER_VALIDATE_URL ), $meta, null ) === false; } ); $real_data = []; $value = []; $value_type = []; $labels = []; foreach ( $custom_keys as $val ) { $key = $post_id . '::' . $val; $real_data[ $key ] = $custom[ $val ][0]; //Value that will be displayed $value[ $key ] = $val; //Value appearing as option title $value_type[ $key ] = '0'; //Value type (text) $labels[ $val ] = static::get_label_for_key( $val, $post_id ); } if ( ! isset( $this->external_fields ) ) { $this->external_fields = $this->get_all_external_fields(); } if ( ! empty( $this->external_fields['text'] ) ) { foreach ( $this->external_fields['text'] as $k => $v ) { $key = $post_id . '::' . $k; $real_data[ $key ] = $v['value']; //Value that will be displayed $value[ $key ] = $v['label']; //Value appearing as option title $value_type[ $key ] = '0'; //Value type (text) $labels[ $k ] = $v['label']; } } return [ 'real_data' => $real_data, 'value' => $value, 'value_type' => $value_type, 'labels' => $labels, ]; } /** * Get the 'nice' display name for this custom field key. * Each CF plugin seems to have its own way of retrieving these * * Used also in our Theme Builder * * @param $key * @param $post_id * * @return string */ public static function get_label_for_key( $key, $post_id ) { $label = ''; $field_obj = static::get_post_acf_data( $key, $post_id ); if ( ! empty( $field_obj ) && ! empty( $field_obj['label'] ) ) { $label = $field_obj['label']; } return $label; } /** * Check if this post + key have ACF data. If it does, return it, else return an empty array * Used in TTB * * @param $key * @param $post_id * * @return array */ public static function get_post_acf_data( $key, $post_id ) { $field_obj = []; if ( function_exists( 'get_field_object' ) ) { $field_obj = get_field_object( $key, $post_id ); } return $field_obj; } /** * Add some inline shortcodes. * * @param $shortcodes * * @return array */ public function tcb_inline_shortcodes( $shortcodes ) { $custom_data_global = $this->global_custom_metadata(); $custom_shortcodes = array( 'Custom fields' => array( array( 'name' => __( 'Custom Fields Global', 'thrive-cb' ), 'value' => static::GLOBAL_SHORTCODE_DATA, 'option' => __( 'Custom Fields', 'thrive-cb' ), 'extra_param' => 'CFG', 'input' => [ 'id' => [ 'extra_options' => [], 'label' => 'Field', 'real_data' => $custom_data_global['real_data'], 'type' => 'select', 'value' => $custom_data_global['value'], 'value_type' => $custom_data_global['value_type'], 'labels' => $custom_data_global['labels'], ], ], ), array( 'name' => 'Custom Fields Postlist', 'value' => 'tcb_post_custom_field', 'option' => __( 'Custom Fields', 'thrive-cb' ), 'extra_param' => 'CFP', 'input' => [ 'id' => [ 'extra_options' => [], 'label' => 'Field', 'real_data' => [], 'type' => 'select', 'value' => [], 'value_type' => [], 'labels' => [], ], ], ), ), ); return array_merge_recursive( $shortcodes, $custom_shortcodes ); } /** * Replace the shortcode with its content * * @param $args * * @return mixed|string */ public function global_shortcode_url_link( $args ) { $data = ''; if ( ! empty( $args['id'] ) ) { $shortcode_data = $this->get_parsed_shortcode_data( $args['id'] ); $groups = $this->global_custom_links( (int) $shortcode_data['post_id'] ); if ( isset( $groups[ $shortcode_data['id'] ] ) ) { $data = $groups[ $shortcode_data['id'] ]['url']; } } return $data; } /** * Replace the shortcode with its content * * @param $args * * @return mixed|string */ public function global_shortcode_url_data( $args ) { $data = ''; if ( ! empty( $args['id'] ) ) { $shortcode_data = $this->get_parsed_shortcode_data( $args['id'] ); $full_id = $shortcode_data['post_id'] . '::' . $shortcode_data['id']; $groups = $this->global_custom_metadata( (int) $shortcode_data['post_id'] ); if ( isset( $groups['real_data'][ $full_id ] ) ) { $data = $groups['real_data'][ $full_id ]; } } return TVD_Global_Shortcodes::maybe_link_wrap( $data, $args ); } /** * Get the post ID and the shortcode ID from the string. If no post ID exists, use the current post ID. * * @param $data * * @return array */ private function get_parsed_shortcode_data( $data ) { if ( strpos( $data, '::' ) ) { $shortcode_data = explode( '::', $data ); $post_id = $shortcode_data[0]; $shortcode_id = $shortcode_data[1]; } else { /* in certain cases where we don't have an ID ( TTB ), get the current post ID */ $post_id = get_the_ID(); $shortcode_id = $data; } return [ 'post_id' => $post_id, 'id' => $shortcode_id, ]; } private function verify_video_url( $url, $get_regex = false ) { if ( is_string( $url ) ) { foreach ( $this->video_regex as $reg => $provider ) { if ( preg_match( $reg, $url, $aux ) ) { return $get_regex ? $aux : [ 'value' => $aux[0], 'video_src' => $provider, ]; } } if ( preg_match( '/.+\.(wmv|avi|mov|mpg|mp4|m4v|ogv|3gp|3g2|webm)$/', $url, $aux ) ) { //Exception for mov file types if ( $aux[1] == 'mov' ) { $aux[1] = 'mp4'; } return $get_regex ? $aux : [ 'value' => $url, 'video_src' => 'external', 'url' => $url, 'title' => 'External Video', 'id' => 0, 'mime_type' => 'video/' . $aux[1], ]; } } return false; } /** * Integration with Advanced Custom Fields * https://www.advancedcustomfields.com/ * * Returns an array of custom fields from the plugin * * @param $post_id * @param $field_types * * @return array */ private function get_acf_fields( $post_id = false, $field_types = [] ) { $advanced_custom_fields = get_field_objects( $post_id, false ); $fields = []; if ( ! is_array( $advanced_custom_fields ) ) { return $fields; } foreach ( $advanced_custom_fields as $field_key => $value ) { $attachment = acf_get_attachment( $value['value'] ); $formatted_value = get_field( $value['key'] ); $acf_key = static::ACF_PREFIX . $field_key; if ( $value['value'] === '' && 'true_false' !== $value['type'] ) { continue; } foreach ( $field_types as $k => $v ) { if ( ! in_array( $value['type'], $v ) ) { continue; } if ( ! isset( $fields[ $k ] ) ) { $fields[ $k ] = []; } if ( ! isset( $fields[ $k ][ $acf_key ] ) ) { $field = []; switch ( $k ) { case 'countdown': $field = [ 'value' => $formatted_value ]; $date = date_create_from_format( 'Y-m-d H:i:s', $value['value'] ); if ( $date !== false ) { $field['date'] = $date->format( 'Y-m-d' ); $field['hour'] = $date->format( 'H' ); $field['min'] = $date->format( 'i' ); } break; case 'link': $field = [ 'value' => $formatted_value ]; if ( in_array( $value['type'], [ 'file', 'image' ] ) ) { $field['value'] = $attachment === false ? '' : $attachment['url']; } else { $field['value'] = $value['type'] === 'page_link' ? get_permalink( $value['value'] ) : ( $value['type'] === 'link' ? $value['value']['url'] : $field['value'] ); } if ( ! filter_var( $field['value'], FILTER_VALIDATE_URL ) ) { if ( filter_var( $field['value'], FILTER_VALIDATE_EMAIL ) ) { $field['value'] = 'mailto:' . $field['value']; } else { $field = null; } } break; case 'video': case 'audio': case 'image': //TODO Filter types in case of video/audio (ex: .flv is not working) if ( ! empty( $attachment ) && $attachment !== false && $attachment['type'] === $k ) { $field = array_merge( $attachment, [ 'name' => $acf_key, 'mime' => $attachment['mime_type'], ] ); $field['video_src'] = 'external'; } else { $field = $k === 'video' ? $this->verify_video_url( $value['value'] ) : false; } break; case 'map': if ( is_string( $value['value'] ) && preg_match( '/(-?[0-9]+\.[0-9]+),(-?[0-9]+\.[0-9]+)/', $value['value'], $match ) ) { $field = [ 'latitude' => $match[1], 'longitude' => $match[2], ]; } break; default: $aux = get_field_object( $field_key ); if ( ! empty( $aux['display_format'] ) ) { $date_value = $aux['value']; /* if the locale is english, we can use the date formatting functions, otherwise they don't work properly */ if ( strpos( get_locale(), 'en' ) === 0 ) { $datetime = DateTime::createFromFormat( $aux['return_format'], $aux['value'] ); if ( $datetime !== false ) { /* not sure if this conversion is 100% needed or intended ( it converts return_format to display_format, but the descriptions in ACF are misleading ) */ $date_value = date_format( $datetime, $aux['display_format'] ); } } $field = [ 'value' => $date_value ]; } else if ( $value['type'] === 'checkbox' ) { //Convert from Checkbox array to string $field = array( 'value' => implode( ', ', $value['value'] ) ); } else { $value['value'] = preg_replace( '(\n)', '
', $value['value'] ); //replace \n from text/textarea $field = [ 'value' => $value['value'] ]; } break; } if ( ! empty( $field ) && is_array( $field ) ) { $acf_key = $this->replace_characters( $acf_key ); $fields[ $k ][ $acf_key ] = array_merge( [ 'label' => $value['label'], 'name' => $acf_key, 'type' => $value['type'], ], $field ); } } } } return $fields; } public function replace_characters( $str ) { foreach ( $this->pattern_replacement as $pattern => $replacement ) { $str = preg_replace( $pattern, $replacement, $str ); } return $str; } /** * Entry point for retrieving all custom fields * * @param array $fields * * @return array */ public function get_all_external_fields() { if ( ! empty( $this->external_fields ) ) { return $this->external_fields; } $this->external_fields = []; if ( tvd_has_external_fields_plugins() ) { $this->external_fields = array_merge_recursive( $this->get_acf_fields( false, $this->field_types ), $this->external_fields ); } return $this->external_fields; } /** * Fetches the fields that are available for postlist element for post with given post_id * * @param $post_id * * @return array */ public function get_all_external_postlist_fields( $post_id ) { $fields = []; if ( tvd_has_external_fields_plugins() ) { $fields = $this->get_acf_fields( $post_id, $this->postlist_field_types ); } return $fields; } } /** * Returns the instance of the Custom Fields Shortcode Class * * @return TCB_Custom_Fields_Shortcode */ function tcb_custom_fields_api() { return TCB_Custom_Fields_Shortcode::get_instance(); } add_action('init', 'tcb_custom_fields_api');