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

This function handles all necessary HTML encoding. Provide normal, unescaped string values.
The HTML API will encode the strings appropriately so that the browser will interpret them as the intended value.

Example:

// Renders “Eggs & Milk” in a browser, encoded as `<abbr title="Eggs &amp; Milk">`.
$processor->set_attribute( 'title', 'Eggs & Milk' );

// Renders “Eggs &amp; Milk” in a browser, encoded as `<abbr title="Eggs &amp;amp; Milk">`.
$processor->set_attribute( 'title', 'Eggs &amp; Milk' );

// Renders `true` as `<abbr title>`.
$processor->set_attribute( 'title', true );

// Renders without the attribute for `false` as `<abbr>`.
$processor->set_attribute( 'title', false );

Special handling is provided for boolean attribute values:

  • 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.

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 ): bool {
	if (
		self::STATE_MATCHED_TAG !== $this->parser_state ||
		$this->is_closing_tag
	) {
		return false;
	}

	$name_length = strlen( $name );

	/**
	 * 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 following
	 * are not allowed to be set as part of an HTML attribute name:
	 *
	 *  - greater-than “>”
	 *  - ampersand “&”
	 *
	 * @see https://html.spec.whatwg.org/#attributes-2
	 */
	if (
		0 === $name_length ||
		// Syntax-like characters.
		strcspn( $name, '"\'>&</ =' ) !== $name_length ||
		// Control characters.
		strcspn(
			$name,
			"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F" .
			"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
		) !== $name_length ||
		// Unicode noncharacters.
		wp_has_noncharacters( $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 attribute values appropriately.
		 *
		 * @see https://html.spec.whatwg.org/#attributes-3
		 */
		$escaped_new_value = in_array( $comparable_name, wp_kses_uri_attributes(), true )
			? esc_url( $value )
			: strtr(
				$value,
				array(
					'<' => '&lt;',
					'>' => '&gt;',
					'&' => '&amp;',
					'"' => '&quot;',
					"'" => '&apos;',
				)
			);

		// 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;
}

Changelog

VersionDescription
6.9.0Escapes all character references instead of trying to avoid double-escaping.
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.