lic static function get_conditionals() { return [ Front_End_Conditional::class ]; } /** * Front_End_Integration constructor. * * @codeCoverageIgnore It sets dependencies. * * @param Meta_Tags_Context_Memoizer $context_memoizer The meta tags context memoizer. * @param ContainerInterface $service_container The DI container. * @param Options_Helper $options The options helper. * @param Helpers_Surface $helpers The helpers surface. * @param WPSEO_Replace_Vars $replace_vars The replace vars helper. * @param Indexable_Repository $indexable_repository The indexable repository. * @param Permalink_Helper $permalink_helper The permalink helper. */ public function __construct( Meta_Tags_Context_Memoizer $context_memoizer, ContainerInterface $service_container, Options_Helper $options, Helpers_Surface $helpers, WPSEO_Replace_Vars $replace_vars, Indexable_Repository $indexable_repository, Permalink_Helper $permalink_helper ) { $this->container = $service_container; $this->context_memoizer = $context_memoizer; $this->options = $options; $this->helpers = $helpers; $this->replace_vars = $replace_vars; $this->indexable_repository = $indexable_repository; $this->permalink_helper = $permalink_helper; } /** * Registers the appropriate hooks to show the SEO metadata on the frontend. * * Removes some actions to remove metadata that WordPress shows on the frontend, * to avoid duplicate and/or mismatched metadata. * * @return void */ public function register_hooks() { \add_filter( 'render_block', [ $this, 'query_loop_next_prev' ], 1, 2 ); \add_action( 'wp_head', [ $this, 'call_wpseo_head' ], 1 ); // Filter the title for compatibility with other plugins and themes. \add_filter( 'wp_title', [ $this, 'filter_title' ], 15 ); // Filter the title for compatibility with block-based themes. \add_filter( 'pre_get_document_title', [ $this, 'filter_title' ], 15 ); // Removes our robots presenter from the list when wp_robots is handling this. \add_filter( 'wpseo_frontend_presenter_classes', [ $this, 'filter_robots_presenter' ] ); \add_action( 'wpseo_head', [ $this, 'present_head' ], -9999 ); \add_action( 'wpseo_head', [ $this, 'update_outdated_permalink' ], -10_000 ); \remove_action( 'wp_head', 'rel_canonical' ); \remove_action( 'wp_head', 'index_rel_link' ); \remove_action( 'wp_head', 'start_post_rel_link' ); \remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head' ); \remove_action( 'wp_head', 'noindex', 1 ); \remove_action( 'wp_head', '_wp_render_title_tag', 1 ); \remove_action( 'wp_head', '_block_template_render_title_tag', 1 ); \remove_action( 'wp_head', 'gutenberg_render_title_tag', 1 ); } /** * Filters the title, mainly used for compatibility reasons. * * @return string */ public function filter_title() { $context = $this->context_memoizer->for_current_page(); $title_presenter = new Title_Presenter(); /** This filter is documented in src/integrations/front-end-integration.php */ $title_presenter->presentation = \apply_filters( 'wpseo_frontend_presentation', $context->presentation, $context ); $title_presenter->replace_vars = $this->replace_vars; $title_presenter->helpers = $this->helpers; \remove_filter( 'pre_get_document_title', [ $this, 'filter_title' ], 15 ); $title = \esc_html( $title_presenter->get() ); \add_filter( 'pre_get_document_title', [ $this, 'filter_title' ], 15 ); return $title; } /** * Checks if the current entity has a permalink that has a mismatch * with the permalink stored in its indexable. If they differ, purges the indexable's * permalink so it will be recalculated in the same request. * * @return void */ public function update_outdated_permalink() { $dynamic_permalinks_conditional = new Dynamic_Product_Permalinks_Conditional(); if ( ! $dynamic_permalinks_conditional->is_met() ) { return; } $woocommerce_version_conditional = new WooCommerce_Version_Conditional(); if ( ! $woocommerce_version_conditional->is_met() ) { return; } $context = $this->context_memoizer->for_current_page(); // We're adding this fix only for products because of the 10.5 Woo release. We might expand this for all cases in the future. if ( $context->indexable->object_sub_type !== 'product' ) { return; } $current_permalink = $this->permalink_helper->get_permalink_for_post( $context->indexable->object_sub_type, $context->indexable->object_id ); $indexable_permalink = $context->indexable->permalink; // Only purge if the permalinks differ. if ( $current_permalink !== $indexable_permalink ) { $this->indexable_repository->reset_permalink( $context->indexable->object_type, $context->indexable->object_sub_type, $context->indexable->object_id, ); // Clear the memoizer caches so present_head() sees the updated indexable. $this->context_memoizer->clear_for_current_page(); $this->context_memoizer->clear( $context->indexable ); } } /** * Filters the next and prev links in the query loop block. * * @param string $html The HTML output. * @param array $block The block. * @return string The filtered HTML output. */ public function query_loop_next_prev( $html, $block ) { if ( $block['blockName'] === 'core/query' ) { // Check that the query does not inherit the main query. if ( isset( $block['attrs']['query']['inherit'] ) && ! $block['attrs']['query']['inherit'] ) { \add_filter( 'wpseo_adjacent_rel_url', [ $this, 'adjacent_rel_url' ], 1, 3 ); } } if ( $block['blockName'] === 'core/query-pagination-next' ) { $this->next = $html; } if ( $block['blockName'] === 'core/query-pagination-previous' ) { $this->prev = $html; } return $html; } /** * Returns correct adjacent pages when Query loop block does not inherit query from template. * Prioritizes existing prev and next links. * Includes a safety check for full urls though it is not expected in the query pagination block. * * @param string $link The current link. * @param string $rel Link relationship, prev or next. * @param Indexable_Presentation|null $presentation The indexable presentation. * * @return string The correct link. */ public function adjacent_rel_url( $link, $rel, $presentation = null ) { // Prioritize existing prev and next links. if ( $link ) { return $link; } // Safety check for rel value. if ( $rel !== 'next' && $rel !== 'prev' ) { return $link; } // Check $this->next or $this->prev for existing links. if ( $this->$rel === null ) { return $link; } $processor = new WP_HTML_Tag_Processor( $this->$rel ); if ( ! $processor->next_tag( [ 'tag_name' => 'a' ] ) ) { return $link; } $href = $processor->get_attribute( 'href' ); if ( ! $href ) { return $link; } // Safety check for full url, not expected. if ( \strpos( $href, 'http' ) === 0 ) { return $href; } // Check if $href is relative and append last part of the url to permalink. if ( \strpos( $href, '/' ) === 0 ) { $href_parts = \explode( '/', $href ); return $presentation->permalink . \end( $href_parts ); } return $link; } /** * Filters our robots presenter, but only when wp_robots is attached to the wp_head action. * * @param array $presenters The presenters for current page. * * @return array The filtered presenters. */ public function filter_robots_presenter( $presenters ) { if ( ! \function_exists( 'wp_robots' ) ) { return $presenters; } if ( ! \has_action( 'wp_head', 'wp_robots' ) ) { return $presenters; } if ( \wp_is_serving_rest_request() ) { return $presenters; } return \array_diff( $presenters, [ 'Yoast\\WP\\SEO\\Presenters\\Robots_Presenter' ] ); } /** * Presents the head in the front-end. Resets wp_query if it's not the main query. * * @codeCoverageIgnore It just calls a WordPress function. * * @return void */ public function call_wpseo_head() { global $wp_query; $old_wp_query = $wp_query; // phpcs:ignore WordPress.WP.DiscouragedFunctions.wp_reset_query_wp_reset_query -- Reason: The recommended function, wp_reset_postdata, doesn't reset wp_query. \wp_reset_query(); \do_action( 'wpseo_head' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Reason: we have to restore the query. $GLOBALS['wp_query'] = $old_wp_query; } /** * Echoes all applicable presenters for a page. * * @return void */ public function present_head() { $context = $this->context_memoizer->for_current_page(); $presenters = $this->get_presenters( $context->page_type, $context ); /** * Filter 'wpseo_frontend_presentation' - Allow filtering the presentation used to output our meta values. * * @param Indexable_Presentation $presentation The indexable presentation. * @param Meta_Tags_Context $context The meta tags context for the current page. */ $presentation = \apply_filters( 'wpseo_frontend_presentation', $context->presentation, $context ); echo \PHP_EOL; foreach ( $presenters as $presenter ) { $presenter->presentation = $presentation; $presenter->helpers = $this->helpers; $presenter->replace_vars = $this->replace_vars; $output = $presenter->present(); if ( ! empty( $output ) ) { // phpcs:ignore WordPress.Security.EscapeOutput -- Presenters are responsible for correctly escaping their output. echo "\t" . $output . \PHP_EOL; } } echo \PHP_EOL . \PHP_EOL; } /** * Returns all presenters for this page. * * @param string $page_type The page type. * @param Meta_Tags_Context|null $context The meta tags context for the current page. * * @return Abstract_Indexable_Presenter[] The presenters. */ public function get_presenters( $page_type, $context = null ) { $context ??= $this->context_memoizer->for_current_page(); $needed_presenters = $this->get_needed_presenters( $page_type ); $callback = static function ( $presenter ) { if ( ! \class_exists( $presenter ) ) { return null; } return new $presenter(); }; $presenters = \array_filter( \array_map( $callback, $needed_presenters ) ); /** * Filter 'wpseo_frontend_presenters' - Allow filtering the presenter instances in or out of the request. * * @param Abstract_Indexable_Presenter[] $presenters List of presenter instances. * @param Meta_Tags_Context $context The meta tags context for the current page. */ $presenter_instances = \apply_filters( 'wpseo_frontend_presenters', $presenters, $context ); if ( ! \is_array( $presenter_instances ) ) { $presenter_instances = $presenters; } $is_presenter_callback = static function ( $presenter_instance ) { return $presenter_instance instanceof Abstract_Indexable_Presenter; }; $presenter_instances = \array_filter( $presenter_instances, $is_presenter_callback ); return \array_merge( [ new Marker_Open_Presenter() ], $presenter_instances, [ new Marker_Close_Presenter() ], ); } /** * Generate the array of presenters we need for the current request. * * @param string $page_type The page type we're retrieving presenters for. * * @return string[] The presenters. */ private function get_needed_presenters( $page_type ) { $presenters = $this->get_presenters_for_page_type( $page_type ); $presenters = $this->maybe_remove_title_presenter( $presenters ); $callback = static function ( $presenter ) { return "Yoast\WP\SEO\Presenters\\{$presenter}_Presenter"; }; $presenters = \array_map( $callback, $presenters ); /** * Filter 'wpseo_frontend_presenter_classes' - Allow filtering presenters in or out of the request. * * @param array $presenters List of presenters. * @param string $page_type The current page type. */ $presenters = \apply_filters( 'wpseo_frontend_presenter_classes', $presenters, $page_type ); return $presenters; } /** * Filters the presenters based on the page type. * * @param string $page_type The page type. * * @return string[] The presenters. */ private function get_presenters_for_page_type( $page_type ) { if ( $page_type === 'Error_Page' ) { $presenters = $this->base_presenters; if ( $this->options->get( 'opengraph' ) === true ) { $presenters = \array_merge( $presenters, $this->open_graph_error_presenters ); } return \array_merge( $presenters, $this->closing_presenters ); } $presenters = $this->get_all_presenters(); if ( \in_array( $page_type, [ 'Static_Home_Page', 'Home_Page' ], true ) ) { $presenters = \array_merge( $presenters, $this->webmaster_verification_presenters ); } // Filter out the presenters only needed for singular pages on non-singular pages. if ( ! \in_array( $page_type, [ 'Post_Type', 'Static_Home_Page' ], true ) ) { $presenters = \array_diff( $presenters, $this->singular_presenters ); } // Filter out `twitter:data` presenters for static home pages. if ( $page_type === 'Static_Home_Page' ) { $presenters = \array_diff( $presenters, $this->slack_presenters ); } return $presenters; } /** * Returns a list of all available presenters based on settings. * * @return string[] The presenters. */ private function get_all_presenters() { $presenters = \array_merge( $this->base_presenters, $this->indexing_directive_presenters ); if ( $this->options->get( 'opengraph' ) === true ) { $presenters = \array_merge( $presenters, $this->open_graph_presenters ); } if ( $this->options->get( 'twitter' ) === true && \apply_filters( 'wpseo_output_twitter_card', true ) !== false ) { $presenters = \array_merge( $presenters, $this->twitter_card_presenters ); } if ( $this->options->get( 'enable_enhanced_slack_sharing' ) === true && \apply_filters( 'wpseo_output_enhanced_slack_data', true ) !== false ) { $presenters = \array_merge( $presenters, $this->slack_presenters ); } return \array_merge( $presenters, $this->closing_presenters ); } /** * Whether the title presenter should be removed. * * @return bool True when the title presenter should be removed, false otherwise. */ public function should_title_presenter_be_removed() { return ! \get_theme_support( 'title-tag' ) && ! $this->options->get( 'forcerewritetitle', false ); } /** * Checks if the Title presenter needs to be removed. * * @param string[] $presenters The presenters. * * @return string[] The presenters. */ private function maybe_remove_title_presenter( $presenters ) { // Do not remove the title if we're on a REST request. if ( \wp_is_serving_rest_request() ) { return $presenters; } // Remove the title presenter if the theme is hardcoded to output a title tag so we don't have two title tags. if ( $this->should_title_presenter_be_removed() ) { $presenters = \array_diff( $presenters, [ 'Title' ] ); } return $presenters; } } lic static function get_conditionals() { return [ Front_End_Conditional::class ]; } /** * Front_End_Integration constructor. * * @codeCoverageIgnore It sets dependencies. * * @param Meta_Tags_Context_Memoizer $context_memoizer The meta tags context memoizer. * @param ContainerInterface $service_container The DI container. * @param Options_Helper $options The options helper. * @param Helpers_Surface $helpers The helpers surface. * @param WPSEO_Replace_Vars $replace_vars The replace vars helper. * @param Indexable_Repository $indexable_repository The indexable repository. * @param Permalink_Helper $permalink_helper The permalink helper. */ public function __construct( Meta_Tags_Context_Memoizer $context_memoizer, ContainerInterface $service_container, Options_Helper $options, Helpers_Surface $helpers, WPSEO_Replace_Vars $replace_vars, Indexable_Repository $indexable_repository, Permalink_Helper $permalink_helper ) { $this->container = $service_container; $this->context_memoizer = $context_memoizer; $this->options = $options; $this->helpers = $helpers; $this->replace_vars = $replace_vars; $this->indexable_repository = $indexable_repository; $this->permalink_helper = $permalink_helper; } /** * Registers the appropriate hooks to show the SEO metadata on the frontend. * * Removes some actions to remove metadata that WordPress shows on the frontend, * to avoid duplicate and/or mismatched metadata. * * @return void */ public function register_hooks() { \add_filter( 'render_block', [ $this, 'query_loop_next_prev' ], 1, 2 ); \add_action( 'wp_head', [ $this, 'call_wpseo_head' ], 1 ); // Filter the title for compatibility with other plugins and themes. \add_filter( 'wp_title', [ $this, 'filter_title' ], 15 ); // Filter the title for compatibility with block-based themes. \add_filter( 'pre_get_document_title', [ $this, 'filter_title' ], 15 ); // Removes our robots presenter from the list when wp_robots is handling this. \add_filter( 'wpseo_frontend_presenter_classes', [ $this, 'filter_robots_presenter' ] ); \add_action( 'wpseo_head', [ $this, 'present_head' ], -9999 ); \add_action( 'wpseo_head', [ $this, 'update_outdated_permalink' ], -10_000 ); \remove_action( 'wp_head', 'rel_canonical' ); \remove_action( 'wp_head', 'index_rel_link' ); \remove_action( 'wp_head', 'start_post_rel_link' ); \remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head' ); \remove_action( 'wp_head', 'noindex', 1 ); \remove_action( 'wp_head', '_wp_render_title_tag', 1 ); \remove_action( 'wp_head', '_block_template_render_title_tag', 1 ); \remove_action( 'wp_head', 'gutenberg_render_title_tag', 1 ); } /** * Filters the title, mainly used for compatibility reasons. * * @return string */ public function filter_title() { $context = $this->context_memoizer->for_current_page(); $title_presenter = new Title_Presenter(); /** This filter is documented in src/integrations/front-end-integration.php */ $title_presenter->presentation = \apply_filters( 'wpseo_frontend_presentation', $context->presentation, $context ); $title_presenter->replace_vars = $this->replace_vars; $title_presenter->helpers = $this->helpers; \remove_filter( 'pre_get_document_title', [ $this, 'filter_title' ], 15 ); $title = \esc_html( $title_presenter->get() ); \add_filter( 'pre_get_document_title', [ $this, 'filter_title' ], 15 ); return $title; } /** * Checks if the current entity has a permalink that has a mismatch * with the permalink stored in its indexable. If they differ, purges the indexable's * permalink so it will be recalculated in the same request. * * @return void */ public function update_outdated_permalink() { $dynamic_permalinks_conditional = new Dynamic_Product_Permalinks_Conditional(); if ( ! $dynamic_permalinks_conditional->is_met() ) { return; } $woocommerce_version_conditional = new WooCommerce_Version_Conditional(); if ( ! $woocommerce_version_conditional->is_met() ) { return; } $context = $this->context_memoizer->for_current_page(); // We're adding this fix only for products because of the 10.5 Woo release. We might expand this for all cases in the future. if ( $context->indexable->object_sub_type !== 'product' ) { return; } $current_permalink = $this->permalink_helper->get_permalink_for_post( $context->indexable->object_sub_type, $context->indexable->object_id ); $indexable_permalink = $context->indexable->permalink; // Only purge if the permalinks differ. if ( $current_permalink !== $indexable_permalink ) { $this->indexable_repository->reset_permalink( $context->indexable->object_type, $context->indexable->object_sub_type, $context->indexable->object_id, ); // Clear the memoizer caches so present_head() sees the updated indexable. $this->context_memoizer->clear_for_current_page(); $this->context_memoizer->clear( $context->indexable ); } } /** * Filters the next and prev links in the query loop block. * * @param string $html The HTML output. * @param array $block The block. * @return string The filtered HTML output. */ public function query_loop_next_prev( $html, $block ) { if ( $block['blockName'] === 'core/query' ) { // Check that the query does not inherit the main query. if ( isset( $block['attrs']['query']['inherit'] ) && ! $block['attrs']['query']['inherit'] ) { \add_filter( 'wpseo_adjacent_rel_url', [ $this, 'adjacent_rel_url' ], 1, 3 ); } } if ( $block['blockName'] === 'core/query-pagination-next' ) { $this->next = $html; } if ( $block['blockName'] === 'core/query-pagination-previous' ) { $this->prev = $html; } return $html; } /** * Returns correct adjacent pages when Query loop block does not inherit query from template. * Prioritizes existing prev and next links. * Includes a safety check for full urls though it is not expected in the query pagination block. * * @param string $link The current link. * @param string $rel Link relationship, prev or next. * @param Indexable_Presentation|null $presentation The indexable presentation. * * @return string The correct link. */ public function adjacent_rel_url( $link, $rel, $presentation = null ) { // Prioritize existing prev and next links. if ( $link ) { return $link; } // Safety check for rel value. if ( $rel !== 'next' && $rel !== 'prev' ) { return $link; } // Check $this->next or $this->prev for existing links. if ( $this->$rel === null ) { return $link; } $processor = new WP_HTML_Tag_Processor( $this->$rel ); if ( ! $processor->next_tag( [ 'tag_name' => 'a' ] ) ) { return $link; } $href = $processor->get_attribute( 'href' ); if ( ! $href ) { return $link; } // Safety check for full url, not expected. if ( \strpos( $href, 'http' ) === 0 ) { return $href; } // Check if $href is relative and append last part of the url to permalink. if ( \strpos( $href, '/' ) === 0 ) { $href_parts = \explode( '/', $href ); return $presentation->permalink . \end( $href_parts ); } return $link; } /** * Filters our robots presenter, but only when wp_robots is attached to the wp_head action. * * @param array $presenters The presenters for current page. * * @return array The filtered presenters. */ public function filter_robots_presenter( $presenters ) { if ( ! \function_exists( 'wp_robots' ) ) { return $presenters; } if ( ! \has_action( 'wp_head', 'wp_robots' ) ) { return $presenters; } if ( \wp_is_serving_rest_request() ) { return $presenters; } return \array_diff( $presenters, [ 'Yoast\\WP\\SEO\\Presenters\\Robots_Presenter' ] ); } /** * Presents the head in the front-end. Resets wp_query if it's not the main query. * * @codeCoverageIgnore It just calls a WordPress function. * * @return void */ public function call_wpseo_head() { global $wp_query; $old_wp_query = $wp_query; // phpcs:ignore WordPress.WP.DiscouragedFunctions.wp_reset_query_wp_reset_query -- Reason: The recommended function, wp_reset_postdata, doesn't reset wp_query. \wp_reset_query(); \do_action( 'wpseo_head' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Reason: we have to restore the query. $GLOBALS['wp_query'] = $old_wp_query; } /** * Echoes all applicable presenters for a page. * * @return void */ public function present_head() { $context = $this->context_memoizer->for_current_page(); $presenters = $this->get_presenters( $context->page_type, $context ); /** * Filter 'wpseo_frontend_presentation' - Allow filtering the presentation used to output our meta values. * * @param Indexable_Presentation $presentation The indexable presentation. * @param Meta_Tags_Context $context The meta tags context for the current page. */ $presentation = \apply_filters( 'wpseo_frontend_presentation', $context->presentation, $context ); echo \PHP_EOL; foreach ( $presenters as $presenter ) { $presenter->presentation = $presentation; $presenter->helpers = $this->helpers; $presenter->replace_vars = $this->replace_vars; $output = $presenter->present(); if ( ! empty( $output ) ) { // phpcs:ignore WordPress.Security.EscapeOutput -- Presenters are responsible for correctly escaping their output. echo "\t" . $output . \PHP_EOL; } } echo \PHP_EOL . \PHP_EOL; } /** * Returns all presenters for this page. * * @param string $page_type The page type. * @param Meta_Tags_Context|null $context The meta tags context for the current page. * * @return Abstract_Indexable_Presenter[] The presenters. */ public function get_presenters( $page_type, $context = null ) { $context ??= $this->context_memoizer->for_current_page(); $needed_presenters = $this->get_needed_presenters( $page_type ); $callback = static function ( $presenter ) { if ( ! \class_exists( $presenter ) ) { return null; } return new $presenter(); }; $presenters = \array_filter( \array_map( $callback, $needed_presenters ) ); /** * Filter 'wpseo_frontend_presenters' - Allow filtering the presenter instances in or out of the request. * * @param Abstract_Indexable_Presenter[] $presenters List of presenter instances. * @param Meta_Tags_Context $context The meta tags context for the current page. */ $presenter_instances = \apply_filters( 'wpseo_frontend_presenters', $presenters, $context ); if ( ! \is_array( $presenter_instances ) ) { $presenter_instances = $presenters; } $is_presenter_callback = static function ( $presenter_instance ) { return $presenter_instance instanceof Abstract_Indexable_Presenter; }; $presenter_instances = \array_filter( $presenter_instances, $is_presenter_callback ); return \array_merge( [ new Marker_Open_Presenter() ], $presenter_instances, [ new Marker_Close_Presenter() ], ); } /** * Generate the array of presenters we need for the current request. * * @param string $page_type The page type we're retrieving presenters for. * * @return string[] The presenters. */ private function get_needed_presenters( $page_type ) { $presenters = $this->get_presenters_for_page_type( $page_type ); $presenters = $this->maybe_remove_title_presenter( $presenters ); $callback = static function ( $presenter ) { return "Yoast\WP\SEO\Presenters\\{$presenter}_Presenter"; }; $presenters = \array_map( $callback, $presenters ); /** * Filter 'wpseo_frontend_presenter_classes' - Allow filtering presenters in or out of the request. * * @param array $presenters List of presenters. * @param string $page_type The current page type. */ $presenters = \apply_filters( 'wpseo_frontend_presenter_classes', $presenters, $page_type ); return $presenters; } /** * Filters the presenters based on the page type. * * @param string $page_type The page type. * * @return string[] The presenters. */ private function get_presenters_for_page_type( $page_type ) { if ( $page_type === 'Error_Page' ) { $presenters = $this->base_presenters; if ( $this->options->get( 'opengraph' ) === true ) { $presenters = \array_merge( $presenters, $this->open_graph_error_presenters ); } return \array_merge( $presenters, $this->closing_presenters ); } $presenters = $this->get_all_presenters(); if ( \in_array( $page_type, [ 'Static_Home_Page', 'Home_Page' ], true ) ) { $presenters = \array_merge( $presenters, $this->webmaster_verification_presenters ); } // Filter out the presenters only needed for singular pages on non-singular pages. if ( ! \in_array( $page_type, [ 'Post_Type', 'Static_Home_Page' ], true ) ) { $presenters = \array_diff( $presenters, $this->singular_presenters ); } // Filter out `twitter:data` presenters for static home pages. if ( $page_type === 'Static_Home_Page' ) { $presenters = \array_diff( $presenters, $this->slack_presenters ); } return $presenters; } /** * Returns a list of all available presenters based on settings. * * @return string[] The presenters. */ private function get_all_presenters() { $presenters = \array_merge( $this->base_presenters, $this->indexing_directive_presenters ); if ( $this->options->get( 'opengraph' ) === true ) { $presenters = \array_merge( $presenters, $this->open_graph_presenters ); } if ( $this->options->get( 'twitter' ) === true && \apply_filters( 'wpseo_output_twitter_card', true ) !== false ) { $presenters = \array_merge( $presenters, $this->twitter_card_presenters ); } if ( $this->options->get( 'enable_enhanced_slack_sharing' ) === true && \apply_filters( 'wpseo_output_enhanced_slack_data', true ) !== false ) { $presenters = \array_merge( $presenters, $this->slack_presenters ); } return \array_merge( $presenters, $this->closing_presenters ); } /** * Whether the title presenter should be removed. * * @return bool True when the title presenter should be removed, false otherwise. */ public function should_title_presenter_be_removed() { return ! \get_theme_support( 'title-tag' ) && ! $this->options->get( 'forcerewritetitle', false ); } /** * Checks if the Title presenter needs to be removed. * * @param string[] $presenters The presenters. * * @return string[] The presenters. */ private function maybe_remove_title_presenter( $presenters ) { // Do not remove the title if we're on a REST request. if ( \wp_is_serving_rest_request() ) { return $presenters; } // Remove the title presenter if the theme is hardcoded to output a title tag so we don't have two title tags. if ( $this->should_title_presenter_be_removed() ) { $presenters = \array_diff( $presenters, [ 'Title' ] ); } return $presenters; } } Shop – Medistore
30% discount on all products special for November!
FREE SHIPPING FOR ALL ORDERS OF $340
Medistore
All
  • All
  • Accessories
  • Buds
  • Discounted
  • Laptop
  • Mobile
  • Monitors
  • Tablets
280 900 3434 Call Anytime
0
Your Cart

No products in the cart.

Shocking Campaign! 5% discount on the phones, last 20 stocks! Shocking Campaign! 5% discount on the Macbook Pro, last 12 stocks!
Categories
  • Accessories
  • Buds
    • Galaxy Buds2 Live
  • Discounted
  • Laptop
  • Mobile
  • Monitors
  • Tablets
  • 7 PAGES
    • Advanced
Medistore
0
Shocking Campaign! 5% discount on the phones, last 20 stocks! Shocking Campaign! 5% discount on the Macbook Pro, last 12 stocks!
Medistore
0
Your Cart

No products in the cart.

Menu
Categories
  • Accessories
  • Buds
    • Galaxy Buds2 Live
  • Discounted
  • Laptop
  • Mobile
  • Monitors
  • Tablets
  • Home
  • Shop

All Products

1 Accessories
2 Buds
1 Discounted
3 Laptop
5 Mobile
1 Monitors
2 Tablets
  • Clear filters
  • Watch Band
  • Galaxy Buds2 Live
Filter by color
  • Black 1
Product Categories
  • ✓Accessories
    • ✓Watch Band
  • ✓Buds
    • ✓Galaxy Buds2 Live
  • ✓Discounted
  • ✓Laptop
  • ✓Mobile
  • ✓Monitors
  • ✓Sound Devices
  • ✓Tablets
  • ✓Uncategorized
Product Status
Featured Products
  • Xiaomi Redmi Pad 6GB
    Note 5.00 sur 5
    $59.00 Le prix initial était : $59.00.$19.00Le prix actuel est : $19.00.
  • Xiaomi 12 Lite 5G + 4G
    Note 5.00 sur 5
    $1,450.99 Le prix initial était : $1,450.99.$1,250.99Le prix actuel est : $1,250.99.
  • OnePlus 10 Pro Android $1,250.99
  • OnePlus 11 5G Titan Black
    Note 5.00 sur 5
    $1,250.99 Le prix initial était : $1,250.99.$1,140.99Le prix actuel est : $1,140.99.
  • iPad Pro 11-inch 256GB $1,150.75 Le prix initial était : $1,150.75.$975.75Le prix actuel est : $975.75.
Fast Filters:
  • Featured
  • Best sellers
  • Top rated
  • Select Color
    • Select Storage
      • 128GB

    Showing the single result

    Filter
    • Show
    • 9
    • 12
    • 18
    • 24
    • Clear filters
    • Watch Band
    • Galaxy Buds2 Live
    Galaxy Buds Live
    Add to Wishlist
    Compare
    Quick View
    5.001
    Bestseller27%
    Galaxy Buds Live
    $375.89 Le prix initial était : $375.89.$275.89Le prix actuel est : $275.89.
    In stock: 1501
    • ✓ 5 Years Guarantee
    • ✓ free shipping returns
    • ✓ Installment up to 9 months
    Ajouter au panier
    Free Shipping

    Free Shipping for orders over £130

    Money Guarantee

    Within 40 days for an exchange.

    Flexible Payment

    Pay with Multiple Credit Cards

    Online Support

    24 hours a day, 7 days a week

    Social Media
    download apps
    Subscribe to the newsletter

      Need help? Call now!
      (500) 8001 8588, (500) 544 6550
      About Electron
      • Electron Inside
      • About Us
      • Company
      • Careers
      • Brands
      Smart Phones
      • Order Tracking
      • Terms & Conditions
      • Privacy Policy
      • Tutorials
      • FAQ
      Quick Links
      • Electron Inside
      • About Us
      • Company
      • Careers
      • Brands
      Smart TV
      • Order Tracking
      • Terms & Conditions
      • Privacy Policy
      • Tutorials
      • FAQ
      Policies
      • Electron Inside
      • About Us
      • Company
      • Careers
      • Brands
      Accessories
      • Order Tracking
      • Terms & Conditions
      • Privacy Policy
      • Tutorials
      • FAQ
      Brands
      • Samsung
      • Apple
      • LG
      • Bosch
      • Beko
      Bestseller
      • Iphone 14 Pro Max
      • Iphone 13 Pro Max
      • Samsung Note 21 Ultra
      • Xiaomi Vacuum Cleaner
      Secure Shopping
      All right reserved. Porta lorem mollis aliquam ut porttitor leo. Magna ac placerat vestibulum lectus mauris ultrices eros. Tortor condimentum lacinia quis...
      Don't show this popup again.

      Want exclusive access to discounts & offers on premium brands?

      Signup Today!

        *Limited time offer. Free USPS shipping only. Only available for US
        Most Popular Categories
        Galaxy Buds Live $375.89 Le prix initial était : $375.89.$275.89Le prix actuel est : $275.89.
        Ajouter au panier