content = $content; $this->set_provider( $content, $url ); } /** * Get the content with an overlay. * * @param array $attributes Embed attributes * @return string Content with embeds replaced by an overlay */ public function get( array $attributes = [] ) { /** * Filter the content after it has been replaced with an overlay. * * @since 1.10.0 * * @param string $content Replaced content */ $content = (string) \apply_filters( 'embed_privacy_overlay_replaced_content', $this->content ); /** * If set to true, unknown providers are not handled via Embed Privacy. * * @since 1.5.0 * * @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 ); // get default external content // special case for youtube-nocookie.com as it is part of YouTube provider // and gets rewritten in Divi // see: https://github.com/epiphyt/embed-privacy/issues/69 if ( ! $ignore_unknown_providers && ( ! \str_contains( $content, 'youtube-nocookie.com' ) || ! Providers::is_always_active( 'youtube' ) ) ) { $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; } } return $content; } /** * Get a list of characters to replace to prevent problems with DOMDocument. * * @return array List of character replacements */ private static function get_character_replacements() { $replacements = [ '%' => '@@epi_percentage', ' ' => ' data-epi-spacing ', '[' => '@@epi_square_bracket_start', ']' => '@@epi_square_bracket_end', '{' => '@@epi_curly_bracket_start', '}' => '@@epi_curly_bracket_end', ]; /** * Filter character replacements. * * @param array $replacements Current replacements */ $replacements = (array) \apply_filters( 'embed_privacy_overlay_character_replacements', $replacements ); return $replacements; } /** * Get the overlay provider. * * @return \epiphyt\Embed_privacy\embed\Provider Provider object */ public function get_provider() { return $this->provider; } /** * Replace embedded content with an overlay. * * @param string $content Content to replace embeds in * @param array $attributes Additional attributes * @return string Updated content */ private function replace_content( $content, $attributes ) { if ( empty( $content ) ) { return $content; } if ( ! $this->provider instanceof Provider ) { return $content; } /** * Filter whether to ignore this embed. * * @since 1.9.0 * * @param bool $ignore_embed Whether to ignore this embed * @param string $content The original content * @param string $provider_title Embed provider title * @param string $provider_name Embed provider name * @param array $attributes Additional attributes */ $ignore_embed = (bool) \apply_filters( 'embed_privacy_ignore_embed', false, $content, $this->provider->get_title(), $this->provider->get_name(), $attributes ); if ( $ignore_embed ) { return $content; } $attributes = \wp_parse_args( $attributes, [ 'additional_checks' => [], 'check_always_active' => false, 'elements' => [ 'embed', 'iframe', 'object' ], 'element_attribute' => 'src', 'height' => 0, 'ignore_aspect_ratio' => false, 'is_oembed' => false, 'regex' => Replacer::extend_pattern( $this->provider->get_pattern(), $this->provider ), 'strip_newlines' => ! \has_blocks( $content ), 'width' => 0, ] ); if ( $attributes['is_oembed'] ) { return Template::get( $this->provider, $content, $attributes ); } \libxml_use_internal_errors( true ); $dom = new DOMDocument(); $character_replacements = self::get_character_replacements(); $dom->loadHTML( '' . \str_replace( \array_keys( $character_replacements ), \array_values( $character_replacements ), $content ) . '', \LIBXML_HTML_NOIMPLIED | \LIBXML_HTML_NODEFDTD ); $template_dom = new DOMDocument(); // detect domain if WordPress is installed on a sub domain $host = \wp_parse_url( \home_url(), \PHP_URL_HOST ); if ( ! \filter_var( $host, \FILTER_VALIDATE_IP ) ) { $host_array = \explode( '.', \str_replace( 'www.', '', $host ) ); $tld_count = \count( $host_array ); if ( $tld_count >= 3 && \strlen( $host_array[ $tld_count - 2 ] ) === 2 ) { $host = \implode( '.', \array_splice( $host_array, $tld_count - 3, 3 ) ); } else if ( $tld_count >= 2 ) { $host = \implode( '.', \array_splice( $host_array, $tld_count - 2, $tld_count ) ); } } foreach ( $attributes['elements'] as $tag ) { $replacements = []; if ( $tag === 'object' ) { $attributes['element_attribute'] = 'data'; } /** @var \DOMElement $element */ foreach ( $dom->getElementsByTagName( $tag ) as $element ) { if ( ! Embed_Privacy::get_instance()->run_checks( $attributes['additional_checks'], $element ) ) { continue; } // ignore embeds from the same (sub-)domain if ( \preg_match( '/https?:\/\/(.*\.)?' . \preg_quote( $host, '/' ) . '/', $element->getAttribute( $attributes['element_attribute'] ) ) ) { continue; } if ( ! empty( $attributes['regex'] ) && ! \preg_match( $attributes['regex'], $element->getAttribute( $attributes['element_attribute'] ) ) ) { continue; } // providers need to be explicitly checked if they're always active // see https://github.com/epiphyt/embed-privacy/issues/115 if ( $attributes['check_always_active'] && Providers::is_always_active( $this->provider->get_name() ) ) { if ( ! empty( $attributes['assets'] ) ) { $content = Assets::get_static( $attributes['assets'], $content ); } return $content; } if ( $this->provider->is_unknown() ) { $embedded_host = \wp_parse_url( $element->getAttribute( $attributes['element_attribute'] ), \PHP_URL_HOST ); // embeds with relative paths have no host // and they are local by definition, so do nothing // see https://github.com/epiphyt/embed-privacy/issues/27 if ( empty( $embedded_host ) ) { return $content; } $this->provider->set_title( $embedded_host ); $this->provider->set_name( \sanitize_title( $embedded_host ) ); // unknown providers need to be explicitly checked if they're always active // see https://github.com/epiphyt/embed-privacy/issues/115 if ( $attributes['check_always_active'] && Providers::is_always_active( $this->provider->get_name() ) ) { if ( ! empty( $attributes['assets'] ) ) { $content = Assets::get_static( $attributes['assets'], $content ); } return $content; } // check URL for available provider foreach ( Providers::get_instance()->get_list() as $provider ) { if ( $provider->is_matching( $element->getAttribute( $attributes['element_attribute'] ) ) && empty( $replacements ) ) { continue 2; } } } /* translators: embed title */ $attributes['embed_title'] = $element->hasAttribute( 'title' ) ? $element->getAttribute( 'title' ) : ''; $attributes['embed_url'] = $element->getAttribute( $attributes['element_attribute'] ); $attributes['height'] = $element->hasAttribute( 'height' ) ? $element->getAttribute( 'height' ) : 0; $attributes['width'] = $element->hasAttribute( 'width' ) ? $element->getAttribute( 'width' ) : 0; // get overlay template as DOM element $template_dom->loadHTML( '' . \str_replace( '%', '%_epi_', Template::get( $this->provider, $dom->saveHTML( $element ), $attributes ) ) . '', \LIBXML_HTML_NOIMPLIED | \LIBXML_HTML_NODEFDTD ); $overlay = null; /** @var \DOMElement $div */ foreach ( $template_dom->getElementsByTagName( 'div' ) as $div ) { if ( \stripos( $div->getAttribute( 'class' ), 'embed-privacy-container' ) !== false ) { $overlay = $div; break; } } // store the elements to replace (see regressive loop down below) if ( $overlay instanceof DOMNode || $overlay instanceof DOMElement ) { $replacements[] = [ 'element' => $element, 'replace' => $dom->importNode( $overlay, true ), ]; } // reset embed provider name if ( $this->provider->is_unknown() ) { $this->provider->set_name( '' ); $this->provider->set_title( '' ); } } if ( ! empty( $replacements ) ) { $this->replacements = \array_merge( $this->replacements, $replacements ); Embed_Privacy::get_instance()->has_embed = true; $elements = $dom->getElementsByTagName( $tag ); $i = $elements->length - 1; // use regressive loop for replaceChild() // see: https://www.php.net/manual/en/domnode.replacechild.php#50500 while ( $i > -1 ) { $element = $elements->item( $i ); foreach ( $replacements as $replacement ) { if ( $replacement['element'] === $element ) { $element->parentNode->replaceChild( $replacement['replace'], $replacement['element'] ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase } } --$i; } $content = $dom->saveHTML( $dom->documentElement ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase } } \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 ) { $content = \preg_replace( $attributes['regex'], Template::get( $this->provider, $matches[0], $attributes ), $content, 1 ); } // decode to make sure there is nothing left encoded if replacements have been made // otherwise, content is untouched by DOMDocument, and we don't need a decoding // only required for WPBakery Page Builder if ( ! empty( $this->replacements ) && \str_contains( 'vc_row', $content ) ) { $content = \rawurldecode( $content ); } return \str_replace( \array_merge( [ '', '', '%20data-epi-spacing%20', ], \array_values( $character_replacements ) ), \array_merge( [ '', '', ' ', ], \array_keys( $character_replacements ) ), $content ); } /** * Set the provider for this overlay. * * @param string $content Content to get the provider from * @param string $url URL to the embedded content */ private function set_provider( $content, $url = '' ) { $providers = Providers::get_instance()->get_list(); foreach ( $providers as $provider ) { if ( $provider->is_matching( $content, Replacer::extend_pattern( $provider->get_pattern(), $provider ) ) || ( ! empty( $url ) && $provider->is_matching( $url ) ) ) { $this->provider = $provider; break; } } // 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 ); } }