You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
202 lines
5.8 KiB
202 lines
5.8 KiB
<?php |
|
/** |
|
* Class AMP_Links_Sanitizer. |
|
* |
|
* @package AMP |
|
*/ |
|
|
|
/** |
|
* Class AMP_Link_Sanitizer. |
|
* |
|
* Adapts links for AMP-to-AMP navigation: |
|
* - In paired AMP (Transitional and Reader modes), internal links get '?amp' added to them. |
|
* - Internal links on AMP pages get rel=amphtml added to them. |
|
* - Forms with internal actions get a hidden 'amp' input added to them. |
|
* - AMP pages get meta[amp-to-amp-navigation] added to them. |
|
* - Any elements in the admin bar are excluded. |
|
* |
|
* Adapted from https://gist.github.com/westonruter/f9ee9ea717d52471bae092879e3d52b0 |
|
* |
|
* @link https://github.com/ampproject/amphtml/issues/12496 |
|
* @since 1.4.0 |
|
*/ |
|
class AMP_Link_Sanitizer extends AMP_Base_Sanitizer { |
|
|
|
/** |
|
* Default meta tag content. |
|
* |
|
* @var string |
|
*/ |
|
const DEFAULT_META_CONTENT = 'AMP-Redirect-To; AMP.navigateTo'; |
|
|
|
/** |
|
* Placeholder for default arguments, to be set in child classes. |
|
* |
|
* @var array |
|
*/ |
|
protected $DEFAULT_ARGS = [ // phpcs:ignore WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase |
|
'paired' => false, // Only set to true when in a paired mode (will be false when amp_is_canonical()). Controls whether query var is added. |
|
'meta_content' => self::DEFAULT_META_CONTENT, |
|
]; |
|
|
|
/** |
|
* Home host. |
|
* |
|
* @var string |
|
*/ |
|
protected $home_host; |
|
|
|
/** |
|
* Content path. |
|
* |
|
* @var string |
|
*/ |
|
protected $content_path; |
|
|
|
/** |
|
* Admin path. |
|
* |
|
* @var string |
|
*/ |
|
protected $admin_path; |
|
|
|
/** |
|
* Sanitizer constructor. |
|
* |
|
* @param DOMDocument $dom Document. |
|
* @param array $args Args. |
|
*/ |
|
public function __construct( DOMDocument $dom, array $args = [] ) { |
|
if ( ! isset( $args['meta_content'] ) ) { |
|
$args['meta_content'] = self::DEFAULT_META_CONTENT; |
|
} |
|
|
|
parent::__construct( $dom, $args ); |
|
|
|
$this->home_host = wp_parse_url( home_url(), PHP_URL_HOST ); |
|
$this->content_path = wp_parse_url( content_url( '/' ), PHP_URL_PATH ); |
|
$this->admin_path = wp_parse_url( admin_url(), PHP_URL_PATH ); |
|
} |
|
|
|
/** |
|
* Sanitize. |
|
*/ |
|
public function sanitize() { |
|
if ( ! empty( $this->args['meta_content'] ) ) { |
|
$this->add_meta_tag( $this->args['meta_content'] ); |
|
} |
|
|
|
$this->process_links(); |
|
} |
|
|
|
/** |
|
* Add the amp-to-amp-navigation meta tag. |
|
* |
|
* @param string $content The content for the meta tag, for example 'AMP-Redirect-To; AMP.navigateTo'. |
|
* @return DOMElement|null The added meta element if successful. |
|
*/ |
|
public function add_meta_tag( $content = self::DEFAULT_META_CONTENT ) { |
|
$head = $this->dom->documentElement->getElementsByTagName( 'head' )->item( 0 ); |
|
if ( ! $head || ! $content ) { |
|
return null; |
|
} |
|
$meta = $this->dom->createElement( 'meta' ); |
|
$meta->setAttribute( 'name', 'amp-to-amp-navigation' ); |
|
$meta->setAttribute( 'content', $content ); |
|
$head->appendChild( $meta ); |
|
return $meta; |
|
} |
|
|
|
/** |
|
* Process links by adding adding AMP query var to links in paired mode and adding rel=amphtml. |
|
*/ |
|
public function process_links() { |
|
/** |
|
* Element. |
|
* |
|
* @var DOMElement $element |
|
*/ |
|
$xpath = new DOMXPath( $this->dom ); |
|
|
|
// Remove admin bar from DOM to prevent mutating it. |
|
$admin_bar_container = $this->dom->getElementById( 'wpadminbar' ); |
|
$admin_bar_placeholder = null; |
|
if ( $admin_bar_container ) { |
|
$admin_bar_placeholder = $this->dom->createComment( 'wpadminbar' ); |
|
$admin_bar_container->parentNode->replaceChild( $admin_bar_placeholder, $admin_bar_container ); |
|
} |
|
|
|
foreach ( $xpath->query( '//*[ local-name() = "a" or local-name() = "area" ]' ) as $element ) { |
|
if ( ! $element->hasAttribute( 'href' ) ) { |
|
continue; |
|
} |
|
|
|
$href = $element->getAttribute( 'href' ); |
|
|
|
if ( $this->is_frontend_url( $href ) && '#' !== substr( $href, 0, 1 ) ) { |
|
// Always add the amphtml link relation when linking enabled. |
|
$rel = $element->hasAttribute( 'rel' ) ? $element->getAttribute( 'rel' ) . ' ' : ''; |
|
$rel .= 'amphtml'; |
|
$element->setAttribute( 'rel', $rel ); |
|
|
|
// Only add the AMP query var when requested (in Transitional or Reader mode). |
|
if ( ! empty( $this->args['paired'] ) ) { |
|
$href = add_query_arg( amp_get_slug(), '', $href ); |
|
$element->setAttribute( 'href', $href ); |
|
} |
|
} |
|
} |
|
|
|
foreach ( $xpath->query( '//form[ @action and translate( @method, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz") = "get" ]' ) as $element ) { |
|
if ( $this->is_frontend_url( $element->getAttribute( 'action' ) ) ) { |
|
$input = $this->dom->createElement( 'input' ); |
|
$input->setAttribute( 'name', amp_get_slug() ); |
|
$input->setAttribute( 'value', '' ); |
|
$input->setAttribute( 'type', 'hidden' ); |
|
$element->appendChild( $input ); |
|
} |
|
} |
|
|
|
// Replace the admin bar after mutations are done. |
|
if ( $admin_bar_container && $admin_bar_placeholder ) { |
|
$admin_bar_placeholder->parentNode->replaceChild( $admin_bar_container, $admin_bar_placeholder ); |
|
} |
|
} |
|
|
|
/** |
|
* Determine whether a URL is for the frontend. |
|
* |
|
* @param string $url URL. |
|
* @return bool Whether it is a frontend URL. |
|
*/ |
|
public function is_frontend_url( $url ) { |
|
$parsed_url = wp_parse_url( $url ); |
|
|
|
// Skip adding query var to links on other URLs. |
|
if ( ! empty( $parsed_url['host'] ) && $this->home_host !== $parsed_url['host'] ) { |
|
return false; |
|
} |
|
|
|
// Skip adding query var to PHP files (e.g. wp-login.php). |
|
if ( ! empty( $parsed_url['path'] ) && preg_match( '/\.php$/', $parsed_url['path'] ) ) { |
|
return false; |
|
} |
|
|
|
// Skip adding query var to feed URLs. |
|
if ( ! empty( $parsed_url['path'] ) && preg_match( ':/feed/(\w+/)?$:', $parsed_url['path'] ) ) { |
|
return false; |
|
} |
|
|
|
// Skip adding query var to the admin. |
|
if ( ! empty( $parsed_url['path'] ) && false !== strpos( $parsed_url['path'], $this->admin_path ) ) { |
|
return false; |
|
} |
|
|
|
// Skip adding query var to content links (e.g. images). |
|
if ( ! empty( $parsed_url['path'] ) && false !== strpos( $parsed_url['path'], $this->content_path ) ) { |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
}
|
|
|