<commit_before><?php
/**
 * Class AMP_Links_Sanitizer.
 *
 * @package AMP
 */

/**
 * Class AMP_Meta_Sanitizer.
 *
 * Sanitizes meta tags found in the header.
 *
 * @since 1.5.0
 */
class AMP_Meta_Sanitizer extends AMP_Base_Sanitizer {

	/**
	 * Tag.
	 *
	 * @var string HTML <meta> tag to identify and replace with AMP version.
	 */
	public static $tag = 'meta';

	/**
	 * Placeholder for default arguments, to be set in child classes.
	 *
	 * @var array
	 */
	protected $DEFAULT_ARGS = [ // phpcs:ignore WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase
		'use_document_element' => true, // We want to work on the header, so we need the entire document.
	];

	/**
	 * Reference to the shared XPath object to query the DOM.
	 *
	 * @var DOMXPath
	 */
	protected $xpath;

	/**
	 * The document's <head> element.
	 *
	 * @var DOMElement
	 */
	protected $head;

	/*
	 * Tags array keys.
	 */
	const TAG_CHARSET        = 'charset';
	const TAG_VIEWPORT       = 'viewport';
	const TAG_AMP_SCRIPT_SRC = 'amp_script_src';
	const TAG_OTHER          = 'other';

	/**
	 * Associative array of DOMElement arrays.
	 *
	 * Each key in the root level defines one group of meta tags to process.
	 *
	 * @var array $tags {
	 *     An array of meta tag groupings.
	 *
	 *     @type DOMElement[] $charset        Charset meta tag(s).
	 *     @type DOMElement[] $viewport       Viewport meta tag(s).
	 *     @type DOMElement[] $amp_script_src <amp-script> source meta tags.
	 *     @type DOMElement[] $other          Remaining meta tags.
	 * }
	 */
	protected $meta_tags = [
		self::TAG_CHARSET        => [],
		self::TAG_VIEWPORT       => [],
		self::TAG_AMP_SCRIPT_SRC => [],
		self::TAG_OTHER          => [],
	];

	/**
	 * Charset to use for AMP markup.
	 *
	 * @var string
	 */
	const AMP_CHARSET = 'utf-8';

	/**
	 * Viewport settings to use for AMP markup.
	 *
	 * @var string
	 */
	const AMP_VIEWPORT = 'width=device-width';

	/**
	 * Sanitize.
	 */
	public function sanitize() {
		$this->xpath = new DOMXPath( $this->dom );
		$this->head  = $this->ensure_head_is_present();
		$charset     = $this->detect_charset();
		$elements    = $this->dom->getElementsByTagName( static::$tag );

		// Remove all nodes for easy reordering later on.
		$elements = array_map(
			static function ( $element ) {
				return $element->parentNode->removeChild( $element );
			},
			iterator_to_array( $elements, false )
		);

		foreach ( $elements as $element ) {
			/**
			 * Meta tag to process.
			 *
			 * @var DOMElement $element
			 */
			if ( $element->hasAttribute( 'charset' ) ) {
				$this->meta_tags[ self::TAG_CHARSET ][] = $element;
			} elseif ( 'viewport' === $element->getAttribute( 'name' ) ) {
				$this->meta_tags[ self::TAG_VIEWPORT ][] = $element;
			} elseif ( 'amp-script-src' === $element->getAttribute( 'name' ) ) {
				$this->meta_tags[ self::TAG_AMP_SCRIPT_SRC ][] = $element;
			} else {
				$this->meta_tags[ self::TAG_OTHER ][] = $element;
			}
		}

		$this->ensure_charset_is_present( $charset );

		if ( ! $this->is_correct_charset() ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement
			// @TODO Re-encode the content into UTF-8.
			// ... sure?
		}

		$this->ensure_viewport_is_present();

		$this->process_amp_script_meta_tags();

		$this->re_add_meta_tags_in_optimized_order();
	}

	/**
	 * Ensure that the <head> element is present in the document.
	 *
	 * @return DOMElement The document's <head> element.
	 */
	protected function ensure_head_is_present() {
		$head = $this->dom->getElementsByTagName( 'head' )->item( 0 );

		if ( ! $head ) {
			$head = $this->dom->createElement( 'head' );
			$head = $this->dom->documentElement->insertBefore( $head, $this->dom->documentElement->firstChild );
		}

		return $head;
	}

