tag - load multiple image versions depending on the device type. */ class TCB_Logo { /* component name */ const COMPONENT = 'logo'; /* identifier used as a class name */ const IDENTIFIER = 'tcb-logo'; /* shortcode tag in which the logo HTML is wrapped */ const SHORTCODE_TAG = 'tcb_logo'; /* name of the option in the DB where the logo data is stored */ const OPTION_NAME = 'tcb_logo_data'; const DELETED_PLACEHOLDER_SRC = 'editor/css/images/logo_deleted_placeholder.png'; /* list of devices that can have their own logo images ( desktop, tablet, mobile were shortened like this in order to work with mediaAttr() in JS ) */ private static $all_devices = [ 'd', 't', 'm' ]; /** * TCB_Logo constructor. */ public function __construct() { $this->hooks(); } /** * Add actions and filters. */ private function hooks() { add_action( 'init', [ $this, 'init_shortcode' ] ); add_filter( 'tcb_main_frame_localize', [ $this, 'add_localize_params' ] ); add_filter( 'tcb_content_allowed_shortcodes', [ $this, 'tcb_content_allowed_shortcodes' ] ); } /** * Add data to the main frame localize parameters. * * @param $data * * @return mixed */ public function add_localize_params( $data ) { $logos = static::get_logos(); $active_logos = array_filter( $logos, function ( $logo ) { return (int) $logo['active'] === 1; } ); /* get the src for each attachment ID */ foreach ( $active_logos as $key => $logo ) { $logo_data = static::get_attachment_data( $logo['id'], $logo ); $active_logos[ $key ]['src'] = $logo_data['src']; $active_logos[ $key ]['width'] = $logo_data['width']; $active_logos[ $key ]['height'] = $logo_data['height']; $active_logos[ $key ]['data-alt'] = empty( $logo_data['data-alt'] ) ? '' : $logo_data['data-alt']; } $data['logo'] = array( 'routes' => array( 'base' => get_rest_url( get_current_blog_id(), 'tcb/v1/logo' ), 'rename_logo' => get_rest_url( get_current_blog_id(), 'tcb/v1/logo/rename_logo' ), ), /* only localize the active logos */ 'sources' => array_values( $active_logos ), 'deleted_placeholder' => tve_editor_url( static::DELETED_PLACEHOLDER_SRC ), 'is_ttb_active' => tve_dash_is_ttb_active(), 'is_ta_active' => tve_dash_is_plugin_active( 'thrive-apprentice' ), ); return $data; } /** * Render the element. Inside the editor, render a simplified version without responsive stuff. * On the frontend, render a tag for each logo chosen for a responsive device screen. * * @param array $attr * @param bool $render_fallback * * @return string */ public static function render_logo( $attr = [], $render_fallback = false ) { /* if the desktop logo ID is not set, use id = 0 as default and set it in the attr */ if ( ! isset( $attr['data-id-d'] ) ) { $attr['data-id-d'] = 0; } $desktop_id = (int) $attr['data-id-d']; $logos = static::get_logos(); /* set the desktop source as a fallback; get only the src here, since this is an all-browser compatible version */ $fallback_data = static::get_attachment_data( $desktop_id, isset( $logos[ $desktop_id ] ) ? $logos[ $desktop_id ] : null ); /* If we do not have alt in attr, we read it from fallback data */ if ( empty( $attr['data-alt'] ) ) { $attr['data-alt'] = empty( $fallback_data['data-alt'] ) ? '' : $fallback_data['data-alt']; } $img_attr = array( 'src' => $fallback_data['src'], 'height' => $fallback_data['height'], 'width' => $fallback_data['width'], 'alt' => $attr['data-alt'], 'style' => ! empty( $attr['data-img-style'] ) ? $attr['data-img-style'] : '', ); /** * Handle logo image loading */ if ( isset( $attr['loading'] ) ) { $img_attr['loading'] = $attr['loading']; unset( $attr['loading'] ); } else { $img_attr['class'] = 'tve-not-lazy-loaded'; } /* GIFs aren't compatible with srcset, so we use the fallback version */ if ( ! empty( $img_attr['src'] ) && substr( $img_attr['src'], - 4 ) === '.gif' ) { $render_fallback = true; } /* in the editor or when doing ajax ( when the logo is in a symbol ) or when doing rest ( when you add new headers/footers and start from cloud templates ) or when we set a flag, return the desktop src only */ if ( TCB_Utils::in_editor_render() || wp_doing_ajax() || TCB_Utils::is_rest() || $render_fallback ) { $content = TCB_Utils::wrap_content( '', 'img', '', '', $img_attr ); } else { /* if the fallback data is empty and we're outside the editor, return an empty string ( this case happens when the logo was deleted ) */ if ( empty( $fallback_data['src'] ) ) { $content = ''; } else { $content = static::get_picture_element( $attr, $img_attr ); } } $logo_url = apply_filters( 'tcb_logo_site_url', '' ); /* We have to process the shortcode here because we cannot send it as a param inside another shortcode ( logo ) */ if ( ! empty( $attr['data-dynamic-link'] ) ) { $shortcode = "{$attr['data-dynamic-link']} id={$attr['data-shortcode-id']}"; if ( ! empty( $attr['data-custom-redirect'] ) ) { $shortcode .= " logout-redirect={$attr['data-custom-redirect']}"; } $attr['href'] = do_shortcode( "[{$shortcode}]" ); } /* embed the img in a link instead of wrapping it in a div (if an url exists) */ if ( empty( $attr['href'] ) ) { if ( empty( $logo_url ) || ! empty( $attr['data-remove-href'] ) ) { unset( $attr['href'] ); } else { $attr['href'] = $logo_url; } } $href = isset( $attr['href'] ) ? $attr['href'] : ''; /** * Allows filtering the final value of the `href` logo attribute * * @param string $href current url * @param array $attr array of shortcode attributes */ $href = apply_filters( 'tcb_logo_url', $href, $attr ); if ( ! empty( $href ) ) { $attr['href'] = $href; } return TCB_Utils::wrap_content( $content, 'a', '', static::get_classes( $attr ), static::get_attr( $attr ) ); } /** * Get the picture element containing the sources. ( For info on how this works, see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture ) * * @param array $attr * @param array $img_attr * * @return string */ public static function get_picture_element( $attr, $img_attr ) { $picture_content = ''; foreach ( static::$all_devices as $device ) { if ( isset( $attr[ 'data-id-' . $device ] ) ) { $id = (int) $attr[ 'data-id-' . $device ]; $media_attr = [ 'srcset' => static::get_srcset( $id ), ]; /* add media rules to restrict where each source is displayed */ switch ( $device ) { case 'd': $media_attr['media'] = '(min-width:' . TCB_DESKTOP_THRESHOLD . 'px)'; break; case 't': $media_attr['media'] = '(min-width:' . TCB_TABLET_THRESHOLD . 'px) and (max-width:' . TCB_DESKTOP_THRESHOLD . 'px)'; break; case 'm': $media_attr['media'] = '(max-width:' . ( TCB_TABLET_THRESHOLD - 1 ) . 'px)'; break; default: break; } $picture_content .= TCB_Utils::wrap_content( '', 'source', '', '', $media_attr ); } } /* add the fallback img */ $picture_content .= TCB_Utils::wrap_content( '', 'img', '', '', $img_attr ); /* wrap it in the tag and return */ return TCB_Utils::wrap_content( $picture_content, 'picture' ); } /** * Get all the attachment data for this logo ID. If the logo ID is not found or we don't have an attachment ID, return placeholder data instead. * * @param int $id * @param array $logo * * @return array */ public static function get_attachment_data( $id, $logo = null ) { $attachment_id = static::get_attachment_id( $id ); if ( empty( $attachment_id ) ) { /* get the placeholder for this id ( it can differ depending on the ID : 0 and 1 have their own placeholder ) */ $data = static::get_placeholder_data( $id, $logo ); } else { $attachment_data = wp_get_attachment_image_src( $attachment_id, 'full' ); if ( empty( $attachment_data[0] ) ) { $data = static::get_placeholder_data( $id ); } else { $data = array( 'src' => $attachment_data[0], 'width' => $attachment_data[1], 'height' => $attachment_data[2], 'data-alt' => trim( strip_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) ), ); } } return $data; } /** * For the given logo ID, look for the attachment ID and return it. If it's not found, return null. * * @param int $id * * @return mixed|null */ public static function get_attachment_id( $id ) { $logos = static::get_logos(); $index = - 1; /* look for the logo ID in the array of logo data */ foreach ( $logos as $key => $logo_data ) { if ( $id === $logo_data['id'] ) { $index = $key; break; } } $attachment_id = null; /* if the logo ID was not found, render the placeholder */ if ( $index !== - 1 ) { /* if we found the key for the logo ID, get the src */ $logo_data = $logos[ $index ]; /* if the logo is active or it's light or dark, start looking for the image ID */ if ( $logo_data['active'] || $id === 0 || $id === 1 ) { if ( ! empty( $logo_data['attachment_id'] ) ) { $attachment_id = $logo_data['attachment_id']; } } } return $attachment_id; } /** * Get the image source for this logo ID. It can be a placeholder too * * @param $id * * @return mixed */ public static function get_src( $id ) { $attachment_data = static::get_attachment_data( $id ); return $attachment_data['src']; } /** * Get the srcset attribute for this logo ID. If empty, return the normal source instead. * * @param $id * * @return bool|mixed|string */ public static function get_srcset( $id ) { $attachment_id = static::get_attachment_id( $id ); if ( ! empty( $attachment_id ) ) { /* get the full srcset so the browser can pick the best image size for the current screen */ $srcset = wp_get_attachment_image_srcset( $attachment_id ); } if ( empty( $srcset ) ) { /* if the srcset is empty (this happens for SVGs and for small images) or we don't want the srcset, get the src instead */ $srcset = static::get_src( $id ); } return $srcset; } /** * Return the placeholder data according to the ID. * For id = 0 or 1, return the light/dark placeholder, for any other ID, return a 'logo has been deleted' placeholder. * * @param int $id * @param array $logo * * @return array */ public static function get_placeholder_data( $id, $logo = null ) { $data = [ 'height' => '', 'width' => '', ]; $is_default_logo = ! empty( $logo ) && isset( $logo['scope'] ) && $logo['scope'] === 'tva'; if ( $id === 0 || ( $is_default_logo && $logo['name'] === 'Dark' ) ) { $data['src'] = tve_editor_url( 'editor/css/images/logo_placeholder_dark.svg' ); } elseif ( $id === 1 || ( $is_default_logo && $logo['name'] === 'Light' ) ) { $data['src'] = tve_editor_url( 'editor/css/images/logo_placeholder_light.svg' ); } else { /* If we're in the editor, render a image. On the frontend, leave it blank. */ $data['src'] = TCB_Utils::in_editor_render() ? tve_editor_url( static::DELETED_PLACEHOLDER_SRC ) : ''; } $data['is_placeholder'] = 1; return $data; } /** * Get the src directly ( called from TTB ) * * @param int $id * * @return mixed */ public static function get_placeholder_src( $id = 0 ) { $data = static::get_placeholder_data( $id ); return $data['src']; } /** * Add the two initial logos and their placeholders ( these exist without having to be added manually and cannot be deleted ). * * @return array */ public static function initialize_default_logos() { $logos = [ 0 => [ 'id' => 0, 'attachment_id' => '', 'active' => 1, 'default' => 1, 'name' => 'Dark', ], 1 => [ 'id' => 1, 'attachment_id' => '', 'active' => 1, 'default' => 1, 'name' => 'Light', ], ]; /* update inside the DB */ update_option( static::OPTION_NAME, $logos ); return $logos; } /** * Get the logo data. ( get_option() is cached, so it's ok to call this lots of times ). * * @param bool $apply_filter * * @return mixed|void */ public static function get_logos( $apply_filter = true ) { $logos = get_option( static::OPTION_NAME ); /* if the option is empty, then we have to initialize the logo array with the default values */ if ( empty( $logos ) ) { /* initialize the default logos */ $logos = static::initialize_default_logos(); } if ( $apply_filter ) { /** * Allow other plugins to alter the logo list. * Used in Thrive Apprentice plugin to modify the default light and dark logo when the system edits an apprentice page */ $logos = apply_filters( 'tcb_get_logos', $logos ); } return $logos; } /** * @param $attr * * @return string */ private static function get_classes( $attr ) { $class = [ static::IDENTIFIER, THRIVE_WRAPPER_CLASS ]; /* set responsive/animation classes, if they are present */ if ( ! empty( $attr['class'] ) ) { $class[] = $attr['class']; } return implode( ' ', $class ); } /** * @param $attr * * @return array */ private static function get_attr( $attr ) { /* if we're not in the editor or not doing ajax ( for symbols ), remove the logo ID dataset */ if ( ! TCB_Utils::in_editor_render() && ! wp_doing_ajax() ) { foreach ( static::$all_devices as $device ) { unset( $attr[ 'data-id-' . $device ] ); } } /* we don't need to save this since it's stored in the image */ unset( $attr['data-alt'] ); return $attr; } /** * Add the logo shortcode. */ public function init_shortcode() { add_shortcode( static::SHORTCODE_TAG, function ( $attr, $content, $tag ) { /** * Ability to modify logo attributes before render. * * Used in thrive apprentice to modify the image URL for apprentice pages * * @param array $attributes */ $attr = apply_filters( 'tcb_logo_attributes', TCB_Post_List_Shortcodes::parse_attr( $attr, $tag ) ); return TCB_Logo::render_logo( $attr ); } ); } /** * We need to add our shortcodes to this array in order for them to be processed in the editor. * If we're on the frontend, we don't have to do this. * * @param $shortcodes * * @return array */ public function tcb_content_allowed_shortcodes( $shortcodes ) { if ( is_editor_page_raw( true ) ) { $shortcodes = array_merge( $shortcodes, [ static::SHORTCODE_TAG ] ); } return $shortcodes; } /** * Register REST Routes for the Logo. */ public static function rest_api_init() { require_once TVE_TCB_ROOT_PATH . 'inc/classes/logo/class-tcb-logo-rest.php'; } } new TCB_Logo();