$facts Facts from collect(). * @return array */ public static function items( array $facts ): array { $settings = $facts['settings_url'] ?? ''; $flag = static fn( string $key ): bool => ! empty( $facts[ $key ] ); $scan_done = 'completed' === ( $facts['scan_status'] ?? '' ); $serious = (int) ( $facts['serious_open'] ?? 0 ); $items = array( self::item( 'search_visible', __( 'Search engines are allowed to find the site', 'forensix-digital' ), $flag( 'blog_public' ), __( 'While "Discourage search engines" is on, Google is asked to stay away.', 'forensix-digital' ), (string) ( $facts['reading_url'] ?? '' ), __( 'Open Reading settings', 'forensix-digital' ) ), self::item( 'https', __( 'The site uses https', 'forensix-digital' ), $flag( 'https' ), __( 'Browsers warn visitors about sites without https. Ask your host for a certificate and set the site address to https.', 'forensix-digital' ), (string) $settings, __( 'Open General settings', 'forensix-digital' ) ), self::item( 'permalinks', __( 'Page addresses are readable', 'forensix-digital' ), $flag( 'pretty_permalinks' ), __( 'Addresses like /?p=123 say nothing to visitors or search engines. Choose "Post name" under Permalinks.', 'forensix-digital' ), (string) ( $facts['permalinks_url'] ?? '' ), __( 'Open Permalink settings', 'forensix-digital' ) ), self::item( 'site_title', __( 'The site has its own title and tagline', 'forensix-digital' ), $flag( 'title_set' ) && $flag( 'tagline_set' ), __( 'The title and tagline appear in browser tabs and search results. The WordPress default tagline should be replaced or emptied.', 'forensix-digital' ), (string) $settings, __( 'Open General settings', 'forensix-digital' ) ), self::item( 'site_icon', __( 'The site has an icon', 'forensix-digital' ), $flag( 'site_icon' ), __( 'The small icon shows in browser tabs and bookmarks. Sites without one look unfinished.', 'forensix-digital' ), (string) ( $facts['customizer_url'] ?? '' ), __( 'Choose an icon', 'forensix-digital' ) ), self::item( 'privacy_page', __( 'A privacy policy page is published', 'forensix-digital' ), $flag( 'privacy_page' ), __( 'Most countries expect one when you collect any data, even through a contact form.', 'forensix-digital' ), (string) ( $facts['privacy_url'] ?? '' ), __( 'Open Privacy settings', 'forensix-digital' ) ), self::item( 'sample_content', __( 'The WordPress sample page and post are gone', 'forensix-digital' ), ! $flag( 'sample_content' ), __( '"Hello world!" and "Sample Page" are added to every new site and should not be public.', 'forensix-digital' ), (string) ( $facts['pages_url'] ?? '' ), __( 'Open pages', 'forensix-digital' ) ), self::item( 'no_admin_user', __( 'No user is called "admin"', 'forensix-digital' ), ! $flag( 'admin_user' ), __( 'Attackers try the name "admin" first. Create a user with another name, give it the administrator role and delete the old one.', 'forensix-digital' ), (string) ( $facts['users_url'] ?? '' ), __( 'Open users', 'forensix-digital' ) ), self::item( 'errors_hidden', __( 'Error messages are not shown to visitors', 'forensix-digital' ), ! $flag( 'debug_visible' ), __( 'Technical messages on a live page confuse visitors and can reveal details about your server. Turn off WP_DEBUG_DISPLAY in wp-config.php.', 'forensix-digital' ), '', '' ), self::item( 'sitemap', __( 'A sitemap is available', 'forensix-digital' ), $flag( 'sitemap' ), __( 'A sitemap helps search engines find every page. WordPress provides one unless a plugin switches it off.', 'forensix-digital' ), '', '' ), self::item( 'scan_done', __( 'A site scan has been run', 'forensix-digital' ), $scan_done, __( 'The scan checks your pages for broken links, missing titles, images and more.', 'forensix-digital' ), (string) ( $facts['scan_url'] ?? '' ), __( 'Open Overview', 'forensix-digital' ) ), array( 'id' => 'no_serious_findings', 'title' => __( 'No serious problems are open', 'forensix-digital' ), 'status' => $scan_done ? ( 0 === $serious ? self::DONE : self::TODO ) : self::UNKNOWN, 'why' => $scan_done ? sprintf( /* translators: %d: number of open critical and high findings. */ _n( '%d critical or high problem is still open.', '%d critical or high problems are still open.', $serious, 'forensix-digital' ), $serious ) : __( 'Run a scan first, then this item can tell.', 'forensix-digital' ), 'link' => $scan_done && 0 === $serious ? '' : (string) ( $facts['findings_url'] ?? '' ), 'link_label' => $scan_done && 0 === $serious ? '' : __( 'Open findings', 'forensix-digital' ), ), ); /** * Filters the launch checklist. Add-ons add items for what they know, for example a backup. * * @param array $items Items: id, title, status (done, todo or unknown), why, link, link_label. */ $items = apply_filters( 'forensix_launch_items', $items ); return array_values( array_filter( (array) $items, 'is_array' ) ); } /** * How many items are done. * * @param array $items Items. */ public static function done_count( array $items ): int { return count( array_filter( $items, static fn( array $item ): bool => self::DONE === $item['status'] ) ); } /** * Reads the facts from WordPress. * * @param string $scan_status Status of the latest scan, empty when there is none. * @param int $serious Open critical and high findings. * @return array */ public static function collect( string $scan_status, int $serious ): array { $sample = get_posts( array( 'post_type' => array( 'post', 'page' ), 'post_status' => 'publish', 'name__in' => array( 'hello-world', 'sample-page' ), 'posts_per_page' => 5, ) ); $has_sample = false; foreach ( $sample as $post ) { if ( ContentRules::is_default_post( $post->post_type, $post->post_name, $post->post_title ) ) { $has_sample = true; } } $privacy = (int) get_option( 'wp_page_for_privacy_policy' ); return array( 'blog_public' => '0' !== (string) get_option( 'blog_public', '1' ), 'https' => str_starts_with( home_url(), 'https://' ), 'pretty_permalinks' => '' !== (string) get_option( 'permalink_structure' ), 'title_set' => '' !== trim( (string) get_option( 'blogname' ) ), 'tagline_set' => ContentRules::DEFAULT_TAGLINE !== (string) get_option( 'blogdescription' ), 'site_icon' => has_site_icon(), 'privacy_page' => $privacy > 0 && 'publish' === get_post_field( 'post_status', $privacy ), 'sample_content' => $has_sample, 'admin_user' => false !== get_user_by( 'login', 'admin' ), 'debug_visible' => defined( 'WP_DEBUG' ) && WP_DEBUG && ( ! defined( 'WP_DEBUG_DISPLAY' ) || WP_DEBUG_DISPLAY ), 'sitemap' => self::sitemap_available(), 'scan_status' => $scan_status, 'serious_open' => $serious, 'settings_url' => admin_url( 'options-general.php' ), 'reading_url' => admin_url( 'options-reading.php' ), 'permalinks_url' => admin_url( 'options-permalink.php' ), 'customizer_url' => admin_url( 'customize.php' ), 'privacy_url' => admin_url( 'options-privacy.php' ), 'pages_url' => admin_url( 'edit.php?post_type=page' ), 'users_url' => admin_url( 'users.php' ), 'scan_url' => admin_url( 'admin.php?page=forensix-digital' ), 'findings_url' => admin_url( 'admin.php?page=forensix-findings' ), ); } /** * Whether WordPress or a well-known SEO plugin serves a sitemap. */ private static function sitemap_available(): bool { if ( array() !== ( new SeoProvider() )->detected() ) { return true; } return function_exists( 'wp_sitemaps_get_server' ) && wp_sitemaps_get_server()->sitemaps_enabled(); } /** * One item. * * @param string $id Id. * @param string $title Plain sentence that is true when done. * @param bool $done Whether it is done. * @param string $why Why it matters. * @param string $link Where to fix it, empty for none. * @param string $link_label Link text. * @return array{id: string, title: string, status: string, why: string, link: string, link_label: string} */ private static function item( string $id, string $title, bool $done, string $why, string $link, string $link_label ): array { return array( 'id' => $id, 'title' => $title, 'status' => $done ? self::DONE : self::TODO, 'why' => $why, 'link' => $done ? '' : $link, 'link_label' => $done ? '' : $link_label, ); } }