diff --git a/CHANGELOG.md b/CHANGELOG.md index 13e8a7f..0332e3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## 1.10.5 +* Improved: Renamed Twitter to X +* Fixed: Check for matching provider +* Fixed: Missing assets for YouTube overlay in Elementor +* Fixed: Hiding providers in the opt-out shortcodes if desired +* Fixed: Potential PHP warning + +## 1.10.4 +* Fixed: Blocking embeds appearing in the same content after a disabled/always active provider +* Fixed: Cookie lifetime + +## 1.10.3 +* Fixed: Multiple replacements of the same embed +* Fixed: Replacing unknown embeds +* Fixed: Always return an embed provider via `Providers::get_by_name()` +* Fixed: Warning about potentially non-available asset version + +## 1.10.2 +* Fixed: Potential fatal error for missing check of the availability of the function `is_plugin_active` + +## 1.10.1 +* Fixed: Set correct "Tested up to" to WordPress 6.6 + ## 1.10.0 * Added: Thumbnail support for Polylang * Added: Filter to prevent orphan thumbnail deletion diff --git a/assets/images/embed-twitter.png b/assets/images/embed-twitter.png deleted file mode 100644 index 7547839..0000000 Binary files a/assets/images/embed-twitter.png and /dev/null differ diff --git a/assets/images/embed-x.png b/assets/images/embed-x.png new file mode 100644 index 0000000..b2331a7 Binary files /dev/null and b/assets/images/embed-x.png differ diff --git a/assets/js/embed-privacy.js b/assets/js/embed-privacy.js index cb6b95f..5fc4fda 100644 --- a/assets/js/embed-privacy.js +++ b/assets/js/embed-privacy.js @@ -87,7 +87,7 @@ document.addEventListener( 'DOMContentLoaded', function() { if ( cookie !== null && Object.keys( cookie ).length !== 0 && cookie.constructor === Object ) { cookie[ embedProvider ] = true; - set_cookie( 'embed-privacy', JSON.stringify( cookie ) ); + set_cookie( 'embed-privacy', JSON.stringify( cookie ), 365 ); } else { set_cookie( 'embed-privacy', '{"' + embedProvider + '":true}', 365 ); @@ -172,8 +172,8 @@ document.addEventListener( 'DOMContentLoaded', function() { if ( embedPrivacy.javascriptDetection === 'yes' && alwaysActiveProviders.indexOf( optOutCheckboxes[ i ].getAttribute( 'data-embed-provider' ) ) !== -1 ) { optOutCheckboxes[ i ].checked = true; } - else if ( embedPrivacy.javascriptDetection === 'yes' && ! showAll ) { - optOutCheckboxes[ i ].parentNode.classList.add( 'is-hidden' ); + else if ( ! showAll ) { + optOutCheckboxes[ i ].parentNode.parentNode.classList.add( 'is-hidden' ); continue; } diff --git a/embed-privacy.php b/embed-privacy.php index 3b0c6fa..d1d6d1d 100644 --- a/embed-privacy.php +++ b/embed-privacy.php @@ -5,7 +5,7 @@ Plugin Name: Embed Privacy Plugin URL: https://epiph.yt/en/embed-privacy/ Description: Embed Privacy prevents from loading external embeds directly and lets the user control which one should be loaded. -Version: 1.10.0 +Version: 1.10.5 Author: Epiphyt Author URI: https://epiph.yt/en/ License: GPL2 @@ -30,7 +30,7 @@ */ \defined( 'ABSPATH' ) || exit; -\define( 'EMBED_PRIVACY_VERSION', '1.10.0' ); +\define( 'EMBED_PRIVACY_VERSION', '1.10.5' ); if ( ! \defined( 'EPI_EMBED_PRIVACY_BASE' ) ) { \define( 'EPI_EMBED_PRIVACY_BASE', \WP_PLUGIN_DIR . '/embed-privacy/' ); diff --git a/inc/admin/class-settings.php b/inc/admin/class-settings.php index f0f0492..32798eb 100644 --- a/inc/admin/class-settings.php +++ b/inc/admin/class-settings.php @@ -73,7 +73,7 @@ public static function register() { 'embed_privacy', 'embed_privacy_general', [ - 'description' => \__( 'By enabling this option, tweets are embedded locally as text without any connection to Twitter, and no privacy overlay is required.', 'embed-privacy' ), + 'description' => \__( 'By enabling this option, tweets are embedded locally as text without any connection to X, and no privacy overlay is required.', 'embed-privacy' ), 'name' => 'embed_privacy_local_tweets', 'option_type' => 'option', 'title' => \__( 'Local tweets', 'embed-privacy' ), diff --git a/inc/class-embed-privacy.php b/inc/class-embed-privacy.php index 4ee2be8..186991b 100644 --- a/inc/class-embed-privacy.php +++ b/inc/class-embed-privacy.php @@ -22,7 +22,7 @@ use epiphyt\Embed_Privacy\integration\Maps_Marker; use epiphyt\Embed_Privacy\integration\Polylang; use epiphyt\Embed_Privacy\integration\Shortcodes_Ultimate; -use epiphyt\Embed_Privacy\integration\Twitter; +use epiphyt\Embed_Privacy\integration\X; use epiphyt\Embed_Privacy\thumbnail\Thumbnail; use ReflectionMethod; @@ -91,7 +91,7 @@ class Embed_Privacy { Maps_Marker::class, Polylang::class, Shortcodes_Ultimate::class, - Twitter::class, + X::class, ]; /** @@ -410,10 +410,10 @@ public function get_embed_overlay( $provider, $content ) { return $content; } - $overlay = new Replacement( $content ); + $replacement = new Replacement( $content ); - if ( $overlay->get_provider()->is_matching( $content ) ) { - $content = Template::get( $overlay->get_provider(), $overlay ); + foreach ( $replacement->get_providers() as $provider ) { + $content = Template::get( $provider, $replacement ); } return $content; @@ -704,7 +704,7 @@ public function has_embed( $post = null ) { * @deprecated 1.10.0 Use epiphyt\Embed_Privacy\data\Providers::is_always_active() instead * @since 1.1.0 * - * @param string $provider The embed provider in lowercase + * @param string $provider The embed provider in lowercase * @return bool True if provider is always active, false otherwise */ public function is_always_active_provider( $provider ) { @@ -978,7 +978,7 @@ public function replace_embeds_divi( $item_embed, $url ) { // phpcs:ignore Gener } /** - * Replace twitter embeds. + * Replace X embeds. * * @deprecated 1.6.3 * @since 1.6.1 @@ -1000,7 +1000,7 @@ public function replace_embeds_twitter( $output, $url, $args ) { return $output; } - $provider = Providers::get_instance()->get_by_name( 'twitter' ); + $provider = Providers::get_instance()->get_by_name( 'x' ); if ( ! $provider->is_matching( $url ) ) { return $output; @@ -1012,7 +1012,7 @@ public function replace_embeds_twitter( $output, $url, $args ) { if ( \get_option( 'embed_privacy_local_tweets' ) ) { // check for local tweets - return Twitter::get_local_tweet( $output ); + return X::get_local_tweet( $output ); } $args['embed_url'] = $url; diff --git a/inc/class-frontend.php b/inc/class-frontend.php index 019e901..fa18cd9 100644 --- a/inc/class-frontend.php +++ b/inc/class-frontend.php @@ -40,10 +40,6 @@ public function print_assets() { 'javascriptDetection' => \get_option( 'embed_privacy_javascript_detection' ), ] ); - if ( ! \function_exists( 'is_plugin_active' ) ) { - require_once \ABSPATH . \WPINC . '/plugin.php'; - } - /** * Fires after assets are printed. * diff --git a/inc/class-migration.php b/inc/class-migration.php index bc03051..b2efc14 100644 --- a/inc/class-migration.php +++ b/inc/class-migration.php @@ -29,7 +29,7 @@ class Migration { * @var string Current migration version * @since 1.2.2 */ - private $version = '1.8.0'; + private $version = '1.10.5'; /** * Migration constructor. @@ -196,31 +196,40 @@ public function migrate( $deprecated = null, $deprecated2 = null ) { case $this->version: // most recent version, do nothing break; + case '1.8.0': + $this->migrate_1_10_5(); + break; case '1.7.3': + $this->migrate_1_10_5(); $this->migrate_1_8_0(); break; case '1.7.0': + $this->migrate_1_10_5(); $this->migrate_1_8_0(); $this->migrate_1_7_3(); break; case '1.6.0': + $this->migrate_1_10_5(); $this->migrate_1_8_0(); $this->migrate_1_7_3(); $this->migrate_1_7_0(); break; case '1.5.0': + $this->migrate_1_10_5(); $this->migrate_1_8_0(); $this->migrate_1_7_3(); $this->migrate_1_7_0(); $this->migrate_1_6_0(); break; case '1.4.7': + $this->migrate_1_10_5(); $this->migrate_1_8_0(); $this->migrate_1_7_0(); $this->migrate_1_6_0(); $this->migrate_1_5_0(); break; case '1.4.0': + $this->migrate_1_10_5(); $this->migrate_1_8_0(); $this->migrate_1_7_0(); $this->migrate_1_6_0(); @@ -228,6 +237,7 @@ public function migrate( $deprecated = null, $deprecated2 = null ) { $this->migrate_1_4_7(); break; case '1.3.0': + $this->migrate_1_10_5(); $this->migrate_1_8_0(); $this->migrate_1_7_0(); $this->migrate_1_6_0(); @@ -235,6 +245,7 @@ public function migrate( $deprecated = null, $deprecated2 = null ) { $this->migrate_1_4_0(); break; case '1.2.2': + $this->migrate_1_10_5(); $this->migrate_1_8_0(); $this->migrate_1_7_0(); $this->migrate_1_6_0(); @@ -243,6 +254,7 @@ public function migrate( $deprecated = null, $deprecated2 = null ) { $this->migrate_1_3_0(); break; case '1.2.1': + $this->migrate_1_10_5(); $this->migrate_1_8_0(); $this->migrate_1_7_0(); $this->migrate_1_6_0(); @@ -252,6 +264,7 @@ public function migrate( $deprecated = null, $deprecated2 = null ) { $this->migrate_1_2_2(); break; case '1.2.0': + $this->migrate_1_10_5(); $this->migrate_1_8_0(); $this->migrate_1_7_0(); $this->migrate_1_6_0(); @@ -560,7 +573,7 @@ private function migrate_1_6_0() { * @see https://github.com/epiphyt/embed-privacy/issues/163 * @since 1.7.0 * - * - Update Google Maps regex + * - Update CrowdSignal regex */ private function migrate_1_7_0() { $crowdsignal_provider = \get_posts( [ @@ -674,6 +687,45 @@ private function migrate_1_8_0() { ] ); } + /** + * Migrations for version 1.10.5. + * + * @see https://github.com/epiphyt/embed-privacy/issues/235 + * @since 1.10.5 + * + * - Rename Twitter to X + */ + private function migrate_1_10_5() { + $twitter_provider = \get_posts( [ + 'meta_key' => 'is_system', + 'meta_value' => 'yes', + 'name' => 'twitter', + 'no_found_rows' => true, + 'post_type' => 'epi_embed', + 'update_post_term_cache' => false, + ] ); + $twitter_provider = \reset( $twitter_provider ); + + if ( $twitter_provider instanceof WP_Post ) { + $x_provider = [ + 'ID' => $twitter_provider->ID, + 'post_name' => \sanitize_title( \_x( 'X', 'embed provider', 'embed-privacy' ) ), + 'post_title' => \_x( 'X', 'embed provider', 'embed-privacy' ), + ]; + + /* translators: embed provider */ + if ( $twitter_provider->post_content === \sprintf( \__( 'Click here to display content from %s.', 'embed-privacy' ), \_x( 'Twitter', 'embed provider', 'embed-privacy' ) ) ) { + /* translators: embed provider */ + $x_provider['post_content'] = \sprintf( \__( 'Click here to display content from %s.', 'embed-privacy' ), \_x( 'X', 'embed provider', 'embed-privacy' ) ); + } + + \wp_update_post( $x_provider ); + \update_post_meta( $twitter_provider->ID, 'privacy_policy_url', \__( 'https://x.com/privacy', 'embed-privacy' ) ); + \update_post_meta( $twitter_provider->ID, 'regex_default', '/(twitter|x)\\\.com/' ); + \update_post_meta( $twitter_provider->ID, 'is_system', 'yes' ); + } + } + /** * Register default embed providers. */ @@ -1066,13 +1118,13 @@ public function register_default_embed_providers() { [ 'meta_input' => [ 'is_system' => 'yes', - 'privacy_policy_url' => \__( 'https://twitter.com/privacy', 'embed-privacy' ), - 'regex_default' => '/twitter\\\.com/', + 'privacy_policy_url' => \__( 'https://x.com/privacy', 'embed-privacy' ), + 'regex_default' => '/(twitter|x)\\\.com/', ], /* translators: embed provider */ - 'post_content' => \sprintf( \__( 'Click here to display content from %s.', 'embed-privacy' ), \_x( 'Twitter', 'embed provider', 'embed-privacy' ) ), + 'post_content' => \sprintf( \__( 'Click here to display content from %s.', 'embed-privacy' ), \_x( 'X', 'embed provider', 'embed-privacy' ) ), 'post_status' => 'publish', - 'post_title' => \_x( 'Twitter', 'embed provider', 'embed-privacy' ), + 'post_title' => \_x( 'X', 'embed provider', 'embed-privacy' ), 'post_type' => 'epi_embed', ], [ diff --git a/inc/class-system.php b/inc/class-system.php new file mode 100644 index 0000000..0ec4920 --- /dev/null +++ b/inc/class-system.php @@ -0,0 +1,28 @@ +get_list(); - $provider = null; $name = self::sanitize_name( $name ); foreach ( $embed_providers as $embed_provider ) { @@ -208,7 +209,7 @@ public function get_list( $type = 'all', $args = [] ) { * @param string $identifier Current identifier * @param array $global_list List with all providers of all identifiers */ - $this->list[ $identifier ] = \apply_filters( 'embed_privacy_provider_list', $this->list[ $identifier ], $identifier, $this->list ); + $this->list[ $identifier ] = (array) \apply_filters( 'embed_privacy_provider_list', $this->list[ $identifier ], $identifier, $this->list ); return $this->list[ $identifier ]; } diff --git a/inc/data/class-replacer.php b/inc/data/class-replacer.php index e2fc955..8765cd2 100644 --- a/inc/data/class-replacer.php +++ b/inc/data/class-replacer.php @@ -4,7 +4,7 @@ use epiphyt\Embed_Privacy\embed\Replacement; use epiphyt\Embed_Privacy\Embed_Privacy; use epiphyt\Embed_Privacy\handler\Oembed; -use epiphyt\Embed_Privacy\integration\Twitter; +use epiphyt\Embed_Privacy\integration\X; /** * Replacer functionality. @@ -66,11 +66,11 @@ public static function extend_pattern( $pattern, $provider ) { * * @since 1.10.0 * - * @param string[] $allowed_tags List of allowed tags - * @param string $provider Embed provider + * @param string[] $allowed_tags List of allowed tags + * @param \epiphyt\Embed_privacy\embed\Provider|null $provider Embed provider * @return array Updated list of allowed tags */ - $allowed_tags = \apply_filters( 'embed_privacy_replacer_matcher_elements', $allowed_tags, $provider ); + $allowed_tags = (array) \apply_filters( 'embed_privacy_replacer_matcher_elements', $allowed_tags, $provider ); $tags_regex = '(' . \implode( '|', \array_filter( $allowed_tags, static function( $tag ) { return \preg_quote( $tag, '/' ); @@ -153,52 +153,54 @@ public static function replace_oembed( $output, $url, array $attributes ) { return $output; } - $overlay = new Replacement( $output, $url ); + $replacement = new Replacement( $output, $url ); - // make sure to only run once - if ( \str_contains( $output, 'data-embed-provider="' . $overlay->get_provider()->get_name() . '"' ) ) { - return $output; - } - - // check if cookie is set - if ( Providers::is_always_active( $overlay->get_provider()->get_name() ) ) { - return $output; - } - - $embed_title = Oembed::get_title( $output ); - /* translators: embed title */ - $attributes['embed_title'] = ! empty( $embed_title ) ? $embed_title : ''; - $attributes['embed_url'] = $url; - $attributes['is_oembed'] = true; - $attributes['strip_newlines'] = true; - - // the default dimensions are useless - // so ignore them if recognized as such - $defaults = \wp_embed_defaults( $url ); - - if ( - ! empty( $attributes['height'] ) && $attributes['height'] === $defaults['height'] - && ! empty( $attributes['width'] ) && $attributes['width'] === $defaults['width'] - ) { - unset( $attributes['height'], $attributes['width'] ); + foreach ( $replacement->get_providers() as $provider ) { + // make sure to only run once + if ( \str_contains( $output, 'data-embed-provider="' . $provider->get_name() . '"' ) ) { + return $output; + } + + // check if cookie is set + if ( Providers::is_always_active( $provider->get_name() ) ) { + return $output; + } - $dimensions = Oembed::get_dimensions( $output ); + $embed_title = Oembed::get_title( $output ); + /* translators: embed title */ + $attributes['embed_title'] = ! empty( $embed_title ) ? $embed_title : ''; + $attributes['embed_url'] = $url; + $attributes['is_oembed'] = true; + $attributes['strip_newlines'] = true; - if ( ! empty( $dimensions ) ) { - $attributes = \array_merge( $attributes, $dimensions ); + // the default dimensions are useless + // so ignore them if recognized as such + $defaults = \wp_embed_defaults( $url ); + + if ( + ! empty( $attributes['height'] ) && $attributes['height'] === $defaults['height'] + && ! empty( $attributes['width'] ) && $attributes['width'] === $defaults['width'] + ) { + unset( $attributes['height'], $attributes['width'] ); + + $dimensions = Oembed::get_dimensions( $output ); + + if ( ! empty( $dimensions ) ) { + $attributes = \array_merge( $attributes, $dimensions ); + } + } + + // check for local tweets + if ( $provider->is( 'x' ) && \get_option( 'embed_privacy_local_tweets' ) ) { + return X::get_local_tweet( $output ); + } + + $output = $replacement->get( $attributes ); + + if ( $provider->is( 'youtube' ) ) { + // replace youtube.com with youtube-nocookie.com + $output = \str_replace( 'youtube.com', 'youtube-nocookie.com', $output ); } - } - - // check for local tweets - if ( $overlay->get_provider()->is( 'twitter' ) && \get_option( 'embed_privacy_local_tweets' ) ) { - return Twitter::get_local_tweet( $output ); - } - - $output = $overlay->get( $attributes ); - - if ( $overlay->get_provider()->is( 'youtube' ) ) { - // replace youtube.com with youtube-nocookie.com - $output = \str_replace( 'youtube.com', 'youtube-nocookie.com', $output ); } return $output; diff --git a/inc/embed/class-assets.php b/inc/embed/class-assets.php index 9f50d82..5a03974 100644 --- a/inc/embed/class-assets.php +++ b/inc/embed/class-assets.php @@ -173,6 +173,7 @@ private function set_background() { if ( $background_id ) { $this->background['path'] = \get_attached_file( $background_id ); $this->background['url'] = \wp_get_attachment_url( $background_id ); + $this->background['version'] = ''; } /** @@ -203,6 +204,7 @@ private function set_logo() { if ( \file_exists( \EPI_EMBED_PRIVACY_BASE . 'assets/images/embed-' . $this->provider . '.png' ) ) { $this->logo['path'] = \EPI_EMBED_PRIVACY_BASE . 'assets/images/embed-' . $this->provider . '.png'; $this->logo['url'] = \EPI_EMBED_PRIVACY_URL . 'assets/images/embed-' . $this->provider . '.png'; + $this->logo['version'] = ''; } if ( $this->embed_post instanceof WP_Post ) { @@ -249,6 +251,7 @@ private function set_thumbnail( $attributes ) { $this->thumbnail = [ 'path' => $thumbnail['thumbnail_path'], 'url' => $thumbnail['thumbnail_url'], + 'version' => '', ]; /** diff --git a/inc/embed/class-replacement.php b/inc/embed/class-replacement.php index ae2492e..d4ac172 100644 --- a/inc/embed/class-replacement.php +++ b/inc/embed/class-replacement.php @@ -23,10 +23,15 @@ final class Replacement { private $content = ''; /** - * @var \epiphyt\Embed_privacy\embed\Provider Provider of this overlay + * @var \epiphyt\Embed_privacy\embed\Provider|null Current processed provider for this replacement */ private $provider; + /** + * @var \epiphyt\Embed_privacy\embed\Provider[] List of matching providers for this replacement + */ + private $providers = []; + /** * @var array List of replacements */ @@ -67,7 +72,7 @@ public function get( array $attributes = [] ) { * @param bool $ignore_unknown Whether unknown providers should be ignored * @param string $content The original content */ - $ignore_unknown_providers = \apply_filters( 'embed_privacy_ignore_unknown_providers', false, $content ); + $ignore_unknown_providers = (bool) \apply_filters( 'embed_privacy_ignore_unknown_providers', false, $content ); // get default external content // special case for youtube-nocookie.com as it is part of YouTube provider @@ -81,13 +86,19 @@ public function get( array $attributes = [] ) { ) ) { $attributes['check_always_active'] = true; - $new_content = $this->replace_content( $content, $attributes ); - if ( $new_content !== $content ) { - Embed_Privacy::get_instance()->has_embed = true; - Embed_Privacy::get_instance()->frontend->print_assets(); - $content = $new_content; + foreach ( $this->get_providers() as $provider ) { + $this->provider = $provider; + $new_content = $this->replace_content( $content, $attributes ); + + if ( $new_content !== $content ) { + Embed_Privacy::get_instance()->has_embed = true; + Embed_Privacy::get_instance()->frontend->print_assets(); + $content = $new_content; + } } + + $this->provider = null; } return $content; @@ -111,6 +122,8 @@ private static function get_character_replacements() { /** * Filter character replacements. * + * @since 1.10.0 + * * @param array $replacements Current replacements */ $replacements = (array) \apply_filters( 'embed_privacy_overlay_character_replacements', $replacements ); @@ -119,14 +132,31 @@ private static function get_character_replacements() { } /** - * Get the overlay provider. + * Get the current provider. * - * @return \epiphyt\Embed_privacy\embed\Provider Provider object + * @deprecated 1.10.4 + * + * @return \epiphyt\Embed_privacy\embed\Provider|null Provider object */ public function get_provider() { + \_doing_it_wrong( + __METHOD__, + \esc_html__( 'This method is outdated and will be removed in the future.', 'embed-privacy' ), + '1.10.4' + ); + return $this->provider; } + /** + * Get all providers to replace an embed of. + * + * @return \epiphyt\Embed_privacy\embed\Provider[] Provider object + */ + public function get_providers() { + return $this->providers; + } + /** * Replace embedded content with an overlay. * @@ -339,14 +369,13 @@ private function replace_content( $content, $attributes ) { \libxml_use_internal_errors( false ); // embeds for other elements need to be handled manually - // make sure to test before if the regex matches - // see: https://github.com/epiphyt/embed-privacy/issues/26 if ( empty( $this->replacements ) && ! empty( $attributes['regex'] ) && ! $this->provider->is_unknown() && ! $this->provider->is_disabled() - && \preg_match( $attributes['regex'], $content, $matches ) !== false + && \preg_match( $attributes['regex'], $content, $matches ) === 1 + && ! \str_contains( $matches[0], 'embed-privacy-' ) ) { $content = \preg_replace( $attributes['regex'], @@ -395,38 +424,46 @@ private function replace_content( $content, $attributes ) { * @param string $url URL to the embedded content */ private function set_provider( $content, $url = '' ) { + $current_provider = null; $providers = Providers::get_instance()->get_list(); foreach ( $providers as $provider ) { if ( - $provider->is_matching( + ! $provider->is_matching( $content, Replacer::extend_pattern( $provider->get_pattern(), $provider ) ) - || ( ! empty( $url ) && $provider->is_matching( $url ) ) + && ( ! empty( $url ) && ! $provider->is_matching( $url ) ) ) { - $this->provider = $provider; - break; + continue; } + + $current_provider = $provider; + + // support unknown oEmbed provider + // see https://github.com/epiphyt/embed-privacy/issues/89 + if ( $current_provider === null && ! empty( $url ) ) { + $parsed_url = \wp_parse_url( $url ); + $provider = isset( $parsed_url['host'] ) ? $parsed_url['host'] : ''; + $current_provider = new Provider(); + $current_provider->set_name( $provider ); + $current_provider->set_title( $provider ); + } + + if ( $current_provider === null ) { + $current_provider = new Provider(); + } + + /** + * Filter the overlay provider. + * + * @since 1.10.0 + * + * @param \epiphyt\Embed_Privacy\embed\Provider $provider Current provider + * @param string $content Content to get the provider from + * @param string $url URL to the embedded content + */ + $this->providers[] = \apply_filters( 'embed_privacy_overlay_provider', $current_provider, $content, $url ); } - - // support unknown oEmbed provider - // see https://github.com/epiphyt/embed-privacy/issues/89 - if ( $this->provider === null && ! empty( $url ) ) { - $parsed_url = \wp_parse_url( $url ); - $provider = isset( $parsed_url['host'] ) ? $parsed_url['host'] : ''; - $this->provider = new Provider(); - $this->provider->set_name( $provider ); - $this->provider->set_title( $provider ); - } - - /** - * Filter the overlay provider. - * - * @param \epiphyt\Embed_Privacy\embed\Provider $provider Current provider - * @param string $content Content to get the provider from - * @param string $url URL to the embedded content - */ - $this->provider = \apply_filters( 'embed_privacy_overlay_provider', $this->provider, $content, $url ); } } diff --git a/inc/embed/class-template.php b/inc/embed/class-template.php index 67055fa..41ec756 100644 --- a/inc/embed/class-template.php +++ b/inc/embed/class-template.php @@ -22,7 +22,7 @@ final class Template { * @return string The overlay template */ public static function get( $provider, $output, $attributes = [] ) { - if ( \is_string( $provider ) ) { + if ( ! $provider instanceof Provider ) { \_doing_it_wrong( __METHOD__, \sprintf( @@ -40,7 +40,7 @@ public static function get( $provider, $output, $attributes = [] ) { /** * Filter the overlay arguments. * - * @deprecated 1.10.0 Use embed_privacy_template_arguments instead + * @deprecated 1.10.0 Use embed_privacy_template_attributes instead * @since 1.9.0 * * @param array $attributes Template arguments @@ -57,7 +57,7 @@ public static function get( $provider, $output, $attributes = [] ) { $output, ], '1.10.0', - 'embed_privacy_template_arguments' + 'embed_privacy_template_attributes' ); /** @@ -65,9 +65,9 @@ public static function get( $provider, $output, $attributes = [] ) { * * @since 1.10.0 * - * @param array $attributes Template attributes - * @param string $provider The embed provider - * @param string $output The output before replacing it + * @param array $attributes Template attributes + * @param \epiphyt\Embed_privacy\embed\Provider $provider The embed provider + * @param string $output The output before replacing it */ $attributes = (array) \apply_filters( 'embed_privacy_template_attributes', $attributes, $provider, $output ); diff --git a/inc/integration/class-elementor.php b/inc/integration/class-elementor.php index 7c6bc8d..cc6a8a3 100644 --- a/inc/integration/class-elementor.php +++ b/inc/integration/class-elementor.php @@ -8,6 +8,7 @@ use epiphyt\Embed_Privacy\data\Providers; use epiphyt\Embed_Privacy\embed\Template; use epiphyt\Embed_Privacy\Embed_Privacy; +use epiphyt\Embed_Privacy\System; /** * Elementor integration for Embed Privacy. @@ -128,11 +129,7 @@ private static function get_youtube_overlay( $content ) { * @return bool Whether Elementor has been used */ public static function is_used() { - if ( ! \function_exists( 'is_plugin_active' ) ) { - include_once \ABSPATH . 'wp-admin/includes/plugin.php'; - } - - return \is_plugin_active( 'elementor/elementor.php' ) + return System::is_plugin_active( 'elementor/elementor.php' ) && \get_the_ID() && Plugin::$instance->documents->get( \get_the_ID() )->is_built_with_elementor(); } @@ -169,6 +166,7 @@ public static function replace_youtube( $content ) { if ( \str_contains( $content, 'youtube.com\/watch' ) ) { $content = self::get_youtube_overlay( $content ); + Embed_Privacy::get_instance()->frontend->print_assets(); } return $content; diff --git a/inc/integration/class-kadence-blocks.php b/inc/integration/class-kadence-blocks.php index fe0c053..8e27eee 100644 --- a/inc/integration/class-kadence-blocks.php +++ b/inc/integration/class-kadence-blocks.php @@ -1,6 +1,8 @@ loadHTML( - '' . $html . '', - \LIBXML_HTML_NOIMPLIED | \LIBXML_HTML_NODEFDTD - ); - - // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase - // remove script tag - foreach ( $dom->getElementsByTagName( 'script' ) as $script ) { - $script->parentNode->removeChild( $script ); - } - - $xpath = new DOMXPath( $dom ); - - // get text node, which represents the author name - // and give it a span with class - foreach ( $xpath->query( '//blockquote/text()' ) as $node ) { - $author_node = $dom->createElement( 'span', $node->nodeValue ); - $author_node->setAttribute( 'class', 'embed-privacy-author-meta' ); - $node->parentNode->replaceChild( $author_node, $node ); - } - - // wrap author name by a meta div - /** @var \DOMElement $node */ - foreach ( $dom->getElementsByTagName( 'span' ) as $node ) { - if ( $node->getAttribute( 'class' ) !== 'embed-privacy-author-meta' ) { - continue; - } - - // create meta cite - $parent_node = $dom->createElement( 'cite' ); - $parent_node->setAttribute( 'class', 'embed-privacy-tweet-meta' ); - // append created cite to blockquote - $node->parentNode->appendChild( $parent_node ); - // move author meta inside meta cite - $parent_node->appendChild( $node ); - } - - /** @var \DOMElement $link */ - foreach ( $dom->getElementsByTagName( 'a' ) as $link ) { - if ( ! \preg_match( '/https?:\/\/twitter.com\/([^\/]+)\/status\/(\d+)/', $link->getAttribute( 'href' ) ) ) { - continue; - } - - // modify date in link to tweet - $l10n_date = \wp_date( \get_option( 'date_format' ), \strtotime( $link->nodeValue ) ); - - if ( \is_string( $l10n_date ) ) { - $link->nodeValue = $l10n_date; - } - - // move link inside meta div - if ( isset( $parent_node ) && $parent_node instanceof DOMElement ) { - $parent_node->appendChild( $link ); - } - } - - $content = $dom->saveHTML( $dom->documentElement ); - // phpcs:enable - - return \str_replace( [ '', '' ], [ '
', '
' ], $content ); - } -} +final class Twitter extends X {} diff --git a/inc/integration/class-x.php b/inc/integration/class-x.php new file mode 100644 index 0000000..2d84d1a --- /dev/null +++ b/inc/integration/class-x.php @@ -0,0 +1,90 @@ +loadHTML( + '' . $html . '', + \LIBXML_HTML_NOIMPLIED | \LIBXML_HTML_NODEFDTD + ); + + // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + // remove script tag + foreach ( $dom->getElementsByTagName( 'script' ) as $script ) { + $script->parentNode->removeChild( $script ); + } + + $xpath = new DOMXPath( $dom ); + + // get text node, which represents the author name + // and give it a span with class + foreach ( $xpath->query( '//blockquote/text()' ) as $node ) { + $author_node = $dom->createElement( 'span', $node->nodeValue ); + $author_node->setAttribute( 'class', 'embed-privacy-author-meta' ); + $node->parentNode->replaceChild( $author_node, $node ); + } + + // wrap author name by a meta div + /** @var \DOMElement $node */ + foreach ( $dom->getElementsByTagName( 'span' ) as $node ) { + if ( $node->getAttribute( 'class' ) !== 'embed-privacy-author-meta' ) { + continue; + } + + // create meta cite + $parent_node = $dom->createElement( 'cite' ); + $parent_node->setAttribute( 'class', 'embed-privacy-tweet-meta' ); + // append created cite to blockquote + $node->parentNode->appendChild( $parent_node ); + // move author meta inside meta cite + $parent_node->appendChild( $node ); + } + + /** @var \DOMElement $link */ + foreach ( $dom->getElementsByTagName( 'a' ) as $link ) { + if ( + ! \preg_match( '/https?:\/\/twitter.com\/([^\/]+)\/status\/(\d+)/', $link->getAttribute( 'href' ) ) + && ! \preg_match( '/https?:\/\/x.com\/([^\/]+)\/status\/(\d+)/', $link->getAttribute( 'href' ) ) + ) { + continue; + } + + // modify date in link to tweet + $l10n_date = \wp_date( \get_option( 'date_format' ), \strtotime( $link->nodeValue ) ); + + if ( \is_string( $l10n_date ) ) { + $link->nodeValue = $l10n_date; + } + + // move link inside meta div + if ( isset( $parent_node ) && $parent_node instanceof DOMElement ) { + $parent_node->appendChild( $link ); + } + } + + $content = $dom->saveHTML( $dom->documentElement ); + // phpcs:enable + + return \str_replace( [ '', '' ], [ '
', '
' ], $content ); + } +} diff --git a/inc/thumbnail/class-thumbnail.php b/inc/thumbnail/class-thumbnail.php index 0d12e86..0f74106 100644 --- a/inc/thumbnail/class-thumbnail.php +++ b/inc/thumbnail/class-thumbnail.php @@ -120,7 +120,7 @@ static function( $provider ) { * @param string $id The thumbnail ID * @param string $url The thumbnail URL * @param int $post_id The post ID - * @param string $provider The provider name + * @param string $provider The provider */ $should_delete = \apply_filters( 'embed_privacy_thumbnail_delete_orphaned', $should_delete, $id, $url, $post_id, $provider ); diff --git a/readme.txt b/readme.txt index de202e6..cf6436c 100644 --- a/readme.txt +++ b/readme.txt @@ -1,8 +1,8 @@ === Embed Privacy === Contributors: epiphyt, kittmedia, krafit -Tags: oembed, privacy, gutenberg +Tags: oembed, privacy, gutenberg, iframes, performance Requires at least: 5.9 -Stable tag: 1.10.0 +Stable tag: 1.10.5 Tested up to: 6.6 Requires PHP: 5.6 License: GPL2 @@ -43,9 +43,9 @@ The embedding process itself will be privacy-friendly with Embed Privacy. That m If you use the opt-out functionality with the shortcode or the functionality to allow the user to always display content of certain embed providers, Embed Privacy creates a single cookie called `embed-privacy` with an expiration of 1 year to store the user’s choice. -= Does Embed Privacy support the Gutenberg editor? = += Does Embed Privacy support the block editor? = -Sure thing! We enjoy playing with the new WordPress editor and developed Embed Privacy with Gutenberg in mind, the plugin will work no matter the editor you use. +Sure thing! We enjoy playing with the block editor and developed Embed Privacy with Gutenberg in mind, the plugin will work no matter the editor you use. = Which embeds are currently supported? = @@ -95,38 +95,6 @@ Since version 1.2.0, you can also add custom embed providers by going to **Setti Yes! Since version 1.5.0, Embed Privacy supports downloading and displaying thumbnails in posts for SlideShare, Vimeo and YouTube as background of Embed Privacy’s overlay. -= Developers: How to use Embed Privacy’s methods for custom content? = - -Since version 1.1.0 you can now use our mechanism for content we don’t support in our plugin. You can do it the following way: - -` -/** - * Replace specific content with the Embed Privacy overlay of type 'google-maps'. - * - * @param string $content The content to replace - * @return string The updated content - */ -function prefix_replace_content_with_overlay( $content ) { - // check for Embed Privacy - if ( ! class_exists( 'epiphyt\Embed_Privacy\Embed_Privacy' ) ) { - return $content; - } - - // get Embed Privacy instance - $embed_privacy = epiphyt\Embed_Privacy\Embed_Privacy::get_instance(); - - // check if provider is always active; if so, just return the content - if ( ! $embed_privacy->is_always_active_provider( 'google-maps' ) ) { - // replace the content with the overlay - $content = $embed_privacy->get_output_template( 'Google Maps', 'google-maps', $content ); - // enqueue assets - $embed_privacy->print_assets(); - } - - return $content; -} -` - = Can users opt-out of already opted in embed providers? = Yes! You can use the shortcode `[embed_privacy_opt_out]` to add a list of embed providers anywhere you want (recommendation: add it to your privacy policy) to allow your users to opt-out. @@ -177,6 +145,29 @@ You can report security bugs through the Patchstack Vulnerability Disclosure Pro == Changelog == += 1.10.5 = +* Improved: Renamed Twitter to X +* Fixed: Check for matching provider +* Fixed: Missing assets for YouTube overlay in Elementor +* Fixed: Hiding providers in the opt-out shortcodes if desired +* Fixed: Potential PHP warning + += 1.10.4 = +* Fixed: Blocking embeds appearing in the same content after a disabled/always active provider +* Fixed: Cookie lifetime + += 1.10.3 = +* Fixed: Multiple replacements of the same embed +* Fixed: Replacing unknown embeds +* Fixed: Always return an embed provider via `Providers::get_by_name()` +* Fixed: Warning about potentially non-available asset version + += 1.10.2 = +* Fixed: Potential fatal error for missing check of the availability of the function `is_plugin_active` + += 1.10.1 = +* Fixed: Set correct "Tested up to" to WordPress 6.6 + = 1.10.0 = * Added: Thumbnail support for Polylang * Added: Filter to prevent orphan thumbnail deletion