$this->last_query = $query; $this->sought_tag_name = null; $this->sought_class_name = null; $this->sought_match_offset = 1; $this->stop_on_tag_closers = false; // A single string value means "find the tag of this name". if ( is_string( $query ) ) { $this->sought_tag_name = $query; return; } // An empty query parameter applies no restrictions on the search. if ( null === $query ) { return; } // If not using the string interface, an associative array is required. if ( ! is_array( $query ) ) { _doing_it_wrong( __METHOD__, __( 'The query argument must be an array or a tag name.' ), '6.2.0' ); return; } if ( isset( $query['tag_name'] ) && is_string( $query['tag_name'] ) ) { $this->sought_tag_name = $query['tag_name']; } if ( isset( $query['class_name'] ) && is_string( $query['class_name'] ) ) { $this->sought_class_name = $query['class_name']; } if ( isset( $query['match_offset'] ) && is_int( $query['match_offset'] ) && 0 < $query['match_offset'] ) { $this->sought_match_offset = $query['match_offset']; } if ( isset( $query['tag_closers'] ) ) { $this->stop_on_tag_closers = 'visit' === $query['tag_closers']; } } /** * Checks whether a given tag and its attributes match the search criteria. * * @since 6.2.0 * * @return boolean Whether the given tag and its attribute match the search criteria. */ private function matches() { if ( $this->is_closing_tag && ! $this->stop_on_tag_closers ) { return false; } // Does the tag name match the requested tag name in a case-insensitive manner? if ( null !== $this->sought_tag_name ) { /* * String (byte) length lookup is fast. If they aren't the * same length then they can't be the same string values. */ if ( strlen( $this->sought_tag_name ) !== $this->tag_name_length ) { return false; } /* * Check each character to determine if they are the same. * Defer calls to `strtoupper()` to avoid them when possible. * Calling `strcasecmp()` here tested slowed than comparing each * character, so unless benchmarks show otherwise, it should * not be used. * * It's expected that most of the time that this runs, a * lower-case tag name will be supplied and the input will * contain lower-case tag names, thus normally bypassing * the case comparison code. */ for ( $i = 0; $i < $this->tag_name_length; $i++ ) { $html_char = $this->html[ $this->tag_name_starts_at + $i ]; $tag_char = $this->sought_tag_name[ $i ]; if ( $html_char !== $tag_char && strtoupper( $html_char ) !== $tag_char ) { return false; } } } $needs_class_name = null !== $this->sought_class_name; if ( $needs_class_name && ! isset( $this->attributes['class'] ) ) { return false; } /* * Match byte-for-byte (case-sensitive and encoding-form-sensitive) on the class name. * * This will overlook certain classes that exist in other lexical variations * than was supplied to the search query, but requires more complicated searching. */ if ( $needs_class_name ) { $class_start = $this->attributes['class']->value_starts_at; $class_end = $class_start + $this->attributes['class']->value_length; $class_at = $class_start; /* * Ensure that boundaries surround the class name to avoid matching on * substrings of a longer name. For example, the sequence "not-odd" * should not match for the class "odd" even though "odd" is found * within the class attribute text. * * See https://html.spec.whatwg.org/#attributes-3 * See https://html.spec.whatwg.org/#space-separated-tokens */ while ( // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition false !== ( $class_at = strpos( $this->html, $this->sought_class_name, $class_at ) ) && $class_at < $class_end ) { /* * Verify this class starts at a boundary. */ if ( $class_at > $class_start ) { $character = $this->html[ $class_at - 1 ]; if ( ' ' !== $character && "\t" !== $character && "\f" !== $character && "\r" !== $character && "\n" !== $character ) { $class_at += strlen( $this->sought_class_name ); continue; } } /* * Verify this class ends at a boundary as well. */ if ( $class_at + strlen( $this->sought_class_name ) < $class_end ) { $character = $this->html[ $class_at + strlen( $this->sought_class_name ) ]; if ( ' ' !== $character && "\t" !== $character && "\f" !== $character && "\r" !== $character && "\n" !== $character ) { $class_at += strlen( $this->sought_class_name ); continue; } } return true; } return false; } return true; } }