elements. * @param string[] $descriptions Contents of meta name="description" tags. * @param string[] $canonicals Absolute hrefs of link rel="canonical" tags. * @param string[] $robots Lower-case contents of meta name="robots" tags. * @param array $open_graph Open Graph tags: property (e.g. "og:title") => contents. * @param array $twitter Twitter Card tags: name (e.g. "twitter:card") => contents. * @param Heading[] $headings Headings in document order. * @param string[] $json_ld Raw contents of application/ld+json scripts. * @param string $header_robots Lower-case X-Robots-Tag response header, empty if absent. * @param int[] $body_sketch TextSketch of the main text, empty for short pages. * @param array $a11y Accessibility problems: kind, text, source type, source reference. * @param array $content Content facts: kind, text, source type, source reference. * @param array $perf Performance facts: kind, text, source type, source reference. * @phpstan-param array $open_graph * @phpstan-param array $twitter * @phpstan-param array $a11y * @phpstan-param array $content * @phpstan-param array $perf */ public function __construct( public readonly int $title_count = 0, public readonly array $descriptions = array(), public readonly array $canonicals = array(), public readonly array $robots = array(), public readonly array $open_graph = array(), public readonly array $twitter = array(), public readonly array $headings = array(), public readonly array $json_ld = array(), public readonly string $header_robots = '', public readonly array $body_sketch = array(), public readonly array $a11y = array(), public readonly array $content = array(), public readonly array $perf = array() ) {} /** * Headings of one level. * * @param int $level 1 to 6. * @return Heading[] */ public function headings_of( int $level ): array { return array_values( array_filter( $this->headings, static fn( Heading $h ): bool => $h->level === $level ) ); } /** * Whether the robots meta tag or the X-Robots-Tag header says noindex. */ public function noindex(): bool { foreach ( array_merge( $this->robots, array( $this->header_robots ) ) as $value ) { if ( str_contains( strtolower( $value ), 'noindex' ) ) { return true; } } return false; } /** * Plain arrays for the job state. * * @return array */ public function to_state(): array { return array( 'tc' => $this->title_count, 'd' => $this->descriptions, 'c' => $this->canonicals, 'r' => $this->robots, 'og' => $this->open_graph, 'tw' => $this->twitter, 'h' => array_map( static fn( Heading $h ): array => array( $h->level, $h->text, $h->source->type, $h->source->ref ), $this->headings ), 'j' => $this->json_ld, 'xr' => $this->header_robots, 'bs' => $this->body_sketch, 'a' => $this->a11y, 'ct' => $this->content, 'pf' => $this->perf, ); } /** * Rebuilds the facts from the job state. * * @param array $state Result of to_state(). * @phpstan-param array $state */ public static function from_state( array $state ): self { $headings = array(); foreach ( (array) ( $state['h'] ?? array() ) as $h ) { if ( is_array( $h ) && 4 === count( $h ) ) { $headings[] = new Heading( (int) $h[0], (string) $h[1], new Source( (string) $h[2], (string) $h[3] ) ); } } return new self( (int) ( $state['tc'] ?? 0 ), self::strings( $state['d'] ?? array() ), self::strings( $state['c'] ?? array() ), self::strings( $state['r'] ?? array() ), self::groups( $state['og'] ?? array() ), self::groups( $state['tw'] ?? array() ), $headings, self::strings( $state['j'] ?? array() ), (string) ( $state['xr'] ?? '' ), is_array( $state['bs'] ?? null ) ? array_values( array_map( 'intval', $state['bs'] ) ) : array(), self::problems( $state['a'] ?? array() ), self::problems( $state['ct'] ?? array() ), self::problems( $state['pf'] ?? array() ) ); } /** * Problem or fact lists (accessibility, content) from the state. * * @param mixed $value Value from the state. * @return array */ private static function problems( $value ): array { $out = array(); foreach ( is_array( $value ) ? $value : array() as $item ) { if ( is_array( $item ) && 4 === count( $item ) ) { $item = array_values( $item ); $out[] = array( (string) $item[0], (string) $item[1], (string) $item[2], (string) $item[3] ); } } return $out; } /** * A list of strings. * * @param mixed $value Value from the state. * @return string[] */ private static function strings( $value ): array { return is_array( $value ) ? array_values( array_map( 'strval', $value ) ) : array(); } /** * A map of string lists. * * @param mixed $value Value from the state. * @return array */ private static function groups( $value ): array { $out = array(); if ( is_array( $value ) ) { foreach ( $value as $key => $items ) { $out[ (string) $key ] = self::strings( $items ); } } return $out; } }