WP_HTML_Tag_Processor::set_attribute()publicWP 6.2.0

Updates or creates a new attribute on the currently matched tag with the passed value.

For boolean attributes special handling is provided:

  • When true is passed as the value, then only the attribute name is added to the tag.
  • When false is passed, the attribute gets removed if it existed before.

For string attributes, the value is escaped using the esc_attr function.

Method of the class: WP_HTML_Tag_Processor{}

No Hooks.

Return

true|false. Whether an attribute value was set.

Usage

$WP_HTML_Tag_Processor = new WP_HTML_Tag_Processor();
$WP_HTML_Tag_Processor->set_attribute( $name, $value );
$name(string) (required)
The attribute name to target.
$value(string|true|false) (required)
The new attribute value.

Changelog

Since 6.2.0 Introduced.
Since 6.2.1 Fix: Only create a single update for multiple calls with case-variant attribute names.

WP_HTML_Tag_Processor::set_attribute() code WP 6.6.2

public function set_attribute( $name, $value ) {
	if (
		self::STATE_MATCHED_TAG !== $this->parser_state ||
		$this->is_closing_tag
	) {
		return false;
	}

	/*
	 * WordPress rejects more characters than are strictly forbidden
	 * in HTML5. This is to prevent additional security risks deeper
	 * in the WordPress and plugin stack. Specifically the
	 * less-than (<) greater-than (>) and ampersand (&) aren't allowed.
	 *
	 * The use of a PCRE match enables looking for specific Unicode
	 * code points without writing a UTF-8 decoder. Whereas scanning
	 * for one-byte characters is trivial (with `strcspn`), scanning
	 * for the longer byte sequences would be more complicated. Given
	 * that this shouldn't be in the hot path for execution, it's a
	 * reasonable compromise in efficiency without introducing a
	 * noticeable impact on the overall system.
	 *
	 * @see https://html.spec.whatwg.org/#attributes-2
	 *
	 * @todo As the only regex pattern maybe we should take it out?
	 *       Are Unicode patterns available broadly in Core?
	 */
	if ( preg_match(
		'~[' .
			// Syntax-like characters.
			'"\'>&</ =' .
			// Control characters.
			'\x{00}-\x{1F}' .
			// HTML noncharacters.
			'\x{FDD0}-\x{FDEF}' .
			'\x{FFFE}\x{FFFF}\x{1FFFE}\x{1FFFF}\x{2FFFE}\x{2FFFF}\x{3FFFE}\x{3FFFF}' .
			'\x{4FFFE}\x{4FFFF}\x{5FFFE}\x{5FFFF}\x{6FFFE}\x{6FFFF}\x{7FFFE}\x{7FFFF}' .
			'\x{8FFFE}\x{8FFFF}\x{9FFFE}\x{9FFFF}\x{AFFFE}\x{AFFFF}\x{BFFFE}\x{BFFFF}' .
			'\x{CFFFE}\x{CFFFF}\x{DFFFE}\x{DFFFF}\x{EFFFE}\x{EFFFF}\x{FFFFE}\x{FFFFF}' .
			'\x{10FFFE}\x{10FFFF}' .
		']~Ssu',
		$name
	) ) {
		_doing_it_wrong(
			__METHOD__,
			__( 'Invalid attribute name.' ),
			'6.2.0'
		);

		return false;
	}

	/*
	 * > The values "true" and "false" are not allowed on boolean attributes.
	 * > To represent a false value, the attribute has to be omitted altogether.
	 *     - HTML5 spec, https://html.spec.whatwg.org/#boolean-attributes
	 */
	if ( false === $value ) {
		return $this->remove_attribute( $name );
	}

	if ( true === $value ) {
		$updated_attribute = $name;
	} else {
		$comparable_name = strtolower( $name );

		/*
		 * Escape URL attributes.
		 *
		 * @see https://html.spec.whatwg.org/#attributes-3
		 */
		$escaped_new_value = in_array( $comparable_name, wp_kses_uri_attributes() ) ? esc_url( $value ) : esc_attr( $value );

		// If the escaping functions wiped out the update, reject it and indicate it was rejected.
		if ( '' === $escaped_new_value && '' !== $value ) {
			return false;
		}

		$updated_attribute = "{$name}=\"{$escaped_new_value}\"";
	}

	/*
	 * > There must never be two or more attributes on
	 * > the same start tag whose names are an ASCII
	 * > case-insensitive match for each other.
	 *     - HTML 5 spec
	 *
	 * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive
	 */
	$comparable_name = strtolower( $name );

	if ( isset( $this->attributes[ $comparable_name ] ) ) {
		/*
		 * Update an existing attribute.
		 *
		 * Example – set attribute id to "new" in <div id="initial_id" />:
		 *
		 *     <div id="initial_id"/>
		 *          ^-------------^
		 *          start         end
		 *     replacement: `id="new"`
		 *
		 *     Result: <div id="new"/>
		 */
		$existing_attribute                        = $this->attributes[ $comparable_name ];
		$this->lexical_updates[ $comparable_name ] = new WP_HTML_Text_Replacement(
			$existing_attribute->start,
			$existing_attribute->length,
			$updated_attribute
		);
	} else {
		/*
		 * Create a new attribute at the tag's name end.
		 *
		 * Example – add attribute id="new" to <div />:
		 *
		 *     <div/>
		 *         ^
		 *         start and end
		 *     replacement: ` id="new"`
		 *
		 *     Result: <div id="new"/>
		 */
		$this->lexical_updates[ $comparable_name ] = new WP_HTML_Text_Replacement(
			$this->tag_name_starts_at + $this->tag_name_length,
			0,
			' ' . $updated_attribute
		);
	}

	/*
	 * Any calls to update the `class` attribute directly should wipe out any
	 * enqueued class changes from `add_class` and `remove_class`.
	 */
	if ( 'class' === $comparable_name && ! empty( $this->classname_updates ) ) {
		$this->classname_updates = array();
	}

	return true;
}