WP_HTML_Tag_Processor::set_attribute( string $name, string|bool $value ): bool

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

Description

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.

Parameters

$namestringrequired
The attribute name to target.
$valuestring|boolrequired
The new attribute value.

Return

bool Whether an attribute value was set.

Source

public function set_attribute( $name, $value ) {
	if ( $this->is_closing_tag || null === $this->tag_name_starts_at ) {
		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 {
		$escaped_new_value = esc_attr( $value );
		$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->end,
			$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,
			$this->tag_name_starts_at + $this->tag_name_length,
			' ' . $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;
}

Changelog

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

User Contributed Notes

You must log in before being able to contribute a note or feedback.