wp/wp-includes/html-api/class-wp-html-open-elements.php
changeset 22 8c2e4d02f4ef
parent 21 48c4eec2b7e6
equal deleted inserted replaced
21:48c4eec2b7e6 22:8c2e4d02f4ef
    56 	 *
    56 	 *
    57 	 * The function will be called with the popped item as its argument.
    57 	 * The function will be called with the popped item as its argument.
    58 	 *
    58 	 *
    59 	 * @since 6.6.0
    59 	 * @since 6.6.0
    60 	 *
    60 	 *
    61 	 * @var Closure
    61 	 * @var Closure|null
    62 	 */
    62 	 */
    63 	private $pop_handler = null;
    63 	private $pop_handler = null;
    64 
    64 
    65 	/**
    65 	/**
    66 	 * A function that will be called when an item is pushed onto the stack of open elements.
    66 	 * A function that will be called when an item is pushed onto the stack of open elements.
    67 	 *
    67 	 *
    68 	 * The function will be called with the pushed item as its argument.
    68 	 * The function will be called with the pushed item as its argument.
    69 	 *
    69 	 *
    70 	 * @since 6.6.0
    70 	 * @since 6.6.0
    71 	 *
    71 	 *
    72 	 * @var Closure
    72 	 * @var Closure|null
    73 	 */
    73 	 */
    74 	private $push_handler = null;
    74 	private $push_handler = null;
    75 
    75 
    76 	/**
    76 	/**
    77 	 * Sets a pop handler that will be called when an item is popped off the stack of
    77 	 * Sets a pop handler that will be called when an item is popped off the stack of
    81 	 *
    81 	 *
    82 	 * @since 6.6.0
    82 	 * @since 6.6.0
    83 	 *
    83 	 *
    84 	 * @param Closure $handler The handler function.
    84 	 * @param Closure $handler The handler function.
    85 	 */
    85 	 */
    86 	public function set_pop_handler( Closure $handler ) {
    86 	public function set_pop_handler( Closure $handler ): void {
    87 		$this->pop_handler = $handler;
    87 		$this->pop_handler = $handler;
    88 	}
    88 	}
    89 
    89 
    90 	/**
    90 	/**
    91 	 * Sets a push handler that will be called when an item is pushed onto the stack of
    91 	 * Sets a push handler that will be called when an item is pushed onto the stack of
    95 	 *
    95 	 *
    96 	 * @since 6.6.0
    96 	 * @since 6.6.0
    97 	 *
    97 	 *
    98 	 * @param Closure $handler The handler function.
    98 	 * @param Closure $handler The handler function.
    99 	 */
    99 	 */
   100 	public function set_push_handler( Closure $handler ) {
   100 	public function set_push_handler( Closure $handler ): void {
   101 		$this->push_handler = $handler;
   101 		$this->push_handler = $handler;
       
   102 	}
       
   103 
       
   104 	/**
       
   105 	 * Returns the name of the node at the nth position on the stack
       
   106 	 * of open elements, or `null` if no such position exists.
       
   107 	 *
       
   108 	 * Note that this uses a 1-based index, which represents the
       
   109 	 * "nth item" on the stack, counting from the top, where the
       
   110 	 * top-most element is the 1st, the second is the 2nd, etc...
       
   111 	 *
       
   112 	 * @since 6.7.0
       
   113 	 *
       
   114 	 * @param int $nth Retrieve the nth item on the stack, with 1 being
       
   115 	 *                 the top element, 2 being the second, etc...
       
   116 	 * @return WP_HTML_Token|null Name of the node on the stack at the given location,
       
   117 	 *                            or `null` if the location isn't on the stack.
       
   118 	 */
       
   119 	public function at( int $nth ): ?WP_HTML_Token {
       
   120 		foreach ( $this->walk_down() as $item ) {
       
   121 			if ( 0 === --$nth ) {
       
   122 				return $item;
       
   123 			}
       
   124 		}
       
   125 
       
   126 		return null;
       
   127 	}
       
   128 
       
   129 	/**
       
   130 	 * Reports if a node of a given name is in the stack of open elements.
       
   131 	 *
       
   132 	 * @since 6.7.0
       
   133 	 *
       
   134 	 * @param string $node_name Name of node for which to check.
       
   135 	 * @return bool Whether a node of the given name is in the stack of open elements.
       
   136 	 */
       
   137 	public function contains( string $node_name ): bool {
       
   138 		foreach ( $this->walk_up() as $item ) {
       
   139 			if ( $node_name === $item->node_name ) {
       
   140 				return true;
       
   141 			}
       
   142 		}
       
   143 
       
   144 		return false;
   102 	}
   145 	}
   103 
   146 
   104 	/**
   147 	/**
   105 	 * Reports if a specific node is in the stack of open elements.
   148 	 * Reports if a specific node is in the stack of open elements.
   106 	 *
   149 	 *
   107 	 * @since 6.4.0
   150 	 * @since 6.4.0
   108 	 *
   151 	 *
   109 	 * @param WP_HTML_Token $token Look for this node in the stack.
   152 	 * @param WP_HTML_Token $token Look for this node in the stack.
   110 	 * @return bool Whether the referenced node is in the stack of open elements.
   153 	 * @return bool Whether the referenced node is in the stack of open elements.
   111 	 */
   154 	 */
   112 	public function contains_node( $token ) {
   155 	public function contains_node( WP_HTML_Token $token ): bool {
   113 		foreach ( $this->walk_up() as $item ) {
   156 		foreach ( $this->walk_up() as $item ) {
   114 			if ( $token->bookmark_name === $item->bookmark_name ) {
   157 			if ( $token === $item ) {
   115 				return true;
   158 				return true;
   116 			}
   159 			}
   117 		}
   160 		}
   118 
   161 
   119 		return false;
   162 		return false;
   124 	 *
   167 	 *
   125 	 * @since 6.4.0
   168 	 * @since 6.4.0
   126 	 *
   169 	 *
   127 	 * @return int How many node are in the stack of open elements.
   170 	 * @return int How many node are in the stack of open elements.
   128 	 */
   171 	 */
   129 	public function count() {
   172 	public function count(): int {
   130 		return count( $this->stack );
   173 		return count( $this->stack );
   131 	}
   174 	}
   132 
   175 
   133 	/**
   176 	/**
   134 	 * Returns the node at the end of the stack of open elements,
   177 	 * Returns the node at the end of the stack of open elements,
   136 	 *
   179 	 *
   137 	 * @since 6.4.0
   180 	 * @since 6.4.0
   138 	 *
   181 	 *
   139 	 * @return WP_HTML_Token|null Last node in the stack of open elements, if one exists, otherwise null.
   182 	 * @return WP_HTML_Token|null Last node in the stack of open elements, if one exists, otherwise null.
   140 	 */
   183 	 */
   141 	public function current_node() {
   184 	public function current_node(): ?WP_HTML_Token {
   142 		$current_node = end( $this->stack );
   185 		$current_node = end( $this->stack );
   143 
   186 
   144 		return $current_node ? $current_node : null;
   187 		return $current_node ? $current_node : null;
   145 	}
   188 	}
   146 
   189 
   147 	/**
   190 	/**
       
   191 	 * Indicates if the current node is of a given type or name.
       
   192 	 *
       
   193 	 * It's possible to pass either a node type or a node name to this function.
       
   194 	 * In the case there is no current element it will always return `false`.
       
   195 	 *
       
   196 	 * Example:
       
   197 	 *
       
   198 	 *     // Is the current node a text node?
       
   199 	 *     $stack->current_node_is( '#text' );
       
   200 	 *
       
   201 	 *     // Is the current node a DIV element?
       
   202 	 *     $stack->current_node_is( 'DIV' );
       
   203 	 *
       
   204 	 *     // Is the current node any element/tag?
       
   205 	 *     $stack->current_node_is( '#tag' );
       
   206 	 *
       
   207 	 * @see WP_HTML_Tag_Processor::get_token_type
       
   208 	 * @see WP_HTML_Tag_Processor::get_token_name
       
   209 	 *
       
   210 	 * @since 6.7.0
       
   211 	 *
       
   212 	 * @access private
       
   213 	 *
       
   214 	 * @param string $identity Check if the current node has this name or type (depending on what is provided).
       
   215 	 * @return bool Whether there is a current element that matches the given identity, whether a token name or type.
       
   216 	 */
       
   217 	public function current_node_is( string $identity ): bool {
       
   218 		$current_node = end( $this->stack );
       
   219 		if ( false === $current_node ) {
       
   220 			return false;
       
   221 		}
       
   222 
       
   223 		$current_node_name = $current_node->node_name;
       
   224 
       
   225 		return (
       
   226 			$current_node_name === $identity ||
       
   227 			( '#doctype' === $identity && 'html' === $current_node_name ) ||
       
   228 			( '#tag' === $identity && ctype_upper( $current_node_name ) )
       
   229 		);
       
   230 	}
       
   231 
       
   232 	/**
   148 	 * Returns whether an element is in a specific scope.
   233 	 * Returns whether an element is in a specific scope.
   149 	 *
       
   150 	 * ## HTML Support
       
   151 	 *
       
   152 	 * This function skips checking for the termination list because there
       
   153 	 * are no supported elements which appear in the termination list.
       
   154 	 *
   234 	 *
   155 	 * @since 6.4.0
   235 	 * @since 6.4.0
   156 	 *
   236 	 *
   157 	 * @see https://html.spec.whatwg.org/#has-an-element-in-the-specific-scope
   237 	 * @see https://html.spec.whatwg.org/#has-an-element-in-the-specific-scope
   158 	 *
   238 	 *
   159 	 * @param string   $tag_name         Name of tag check.
   239 	 * @param string   $tag_name         Name of tag check.
   160 	 * @param string[] $termination_list List of elements that terminate the search.
   240 	 * @param string[] $termination_list List of elements that terminate the search.
   161 	 * @return bool Whether the element was found in a specific scope.
   241 	 * @return bool Whether the element was found in a specific scope.
   162 	 */
   242 	 */
   163 	public function has_element_in_specific_scope( $tag_name, $termination_list ) {
   243 	public function has_element_in_specific_scope( string $tag_name, $termination_list ): bool {
       
   244 		foreach ( $this->walk_up() as $node ) {
       
   245 			$namespaced_name = 'html' === $node->namespace
       
   246 				? $node->node_name
       
   247 				: "{$node->namespace} {$node->node_name}";
       
   248 
       
   249 			if ( $namespaced_name === $tag_name ) {
       
   250 				return true;
       
   251 			}
       
   252 
       
   253 			if (
       
   254 				'(internal: H1 through H6 - do not use)' === $tag_name &&
       
   255 				in_array( $namespaced_name, array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), true )
       
   256 			) {
       
   257 				return true;
       
   258 			}
       
   259 
       
   260 			if ( in_array( $namespaced_name, $termination_list, true ) ) {
       
   261 				return false;
       
   262 			}
       
   263 		}
       
   264 
       
   265 		return false;
       
   266 	}
       
   267 
       
   268 	/**
       
   269 	 * Returns whether a particular element is in scope.
       
   270 	 *
       
   271 	 * > The stack of open elements is said to have a particular element in
       
   272 	 * > scope when it has that element in the specific scope consisting of
       
   273 	 * > the following element types:
       
   274 	 * >
       
   275 	 * >   - applet
       
   276 	 * >   - caption
       
   277 	 * >   - html
       
   278 	 * >   - table
       
   279 	 * >   - td
       
   280 	 * >   - th
       
   281 	 * >   - marquee
       
   282 	 * >   - object
       
   283 	 * >   - template
       
   284 	 * >   - MathML mi
       
   285 	 * >   - MathML mo
       
   286 	 * >   - MathML mn
       
   287 	 * >   - MathML ms
       
   288 	 * >   - MathML mtext
       
   289 	 * >   - MathML annotation-xml
       
   290 	 * >   - SVG foreignObject
       
   291 	 * >   - SVG desc
       
   292 	 * >   - SVG title
       
   293 	 *
       
   294 	 * @since 6.4.0
       
   295 	 * @since 6.7.0 Full support.
       
   296 	 *
       
   297 	 * @see https://html.spec.whatwg.org/#has-an-element-in-scope
       
   298 	 *
       
   299 	 * @param string $tag_name Name of tag to check.
       
   300 	 * @return bool Whether given element is in scope.
       
   301 	 */
       
   302 	public function has_element_in_scope( string $tag_name ): bool {
       
   303 		return $this->has_element_in_specific_scope(
       
   304 			$tag_name,
       
   305 			array(
       
   306 				'APPLET',
       
   307 				'CAPTION',
       
   308 				'HTML',
       
   309 				'TABLE',
       
   310 				'TD',
       
   311 				'TH',
       
   312 				'MARQUEE',
       
   313 				'OBJECT',
       
   314 				'TEMPLATE',
       
   315 
       
   316 				'math MI',
       
   317 				'math MO',
       
   318 				'math MN',
       
   319 				'math MS',
       
   320 				'math MTEXT',
       
   321 				'math ANNOTATION-XML',
       
   322 
       
   323 				'svg FOREIGNOBJECT',
       
   324 				'svg DESC',
       
   325 				'svg TITLE',
       
   326 			)
       
   327 		);
       
   328 	}
       
   329 
       
   330 	/**
       
   331 	 * Returns whether a particular element is in list item scope.
       
   332 	 *
       
   333 	 * > The stack of open elements is said to have a particular element
       
   334 	 * > in list item scope when it has that element in the specific scope
       
   335 	 * > consisting of the following element types:
       
   336 	 * >
       
   337 	 * >   - All the element types listed above for the has an element in scope algorithm.
       
   338 	 * >   - ol in the HTML namespace
       
   339 	 * >   - ul in the HTML namespace
       
   340 	 *
       
   341 	 * @since 6.4.0
       
   342 	 * @since 6.5.0 Implemented: no longer throws on every invocation.
       
   343 	 * @since 6.7.0 Supports all required HTML elements.
       
   344 	 *
       
   345 	 * @see https://html.spec.whatwg.org/#has-an-element-in-list-item-scope
       
   346 	 *
       
   347 	 * @param string $tag_name Name of tag to check.
       
   348 	 * @return bool Whether given element is in scope.
       
   349 	 */
       
   350 	public function has_element_in_list_item_scope( string $tag_name ): bool {
       
   351 		return $this->has_element_in_specific_scope(
       
   352 			$tag_name,
       
   353 			array(
       
   354 				'APPLET',
       
   355 				'BUTTON',
       
   356 				'CAPTION',
       
   357 				'HTML',
       
   358 				'TABLE',
       
   359 				'TD',
       
   360 				'TH',
       
   361 				'MARQUEE',
       
   362 				'OBJECT',
       
   363 				'OL',
       
   364 				'TEMPLATE',
       
   365 				'UL',
       
   366 
       
   367 				'math MI',
       
   368 				'math MO',
       
   369 				'math MN',
       
   370 				'math MS',
       
   371 				'math MTEXT',
       
   372 				'math ANNOTATION-XML',
       
   373 
       
   374 				'svg FOREIGNOBJECT',
       
   375 				'svg DESC',
       
   376 				'svg TITLE',
       
   377 			)
       
   378 		);
       
   379 	}
       
   380 
       
   381 	/**
       
   382 	 * Returns whether a particular element is in button scope.
       
   383 	 *
       
   384 	 * > The stack of open elements is said to have a particular element
       
   385 	 * > in button scope when it has that element in the specific scope
       
   386 	 * > consisting of the following element types:
       
   387 	 * >
       
   388 	 * >   - All the element types listed above for the has an element in scope algorithm.
       
   389 	 * >   - button in the HTML namespace
       
   390 	 *
       
   391 	 * @since 6.4.0
       
   392 	 * @since 6.7.0 Supports all required HTML elements.
       
   393 	 *
       
   394 	 * @see https://html.spec.whatwg.org/#has-an-element-in-button-scope
       
   395 	 *
       
   396 	 * @param string $tag_name Name of tag to check.
       
   397 	 * @return bool Whether given element is in scope.
       
   398 	 */
       
   399 	public function has_element_in_button_scope( string $tag_name ): bool {
       
   400 		return $this->has_element_in_specific_scope(
       
   401 			$tag_name,
       
   402 			array(
       
   403 				'APPLET',
       
   404 				'BUTTON',
       
   405 				'CAPTION',
       
   406 				'HTML',
       
   407 				'TABLE',
       
   408 				'TD',
       
   409 				'TH',
       
   410 				'MARQUEE',
       
   411 				'OBJECT',
       
   412 				'TEMPLATE',
       
   413 
       
   414 				'math MI',
       
   415 				'math MO',
       
   416 				'math MN',
       
   417 				'math MS',
       
   418 				'math MTEXT',
       
   419 				'math ANNOTATION-XML',
       
   420 
       
   421 				'svg FOREIGNOBJECT',
       
   422 				'svg DESC',
       
   423 				'svg TITLE',
       
   424 			)
       
   425 		);
       
   426 	}
       
   427 
       
   428 	/**
       
   429 	 * Returns whether a particular element is in table scope.
       
   430 	 *
       
   431 	 * > The stack of open elements is said to have a particular element
       
   432 	 * > in table scope when it has that element in the specific scope
       
   433 	 * > consisting of the following element types:
       
   434 	 * >
       
   435 	 * >   - html in the HTML namespace
       
   436 	 * >   - table in the HTML namespace
       
   437 	 * >   - template in the HTML namespace
       
   438 	 *
       
   439 	 * @since 6.4.0
       
   440 	 * @since 6.7.0 Full implementation.
       
   441 	 *
       
   442 	 * @see https://html.spec.whatwg.org/#has-an-element-in-table-scope
       
   443 	 *
       
   444 	 * @param string $tag_name Name of tag to check.
       
   445 	 * @return bool Whether given element is in scope.
       
   446 	 */
       
   447 	public function has_element_in_table_scope( string $tag_name ): bool {
       
   448 		return $this->has_element_in_specific_scope(
       
   449 			$tag_name,
       
   450 			array(
       
   451 				'HTML',
       
   452 				'TABLE',
       
   453 				'TEMPLATE',
       
   454 			)
       
   455 		);
       
   456 	}
       
   457 
       
   458 	/**
       
   459 	 * Returns whether a particular element is in select scope.
       
   460 	 *
       
   461 	 * This test differs from the others like it, in that its rules are inverted.
       
   462 	 * Instead of arriving at a match when one of any tag in a termination group
       
   463 	 * is reached, this one terminates if any other tag is reached.
       
   464 	 *
       
   465 	 * > The stack of open elements is said to have a particular element in select scope when it has
       
   466 	 * > that element in the specific scope consisting of all element types except the following:
       
   467 	 * >   - optgroup in the HTML namespace
       
   468 	 * >   - option in the HTML namespace
       
   469 	 *
       
   470 	 * @since 6.4.0 Stub implementation (throws).
       
   471 	 * @since 6.7.0 Full implementation.
       
   472 	 *
       
   473 	 * @see https://html.spec.whatwg.org/#has-an-element-in-select-scope
       
   474 	 *
       
   475 	 * @param string $tag_name Name of tag to check.
       
   476 	 * @return bool Whether the given element is in SELECT scope.
       
   477 	 */
       
   478 	public function has_element_in_select_scope( string $tag_name ): bool {
   164 		foreach ( $this->walk_up() as $node ) {
   479 		foreach ( $this->walk_up() as $node ) {
   165 			if ( $node->node_name === $tag_name ) {
   480 			if ( $node->node_name === $tag_name ) {
   166 				return true;
   481 				return true;
   167 			}
   482 			}
   168 
   483 
   169 			if (
   484 			if (
   170 				'(internal: H1 through H6 - do not use)' === $tag_name &&
   485 				'OPTION' !== $node->node_name &&
   171 				in_array( $node->node_name, array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), true )
   486 				'OPTGROUP' !== $node->node_name
   172 			) {
   487 			) {
   173 				return true;
       
   174 			}
       
   175 
       
   176 			switch ( $node->node_name ) {
       
   177 				case 'HTML':
       
   178 					return false;
       
   179 			}
       
   180 
       
   181 			if ( in_array( $node->node_name, $termination_list, true ) ) {
       
   182 				return false;
   488 				return false;
   183 			}
   489 			}
   184 		}
   490 		}
   185 
   491 
   186 		return false;
   492 		return false;
   187 	}
   493 	}
   188 
   494 
   189 	/**
   495 	/**
   190 	 * Returns whether a particular element is in scope.
   496 	 * Returns whether a P is in BUTTON scope.
   191 	 *
       
   192 	 * @since 6.4.0
       
   193 	 *
       
   194 	 * @see https://html.spec.whatwg.org/#has-an-element-in-scope
       
   195 	 *
       
   196 	 * @param string $tag_name Name of tag to check.
       
   197 	 * @return bool Whether given element is in scope.
       
   198 	 */
       
   199 	public function has_element_in_scope( $tag_name ) {
       
   200 		return $this->has_element_in_specific_scope(
       
   201 			$tag_name,
       
   202 			array(
       
   203 
       
   204 				/*
       
   205 				 * Because it's not currently possible to encounter
       
   206 				 * one of the termination elements, they don't need
       
   207 				 * to be listed here. If they were, they would be
       
   208 				 * unreachable and only waste CPU cycles while
       
   209 				 * scanning through HTML.
       
   210 				 */
       
   211 			)
       
   212 		);
       
   213 	}
       
   214 
       
   215 	/**
       
   216 	 * Returns whether a particular element is in list item scope.
       
   217 	 *
       
   218 	 * @since 6.4.0
       
   219 	 * @since 6.5.0 Implemented: no longer throws on every invocation.
       
   220 	 *
       
   221 	 * @see https://html.spec.whatwg.org/#has-an-element-in-list-item-scope
       
   222 	 *
       
   223 	 * @param string $tag_name Name of tag to check.
       
   224 	 * @return bool Whether given element is in scope.
       
   225 	 */
       
   226 	public function has_element_in_list_item_scope( $tag_name ) {
       
   227 		return $this->has_element_in_specific_scope(
       
   228 			$tag_name,
       
   229 			array(
       
   230 				// There are more elements that belong here which aren't currently supported.
       
   231 				'OL',
       
   232 				'UL',
       
   233 			)
       
   234 		);
       
   235 	}
       
   236 
       
   237 	/**
       
   238 	 * Returns whether a particular element is in button scope.
       
   239 	 *
   497 	 *
   240 	 * @since 6.4.0
   498 	 * @since 6.4.0
   241 	 *
   499 	 *
   242 	 * @see https://html.spec.whatwg.org/#has-an-element-in-button-scope
   500 	 * @see https://html.spec.whatwg.org/#has-an-element-in-button-scope
   243 	 *
   501 	 *
   244 	 * @param string $tag_name Name of tag to check.
       
   245 	 * @return bool Whether given element is in scope.
       
   246 	 */
       
   247 	public function has_element_in_button_scope( $tag_name ) {
       
   248 		return $this->has_element_in_specific_scope( $tag_name, array( 'BUTTON' ) );
       
   249 	}
       
   250 
       
   251 	/**
       
   252 	 * Returns whether a particular element is in table scope.
       
   253 	 *
       
   254 	 * @since 6.4.0
       
   255 	 *
       
   256 	 * @see https://html.spec.whatwg.org/#has-an-element-in-table-scope
       
   257 	 *
       
   258 	 * @throws WP_HTML_Unsupported_Exception Always until this function is implemented.
       
   259 	 *
       
   260 	 * @param string $tag_name Name of tag to check.
       
   261 	 * @return bool Whether given element is in scope.
       
   262 	 */
       
   263 	public function has_element_in_table_scope( $tag_name ) {
       
   264 		throw new WP_HTML_Unsupported_Exception( 'Cannot process elements depending on table scope.' );
       
   265 
       
   266 		return false; // The linter requires this unreachable code until the function is implemented and can return.
       
   267 	}
       
   268 
       
   269 	/**
       
   270 	 * Returns whether a particular element is in select scope.
       
   271 	 *
       
   272 	 * @since 6.4.0
       
   273 	 *
       
   274 	 * @see https://html.spec.whatwg.org/#has-an-element-in-select-scope
       
   275 	 *
       
   276 	 * @throws WP_HTML_Unsupported_Exception Always until this function is implemented.
       
   277 	 *
       
   278 	 * @param string $tag_name Name of tag to check.
       
   279 	 * @return bool Whether given element is in scope.
       
   280 	 */
       
   281 	public function has_element_in_select_scope( $tag_name ) {
       
   282 		throw new WP_HTML_Unsupported_Exception( 'Cannot process elements depending on select scope.' );
       
   283 
       
   284 		return false; // The linter requires this unreachable code until the function is implemented and can return.
       
   285 	}
       
   286 
       
   287 	/**
       
   288 	 * Returns whether a P is in BUTTON scope.
       
   289 	 *
       
   290 	 * @since 6.4.0
       
   291 	 *
       
   292 	 * @see https://html.spec.whatwg.org/#has-an-element-in-button-scope
       
   293 	 *
       
   294 	 * @return bool Whether a P is in BUTTON scope.
   502 	 * @return bool Whether a P is in BUTTON scope.
   295 	 */
   503 	 */
   296 	public function has_p_in_button_scope() {
   504 	public function has_p_in_button_scope(): bool {
   297 		return $this->has_p_in_button_scope;
   505 		return $this->has_p_in_button_scope;
   298 	}
   506 	}
   299 
   507 
   300 	/**
   508 	/**
   301 	 * Pops a node off of the stack of open elements.
   509 	 * Pops a node off of the stack of open elements.
   304 	 *
   512 	 *
   305 	 * @see https://html.spec.whatwg.org/#stack-of-open-elements
   513 	 * @see https://html.spec.whatwg.org/#stack-of-open-elements
   306 	 *
   514 	 *
   307 	 * @return bool Whether a node was popped off of the stack.
   515 	 * @return bool Whether a node was popped off of the stack.
   308 	 */
   516 	 */
   309 	public function pop() {
   517 	public function pop(): bool {
   310 		$item = array_pop( $this->stack );
   518 		$item = array_pop( $this->stack );
   311 		if ( null === $item ) {
   519 		if ( null === $item ) {
   312 			return false;
   520 			return false;
   313 		}
   521 		}
   314 
   522 
   315 		if ( 'context-node' === $item->bookmark_name ) {
       
   316 			$this->stack[] = $item;
       
   317 			return false;
       
   318 		}
       
   319 
       
   320 		$this->after_element_pop( $item );
   523 		$this->after_element_pop( $item );
   321 		return true;
   524 		return true;
   322 	}
   525 	}
   323 
   526 
   324 	/**
   527 	/**
   325 	 * Pops nodes off of the stack of open elements until one with the given tag name has been popped.
   528 	 * Pops nodes off of the stack of open elements until an HTML tag with the given name has been popped.
   326 	 *
   529 	 *
   327 	 * @since 6.4.0
   530 	 * @since 6.4.0
   328 	 *
   531 	 *
   329 	 * @see WP_HTML_Open_Elements::pop
   532 	 * @see WP_HTML_Open_Elements::pop
   330 	 *
   533 	 *
   331 	 * @param string $tag_name Name of tag that needs to be popped off of the stack of open elements.
   534 	 * @param string $html_tag_name Name of tag that needs to be popped off of the stack of open elements.
   332 	 * @return bool Whether a tag of the given name was found and popped off of the stack of open elements.
   535 	 * @return bool Whether a tag of the given name was found and popped off of the stack of open elements.
   333 	 */
   536 	 */
   334 	public function pop_until( $tag_name ) {
   537 	public function pop_until( string $html_tag_name ): bool {
   335 		foreach ( $this->walk_up() as $item ) {
   538 		foreach ( $this->walk_up() as $item ) {
   336 			if ( 'context-node' === $item->bookmark_name ) {
       
   337 				return true;
       
   338 			}
       
   339 
       
   340 			$this->pop();
   539 			$this->pop();
   341 
   540 
       
   541 			if ( 'html' !== $item->namespace ) {
       
   542 				continue;
       
   543 			}
       
   544 
   342 			if (
   545 			if (
   343 				'(internal: H1 through H6 - do not use)' === $tag_name &&
   546 				'(internal: H1 through H6 - do not use)' === $html_tag_name &&
   344 				in_array( $item->node_name, array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), true )
   547 				in_array( $item->node_name, array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), true )
   345 			) {
   548 			) {
   346 				return true;
   549 				return true;
   347 			}
   550 			}
   348 
   551 
   349 			if ( $tag_name === $item->node_name ) {
   552 			if ( $html_tag_name === $item->node_name ) {
   350 				return true;
   553 				return true;
   351 			}
   554 			}
   352 		}
   555 		}
   353 
   556 
   354 		return false;
   557 		return false;
   361 	 *
   564 	 *
   362 	 * @see https://html.spec.whatwg.org/#stack-of-open-elements
   565 	 * @see https://html.spec.whatwg.org/#stack-of-open-elements
   363 	 *
   566 	 *
   364 	 * @param WP_HTML_Token $stack_item Item to add onto stack.
   567 	 * @param WP_HTML_Token $stack_item Item to add onto stack.
   365 	 */
   568 	 */
   366 	public function push( $stack_item ) {
   569 	public function push( WP_HTML_Token $stack_item ): void {
   367 		$this->stack[] = $stack_item;
   570 		$this->stack[] = $stack_item;
   368 		$this->after_element_push( $stack_item );
   571 		$this->after_element_push( $stack_item );
   369 	}
   572 	}
   370 
   573 
   371 	/**
   574 	/**
   374 	 * @since 6.4.0
   577 	 * @since 6.4.0
   375 	 *
   578 	 *
   376 	 * @param WP_HTML_Token $token The node to remove from the stack of open elements.
   579 	 * @param WP_HTML_Token $token The node to remove from the stack of open elements.
   377 	 * @return bool Whether the node was found and removed from the stack of open elements.
   580 	 * @return bool Whether the node was found and removed from the stack of open elements.
   378 	 */
   581 	 */
   379 	public function remove_node( $token ) {
   582 	public function remove_node( WP_HTML_Token $token ): bool {
   380 		if ( 'context-node' === $token->bookmark_name ) {
       
   381 			return false;
       
   382 		}
       
   383 
       
   384 		foreach ( $this->walk_up() as $position_from_end => $item ) {
   583 		foreach ( $this->walk_up() as $position_from_end => $item ) {
   385 			if ( $token->bookmark_name !== $item->bookmark_name ) {
   584 			if ( $token->bookmark_name !== $item->bookmark_name ) {
   386 				continue;
   585 				continue;
   387 			}
   586 			}
   388 
   587 
   441 	 * see WP_HTML_Open_Elements::walk_down().
   640 	 * see WP_HTML_Open_Elements::walk_down().
   442 	 *
   641 	 *
   443 	 * @since 6.4.0
   642 	 * @since 6.4.0
   444 	 * @since 6.5.0 Accepts $above_this_node to start traversal above a given node, if it exists.
   643 	 * @since 6.5.0 Accepts $above_this_node to start traversal above a given node, if it exists.
   445 	 *
   644 	 *
   446 	 * @param ?WP_HTML_Token $above_this_node Start traversing above this node, if provided and if the node exists.
   645 	 * @param WP_HTML_Token|null $above_this_node Optional. Start traversing above this node,
   447 	 */
   646 	 *                                            if provided and if the node exists.
   448 	public function walk_up( $above_this_node = null ) {
   647 	 */
       
   648 	public function walk_up( ?WP_HTML_Token $above_this_node = null ) {
   449 		$has_found_node = null === $above_this_node;
   649 		$has_found_node = null === $above_this_node;
   450 
   650 
   451 		for ( $i = count( $this->stack ) - 1; $i >= 0; $i-- ) {
   651 		for ( $i = count( $this->stack ) - 1; $i >= 0; $i-- ) {
   452 			$node = $this->stack[ $i ];
   652 			$node = $this->stack[ $i ];
   453 
   653 
   475 	 *
   675 	 *
   476 	 * @since 6.4.0
   676 	 * @since 6.4.0
   477 	 *
   677 	 *
   478 	 * @param WP_HTML_Token $item Element that was added to the stack of open elements.
   678 	 * @param WP_HTML_Token $item Element that was added to the stack of open elements.
   479 	 */
   679 	 */
   480 	public function after_element_push( $item ) {
   680 	public function after_element_push( WP_HTML_Token $item ): void {
       
   681 		$namespaced_name = 'html' === $item->namespace
       
   682 			? $item->node_name
       
   683 			: "{$item->namespace} {$item->node_name}";
       
   684 
       
   685 		/*
       
   686 		 * When adding support for new elements, expand this switch to trap
       
   687 		 * cases where the precalculated value needs to change.
       
   688 		 */
       
   689 		switch ( $namespaced_name ) {
       
   690 			case 'APPLET':
       
   691 			case 'BUTTON':
       
   692 			case 'CAPTION':
       
   693 			case 'HTML':
       
   694 			case 'TABLE':
       
   695 			case 'TD':
       
   696 			case 'TH':
       
   697 			case 'MARQUEE':
       
   698 			case 'OBJECT':
       
   699 			case 'TEMPLATE':
       
   700 			case 'math MI':
       
   701 			case 'math MO':
       
   702 			case 'math MN':
       
   703 			case 'math MS':
       
   704 			case 'math MTEXT':
       
   705 			case 'math ANNOTATION-XML':
       
   706 			case 'svg FOREIGNOBJECT':
       
   707 			case 'svg DESC':
       
   708 			case 'svg TITLE':
       
   709 				$this->has_p_in_button_scope = false;
       
   710 				break;
       
   711 
       
   712 			case 'P':
       
   713 				$this->has_p_in_button_scope = true;
       
   714 				break;
       
   715 		}
       
   716 
       
   717 		if ( null !== $this->push_handler ) {
       
   718 			( $this->push_handler )( $item );
       
   719 		}
       
   720 	}
       
   721 
       
   722 	/**
       
   723 	 * Updates internal flags after removing an element.
       
   724 	 *
       
   725 	 * Certain conditions (such as "has_p_in_button_scope") are maintained here as
       
   726 	 * flags that are only modified when adding and removing elements. This allows
       
   727 	 * the HTML Processor to quickly check for these conditions instead of iterating
       
   728 	 * over the open stack elements upon each new tag it encounters. These flags,
       
   729 	 * however, need to be maintained as items are added and removed from the stack.
       
   730 	 *
       
   731 	 * @since 6.4.0
       
   732 	 *
       
   733 	 * @param WP_HTML_Token $item Element that was removed from the stack of open elements.
       
   734 	 */
       
   735 	public function after_element_pop( WP_HTML_Token $item ): void {
   481 		/*
   736 		/*
   482 		 * When adding support for new elements, expand this switch to trap
   737 		 * When adding support for new elements, expand this switch to trap
   483 		 * cases where the precalculated value needs to change.
   738 		 * cases where the precalculated value needs to change.
   484 		 */
   739 		 */
   485 		switch ( $item->node_name ) {
   740 		switch ( $item->node_name ) {
       
   741 			case 'APPLET':
   486 			case 'BUTTON':
   742 			case 'BUTTON':
   487 				$this->has_p_in_button_scope = false;
   743 			case 'CAPTION':
   488 				break;
   744 			case 'HTML':
   489 
       
   490 			case 'P':
   745 			case 'P':
   491 				$this->has_p_in_button_scope = true;
   746 			case 'TABLE':
   492 				break;
   747 			case 'TD':
   493 		}
   748 			case 'TH':
   494 
   749 			case 'MARQUEE':
   495 		if ( null !== $this->push_handler ) {
   750 			case 'OBJECT':
   496 			( $this->push_handler )( $item );
   751 			case 'TEMPLATE':
   497 		}
   752 			case 'math MI':
   498 	}
   753 			case 'math MO':
   499 
   754 			case 'math MN':
   500 	/**
   755 			case 'math MS':
   501 	 * Updates internal flags after removing an element.
   756 			case 'math MTEXT':
   502 	 *
   757 			case 'math ANNOTATION-XML':
   503 	 * Certain conditions (such as "has_p_in_button_scope") are maintained here as
   758 			case 'svg FOREIGNOBJECT':
   504 	 * flags that are only modified when adding and removing elements. This allows
   759 			case 'svg DESC':
   505 	 * the HTML Processor to quickly check for these conditions instead of iterating
   760 			case 'svg TITLE':
   506 	 * over the open stack elements upon each new tag it encounters. These flags,
       
   507 	 * however, need to be maintained as items are added and removed from the stack.
       
   508 	 *
       
   509 	 * @since 6.4.0
       
   510 	 *
       
   511 	 * @param WP_HTML_Token $item Element that was removed from the stack of open elements.
       
   512 	 */
       
   513 	public function after_element_pop( $item ) {
       
   514 		/*
       
   515 		 * When adding support for new elements, expand this switch to trap
       
   516 		 * cases where the precalculated value needs to change.
       
   517 		 */
       
   518 		switch ( $item->node_name ) {
       
   519 			case 'BUTTON':
       
   520 				$this->has_p_in_button_scope = $this->has_element_in_button_scope( 'P' );
   761 				$this->has_p_in_button_scope = $this->has_element_in_button_scope( 'P' );
   521 				break;
   762 				break;
   522 
       
   523 			case 'P':
       
   524 				$this->has_p_in_button_scope = $this->has_element_in_button_scope( 'P' );
       
   525 				break;
       
   526 		}
   763 		}
   527 
   764 
   528 		if ( null !== $this->pop_handler ) {
   765 		if ( null !== $this->pop_handler ) {
   529 			( $this->pop_handler )( $item );
   766 			( $this->pop_handler )( $item );
   530 		}
   767 		}
   531 	}
   768 	}
   532 
   769 
   533 	/**
   770 	/**
       
   771 	 * Clear the stack back to a table context.
       
   772 	 *
       
   773 	 * > When the steps above require the UA to clear the stack back to a table context, it means
       
   774 	 * > that the UA must, while the current node is not a table, template, or html element, pop
       
   775 	 * > elements from the stack of open elements.
       
   776 	 *
       
   777 	 * @see https://html.spec.whatwg.org/multipage/parsing.html#clear-the-stack-back-to-a-table-context
       
   778 	 *
       
   779 	 * @since 6.7.0
       
   780 	 */
       
   781 	public function clear_to_table_context(): void {
       
   782 		foreach ( $this->walk_up() as $item ) {
       
   783 			if (
       
   784 				'TABLE' === $item->node_name ||
       
   785 				'TEMPLATE' === $item->node_name ||
       
   786 				'HTML' === $item->node_name
       
   787 			) {
       
   788 				break;
       
   789 			}
       
   790 			$this->pop();
       
   791 		}
       
   792 	}
       
   793 
       
   794 	/**
       
   795 	 * Clear the stack back to a table body context.
       
   796 	 *
       
   797 	 * > When the steps above require the UA to clear the stack back to a table body context, it
       
   798 	 * > means that the UA must, while the current node is not a tbody, tfoot, thead, template, or
       
   799 	 * > html element, pop elements from the stack of open elements.
       
   800 	 *
       
   801 	 * @see https://html.spec.whatwg.org/multipage/parsing.html#clear-the-stack-back-to-a-table-body-context
       
   802 	 *
       
   803 	 * @since 6.7.0
       
   804 	 */
       
   805 	public function clear_to_table_body_context(): void {
       
   806 		foreach ( $this->walk_up() as $item ) {
       
   807 			if (
       
   808 				'TBODY' === $item->node_name ||
       
   809 				'TFOOT' === $item->node_name ||
       
   810 				'THEAD' === $item->node_name ||
       
   811 				'TEMPLATE' === $item->node_name ||
       
   812 				'HTML' === $item->node_name
       
   813 			) {
       
   814 				break;
       
   815 			}
       
   816 			$this->pop();
       
   817 		}
       
   818 	}
       
   819 
       
   820 	/**
       
   821 	 * Clear the stack back to a table row context.
       
   822 	 *
       
   823 	 * > When the steps above require the UA to clear the stack back to a table row context, it
       
   824 	 * > means that the UA must, while the current node is not a tr, template, or html element, pop
       
   825 	 * > elements from the stack of open elements.
       
   826 	 *
       
   827 	 * @see https://html.spec.whatwg.org/multipage/parsing.html#clear-the-stack-back-to-a-table-row-context
       
   828 	 *
       
   829 	 * @since 6.7.0
       
   830 	 */
       
   831 	public function clear_to_table_row_context(): void {
       
   832 		foreach ( $this->walk_up() as $item ) {
       
   833 			if (
       
   834 				'TR' === $item->node_name ||
       
   835 				'TEMPLATE' === $item->node_name ||
       
   836 				'HTML' === $item->node_name
       
   837 			) {
       
   838 				break;
       
   839 			}
       
   840 			$this->pop();
       
   841 		}
       
   842 	}
       
   843 
       
   844 	/**
   534 	 * Wakeup magic method.
   845 	 * Wakeup magic method.
   535 	 *
   846 	 *
   536 	 * @since 6.6.0
   847 	 * @since 6.6.0
   537 	 */
   848 	 */
   538 	public function __wakeup() {
   849 	public function __wakeup() {