self::token() ); if ( $from && $to ) { $args['from'] = $from->format( 'Y-m-d\TH:i:s' ); $args['to'] = $to->format( 'Y-m-d\TH:i:s' ); } return add_query_arg( $args, home_url( '/' ) ); } /** * Reports already sent to the client, newest first. * * @param int $limit Maximum number. * @return array[] Each with from, to (DateTimeImmutable) and entry. */ public static function sent_reports( $limit = 12 ) { $tz = wp_timezone(); $reports = array(); foreach ( Log::query( array( 'type' => Log::TYPE_REPORT, 'per_page' => 100, ) ) as $entry ) { if ( Log::STATUS_SUCCESS !== $entry->status || empty( $entry->details['from'] ) || empty( $entry->details['to'] ) ) { continue; } $key = $entry->details['from'] . '|' . $entry->details['to']; if ( isset( $reports[ $key ] ) ) { continue; } $reports[ $key ] = array( 'from' => ( new \DateTimeImmutable( $entry->details['from'] ) )->setTimezone( $tz ), 'to' => ( new \DateTimeImmutable( $entry->details['to'] ) )->setTimezone( $tz ), 'entry' => $entry, ); if ( count( $reports ) >= $limit ) { break; } } return array_values( $reports ); } /** * Registers the page styles and script; the template prints them, as it * is a standalone page without the theme's header and footer. */ private static function enqueue_assets() { wp_enqueue_style( 'maintained-log-report', MAINTAINED_LOG_URL . 'assets/report.css', array(), MAINTAINED_LOG_VERSION ); wp_add_inline_style( 'maintained-log-report', ':root { --ml-color: ' . sanitize_hex_color( Settings::get( 'brand_color' ) ) . '; }' ); wp_enqueue_script( 'maintained-log-report', MAINTAINED_LOG_URL . 'assets/report.js', array(), MAINTAINED_LOG_VERSION, true ); } /** * Renders the report page when the URL carries a valid token. */ public static function maybe_render() { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Access is controlled by the secret token. if ( ! isset( $_GET[ self::QUERY_VAR ] ) ) { return; } $given = sanitize_text_field( wp_unslash( $_GET[ self::QUERY_VAR ] ) ); if ( ! self::is_enabled() || ! hash_equals( self::token(), $given ) ) { status_header( 404 ); nocache_headers(); wp_die( esc_html__( 'This report link is not valid. Ask your web maintenance team for a new one.', 'maintained-log' ), '', array( 'response' => 404 ) ); } $period = self::period_from_request( isset( $_GET['from'] ) ? sanitize_text_field( wp_unslash( $_GET['from'] ) ) : '', isset( $_GET['to'] ) ? sanitize_text_field( wp_unslash( $_GET['to'] ) ) : '' ); // phpcs:enable nocache_headers(); header( 'X-Robots-Tag: noindex, nofollow', true ); header( 'Referrer-Policy: no-referrer', true ); header( 'Content-Type: text/html; charset=UTF-8' ); self::enqueue_assets(); $report = Reporter::build( $period['from'], $period['to'] ); echo Reporter::render( $report, 'web' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The template escapes its output. exit; } /** * Period requested in the URL, falling back to the latest report sent. * * @param string $from Requested start. * @param string $to Requested end. * @return array{from:\DateTimeImmutable,to:\DateTimeImmutable} */ private static function period_from_request( $from, $to ) { $tz = wp_timezone(); $start = \DateTimeImmutable::createFromFormat( 'Y-m-d\TH:i:s', $from, $tz ); $end = \DateTimeImmutable::createFromFormat( 'Y-m-d\TH:i:s', $to, $tz ); if ( $start && $end && $start < $end && $end->getTimestamp() - $start->getTimestamp() <= 400 * DAY_IN_SECONDS ) { return array( 'from' => $start, 'to' => $end, ); } $sent = self::sent_reports( 1 ); if ( $sent ) { return array( 'from' => $sent[0]['from'], 'to' => $sent[0]['to'], ); } return Reporter::manual_period(); } }