status ) { $items[] = self::item( self::URGENT, __( 'The last site check failed. Fix it and check again: the client report waits for it.', 'maintained-log' ), $log, __( 'See the check', 'maintained-log' ), true ); } // Security. if ( Vulnerabilities::is_enabled() ) { $active = Vulnerabilities::active_exposures(); if ( $active ) { $items[] = self::item( self::URGENT, /* translators: %s: comma-separated names. */ sprintf( _n( 'Known vulnerability to solve in %s.', 'Known vulnerabilities to solve in: %s.', count( $active ), 'maintained-log' ), implode( ', ', wp_list_pluck( $active, 'name' ) ) ), $log . '#ml-vulns', __( 'See what to do', 'maintained-log' ), false ); } $all = Vulnerabilities::exposures(); $inactive = wp_list_filter( $all, array( 'state' => 'inactive' ) ); $uncertain = wp_list_filter( $all, array( 'state' => 'uncertain' ) ); if ( $inactive ) { /* translators: %s: comma-separated names. */ $items[] = self::item( self::SOON, sprintf( __( 'Deactivated with vulnerabilities, delete if not used: %s.', 'maintained-log' ), implode( ', ', wp_list_pluck( $inactive, 'name' ) ) ), $log . '#ml-vulns', __( 'Review', 'maintained-log' ), false ); } if ( $uncertain ) { /* translators: %s: comma-separated names. */ $items[] = self::item( self::SOON, sprintf( __( 'Check whether these vulnerabilities belong to another edition: %s.', 'maintained-log' ), implode( ', ', wp_list_pluck( $uncertain, 'name' ) ) ), $log . '#ml-vulns', __( 'Review', 'maintained-log' ), false ); } $sync = Vulnerabilities::sync_state(); if ( $sync['last_error'] ) { /* translators: %s: error message. */ $items[] = self::item( self::SOON, sprintf( __( 'Vulnerability data could not be updated: %s', 'maintained-log' ), $sync['last_error'] ), $options . '#ml-fetch', __( 'Retry', 'maintained-log' ), false ); } elseif ( $sync['last_success'] && time() - $sync['last_success'] > 3 * DAY_IN_SECONDS ) { $items[] = self::item( self::SOON, __( 'Vulnerability data is more than 3 days old (WP-Cron may not be running).', 'maintained-log' ), $options . '#ml-fetch', __( 'Update now', 'maintained-log' ), true ); } } // Updates. $pending = Alerts::pending_updates(); if ( $pending ) { $items[] = self::item( self::SOON, /* translators: %d: number of updates. */ sprintf( _n( '%d update pending.', '%d updates pending.', count( $pending ), 'maintained-log' ), count( $pending ) ), admin_url( 'update-core.php' ), __( 'Go to updates', 'maintained-log' ), true ); } // Technical status points the client would see as "under review". $notes = Tech_Status::notes(); foreach ( Tech_Status::get()['items'] as $key => $tech ) { if ( Tech_Status::OK !== $tech['status'] && empty( $notes[ $key ]['text'] ) ) { $text = 'health' === $key ? __( 'WordPress Site Health reports critical issues. Solve them or add a note for the client.', 'maintained-log' ) /* translators: 1: item name, 2: detail. */ : sprintf( __( '%1$s: %2$s Solve it or add a note for the client.', 'maintained-log' ), $tech['label'], $tech['detail'] ); $items[] = self::item( self::SOON, $text, $options . '#ml-tech-notes', __( 'Add a note', 'maintained-log' ), true ); } } // Backups. if ( Backups::is_overdue() ) { $items[] = self::item( self::SOON, __( 'Backups have not been confirmed recently: check them in the hosting panel and confirm with one click.', 'maintained-log' ), $log, __( 'Confirm', 'maintained-log' ), true ); } // Report configuration. if ( 'never' !== $settings['report_frequency'] && '' === $settings['report_recipients'] ) { $items[] = self::item( self::SOON, __( 'The client report has no recipient: it will not be sent.', 'maintained-log' ), $options, __( 'Add the client email', 'maintained-log' ), true ); } if ( '' !== $settings['report_recipients'] && '' === trim( (string) $settings['client_name'] ) ) { $items[] = self::item( self::TIP, __( 'Add the client name so the report greets them personally.', 'maintained-log' ), $options, __( 'Settings', 'maintained-log' ), true ); } $cron = Cron_Status::state()['mode']; if ( Cron_Status::STOPPED === $cron ) { $items[] = self::item( self::URGENT, __( 'Scheduled tasks are not running (WP-Cron is disabled and no cron job calls it): no report or alert will be sent.', 'maintained-log' ), $options . '#ml-cron', __( 'Set up the cron job', 'maintained-log' ), true ); } elseif ( Cron_Status::VISITS === $cron && 'never' !== $settings['report_frequency'] ) { $items[] = self::item( self::TIP, __( 'Set up a real cron job in the hosting so reports and alerts go out on time even without visits.', 'maintained-log' ), $options . '#ml-cron', __( 'How to', 'maintained-log' ), false ); } if ( get_option( Reporter::HELD_SINCE_OPTION ) ) { $items[] = self::item( self::URGENT, __( 'The scheduled client report is on hold because a site check is failing.', 'maintained-log' ), $log, __( 'See the log', 'maintained-log' ), true ); } // Things worth doing so the next report shows the work. $next = Reporter::next_send_date(); if ( $next ) { $period_start = get_option( Reporter::LAST_PERIOD_OPTION ); $since = $period_start ? get_gmt_from_date( $period_start ) : gmdate( 'Y-m-d H:i:s', time() - MONTH_IN_SECONDS ); $tasks = Log::count( array( 'type' => Log::TYPE_MANUAL, 'from' => $since, ) ); if ( ! $tasks ) { $items[] = self::item( self::TIP, __( 'No tasks logged in this period yet: log the work done (tests, reviews, content…) with its time so the client sees it.', 'maintained-log' ), $log . '#ml-add', __( 'Log a task', 'maintained-log' ), true ); } if ( Performance::is_enabled() && ! Performance::measurements( $since ) ) { $items[] = self::item( self::TIP, __( 'There is no speed measurement in this period yet.', 'maintained-log' ), $options . '#ml-fetch', __( 'Measure now', 'maintained-log' ), true ); } } // Setup. if ( ! Admin\Onboarding::state()['done'] ) { $items[] = self::item( self::TIP, __( 'The setup assistant has not been completed.', 'maintained-log' ), Admin\Onboarding::url( Admin\Onboarding::state()['step'] ), __( 'Continue', 'maintained-log' ), false ); } /** * Filters the maintainer's pending items. * * @param array[] $items Items. */ $items = apply_filters( 'maintained_log_todo_items', $items ); $order = array( self::URGENT => 0, self::SOON => 1, self::TIP => 2, ); usort( $items, static function ( $a, $b ) use ( $order ) { return $order[ $a['priority'] ] <=> $order[ $b['priority'] ]; } ); return $items; } /** * Builds an item. * * @param string $priority urgent, soon or tip. * @param string $text What to do. * @param string $url Where to do it. * @param string $link Link label. * @param bool $before_report Whether it affects the next report. * @return array */ private static function item( $priority, $text, $url, $link, $before_report ) { return array( 'priority' => $priority, 'text' => $text, 'url' => $url, 'link' => $link, 'before_report' => $before_report, ); } /** * Latest page check (not technical status entries). * * @return object|null */ private static function last_site_check() { foreach ( Log::query( array( 'type' => Log::TYPE_CHECK, 'per_page' => 10, ) ) as $entry ) { if ( 'technical' !== $entry->category ) { return $entry; } } return null; } }