	/**
	 * Detect the charset of the document.
	 *
	 * @return string|false Detected charset of the document, or false if none.
	 */
	protected function detect_charset() {
		$charset = false;

		// Check for HTML 4 http-equiv meta tags.
		$http_equiv_tag = $this->xpath->query( '//meta[ @http-equiv and @content ]' )->item( 0 );
		if ( $http_equiv_tag ) {
			$http_equiv_tag->parentNode->removeChild( $http_equiv_tag );

			// Check for the existence of a proper charset attribute first.
			$charset = $http_equiv_tag->getAttribute( 'charset' );
			if ( ! $charset ) {
				// If not, check whether the charset is included with the content type, and use that.
				$content = $http_equiv_tag->getAttribute( 'content' );

				$matches = [];
				if ( preg_match( '/;\s*charset=(?<charset>[^;]+)/', $content, $matches ) && ! empty( $matches['charset'] ) ) {
					$charset = $matches['charset'];
				}
			}
		}

		// Check for HTML 5 charset meta tag. This overrides the HTML 4 charset.
		$charset_tag = $this->xpath->query( '//meta[ @charset ]' )->item( 0 );
		if ( $charset_tag ) {
			$charset_tag = $charset_tag->parentNode->removeChild( $charset_tag );
			$charset     = $charset_tag->getAttribute( 'charset' );
		}

		return $charset;
	}

