$cursor Cursor with the last id seen. * @return array|null */ public function step( int $scan_id, array $cursor ): ?array { global $wpdb; $languages = Translations::languages(); if ( count( $languages ) < 2 ) { return null; } $last = (int) ( $cursor['last'] ?? 0 ); $rows = (array) $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reads posts in batches. $wpdb->prepare( "SELECT ID, post_type, post_title FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type IN ('post','page') AND ID > %d ORDER BY ID ASC LIMIT %d", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Core table name. $last, self::PER_STEP ) ); foreach ( $rows as $row ) { $this->inspect( $scan_id, $row, $languages ); $last = (int) $row->ID; } return count( $rows ) < self::PER_STEP ? null : array( 'last' => $last ); } /** * Checks one page or post. * * @param int $scan_id Scan id. * @param \stdClass $row Post row. * @param array $languages Active language codes. */ private function inspect( int $scan_id, \stdClass $row, array $languages ): void { $id = (int) $row->ID; $type = (string) $row->post_type; $own = Translations::language_of( $id, $type ); $translations = Translations::of_post( $id, $type, $languages ); $missing = TranslationRules::missing( $languages, $own, $translations ); if ( array() !== $missing ) { $this->record( $scan_id, $id, 'missing_translation', __( 'This page has no translation in some languages', 'forensix-digital' ), array( 'languages' => $missing ), implode( ', ', $missing ) ); } // F-COMMERCE-02: a translation that kept the main language's title or description. $main = Translations::default_language(); $original = (int) ( $translations[ $main ] ?? 0 ); if ( '' === $own || '' === $main || $own === $main || $original < 1 || $original === $id ) { return; } if ( null === $this->seo ) { $this->seo = new SeoProvider(); } $mine = $this->seo->read( $id ); $theirs = $this->seo->read( $original ); $titles = array( 'same_title' => __( 'This translation has the same title as the original', 'forensix-digital' ), 'no_description' => __( 'This translation has no search description', 'forensix-digital' ), 'same_description' => __( 'This translation has the same search description as the original', 'forensix-digital' ), ); $problems = TranslationRules::untranslated( array( 'title' => (string) $row->post_title, 'description' => $mine['description'], ), array( 'title' => get_the_title( $original ), 'description' => $theirs['description'], ) ); foreach ( $problems as $name ) { $this->record( $scan_id, $id, 'translation.' . $name, $titles[ $name ], array( 'original' => $original ), $own ); } } /** * Records a suggestion about one page. * * @param int $scan_id Scan id. * @param int $id Post id. * @param string $name Check name after "commerce.". * @param string $title Finding title. * @param array $details Details. * @param string $context Context shown with the finding. */ private function record( int $scan_id, int $id, string $name, string $title, array $details, string $context ): void { $source = new Source( Source::POST_CONTENT, (string) $id ); $link = get_permalink( $id ); $this->store->record( $scan_id, $this->app, new Observation( 'commerce.' . $name, Priority::LOW, Priority::SUGGESTION, $title, false !== $link ? $link : home_url( '/' ), $source->type, $source->ref, ( new SourceLabels() )->label( $source ), $details ), (string) get_edit_post_link( $id, 'raw' ), $context ); } }