	/**
	 * Always ensure that we have an HTML 5 charset meta tag.
	 *
	 * The charset defaults to utf-8, which is also what AMP requires.
	 *
	 * @param string|false $charset Optional. Charset that was already detected. False if none. Defaults to false.
	 */
	protected function ensure_charset_is_present( $charset = false ) {
		if ( ! empty( $this->meta_tags[ self::TAG_CHARSET ] ) ) {
			return;<commit_msg>Apply suggestions from code review<commit_after><?php
/**
 * Class AMP_Links_Sanitizer.
 *
 * @package AMP
 */

/**
 * Class AMP_Meta_Sanitizer.
 *
 * Sanitizes meta tags found in the header.
 *
 * @since 1.5.0
 */
class AMP_Meta_Sanitizer extends AMP_Base_Sanitizer {

	/**
	 * Tag.
	 *
	 * @var string HTML <meta> tag to identify and replace with AMP version.
	 */
	public static $tag = 'meta';

	/**
	 * Placeholder for default arguments, to be set in child classes.
	 *
	 * @var array
	 */
	protected $DEFAULT_ARGS = [ // phpcs:ignore WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase
		'use_document_element' => true, // We want to work on the header, so we need the entire document.
	];

	/**
	 * Reference to the shared XPath object to query the DOM.
	 *
	 * @var DOMXPath
	 */
	protected $xpath;

	/**
	 * The document's <head> element.
	 *
	 * @var DOMElement
	 */
	protected $head;

	/*
	 * Tags array keys.
	 */
	const TAG_CHARSET        = 'charset';
	const TAG_VIEWPORT       = 'viewport';
	const TAG_AMP_SCRIPT_SRC = 'amp_script_src';
	const TAG_OTHER          = 'other';

	/**
	 * Associative array of DOMElement arrays.
	 *
	 * Each key in the root level defines one group of meta tags to process.
	 *
	 * @var array $tags {
	 *     An array of meta tag groupings.
	 *
	 *     @type DOMElement[] $charset        Charset meta tag(s).
	 *     @type DOMElement[] $viewport       Viewport meta tag(s).
	 *     @type DOMElement[] $amp_script_src <amp-script> source meta tags.
	 *     @type DOMElement[] $other          Remaining meta tags.
	 * }
	 */
	protected $meta_tags = [
		self::TAG_CHARSET        => [],
		self::TAG_VIEWPORT       => [],
		self::TAG_AMP_SCRIPT_SRC => [],
		self::TAG_OTHER          => [],
	];

	/**
	 * Charset to use for AMP markup.
	 *
	 * @var string
	 */
	const AMP_CHARSET = 'utf-8';

	/**
	 * Viewport settings to use for AMP markup.
	 *
	 * @var string
	 */
	const AMP_VIEWPORT = 'width=device-width';

	/**
	 * Sanitize.
	 */
	public function sanitize() {
		$this->xpath = new DOMXPath( $this->dom );
		$this->head  = $this->ensure_head_is_present();
		$charset     = $this->detect_charset();
		$elements    = $this->dom->getElementsByTagName( static::$tag );

		// Remove all nodes for easy reordering later on.
		$elements = array_map(
			static function ( $element ) {
				return $element->parentNode->removeChild( $element );
			},
			iterator_to_array( $elements, false )
		);

		foreach ( $elements as $element ) {
			/**
			 * Meta tag to process.
			 *
			 * @var DOMElement $element
			 */
			if ( $element->hasAttribute( 'charset' ) ) {
				$this->meta_tags[ self::TAG_CHARSET ][] = $element;
			} elseif ( 'viewport' === $element->getAttribute( 'name' ) ) {
				$this->meta_tags[ self::TAG_VIEWPORT ][] = $element;
			} elseif ( 'amp-script-src' === $element->getAttribute( 'name' ) ) {
				$this->meta_tags[ self::TAG_AMP_SCRIPT_SRC ][] = $element;
			} else {
				$this->meta_tags[ self::TAG_OTHER ][] = $element;
			}
		}

		$this->ensure_charset_is_present( $charset );

		if ( ! $this->is_correct_charset() ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement
			// @TODO Re-encode the content into UTF-8.
			// ... sure?
		}

		$this->ensure_viewport_is_present();

		$this->process_amp_script_meta_tags();

		$this->re_add_meta_tags_in_optimized_order();
	}

	/**
	 * Ensure that the <head> element is present in the document.
	 *
	 * @return DOMElement The document's <head> element.
	 */
	protected function ensure_head_is_present() {
		$head = $this->dom->getElementsByTagName( 'head' )->item( 0 );

		if ( ! $head ) {
			$head = $this->dom->createElement( 'head' );
			$head = $this->dom->documentElement->insertBefore( $head, $this->dom->documentElement->firstChild );
		}

		return $head;
	}

	/**
	 * Detect the charset of the document.
	 *
	 * @return string|false Detected charset of the document, or false if none.
	 */
	protected function detect_charset() {
		$charset = false;

		// Check for HTML 4 http-equiv meta tags.
		$http_equiv_tag = $this->xpath->query( '//meta[ translate(@http-equiv, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') = 'content-type' and @content ]' )->item( 0 );
		if ( $http_equiv_tag ) {
			$http_equiv_tag->parentNode->removeChild( $http_equiv_tag );

			// Check for the existence of a proper charset attribute first.
			$charset = $http_equiv_tag->getAttribute( 'charset' );
			if ( ! $charset ) {
				// If not, check whether the charset is included with the content type, and use that.
				$content = $http_equiv_tag->getAttribute( 'content' );

				$matches = [];
				if ( preg_match( '/;\s*charset=(?<charset>[^;]+)/', $content, $matches ) && ! empty( $matches['charset'] ) ) {
					$charset = trim( $matches['charset'] );
				}
			}
		}

		// Check for HTML 5 charset meta tag. This overrides the HTML 4 charset.
		$charset_tag = $this->xpath->query( '//meta[ @charset ]' )->item( 0 );
		if ( $charset_tag ) {
			$charset_tag = $charset_tag->parentNode->removeChild( $charset_tag );
			$charset     = $charset_tag->getAttribute( 'charset' );
		}

		return $charset;
	}

	/**
	 * Always ensure that we have an HTML 5 charset meta tag.
	 *
	 * The charset defaults to utf-8, which is also what AMP requires.
	 *
	 * @param string|false $charset Optional. Charset that was already detected. False if none. Defaults to false.
	 */
	protected function ensure_charset_is_present( $charset = false ) {
		if ( ! empty( $this->meta_tags[ self::TAG_CHARSET ] ) ) {
			return;
{'fraction_non_alphanumeric': 0.14866477012021656, 'fraction_numerical': 0.0023859777920528587, 'mean_word_length': 3.063385533184191, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 117, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '522109', 'n_tokens_mistral': 4073, 'n_tokens_neox': 3526, 'n_words': 941}