Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
14.29% covered (danger)
14.29%
6 / 42
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Admin_Notices
14.29% covered (danger)
14.29%
6 / 42
0.00% covered (danger)
0.00%
0 / 4
198.99
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 get_error_detail_option_name
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_last_error
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 admin_notices
16.67% covered (danger)
16.67%
6 / 36
0.00% covered (danger)
0.00%
0 / 1
127.43
1<?php
2/**
3 * Add an admin notice for new errors.
4 *
5 * @package brianhenryie/bh-wp-logger
6 */
7
8namespace BrianHenryIE\WP_Logger\Admin;
9
10use BrianHenryIE\WP_Logger\API_Interface;
11use BrianHenryIE\WP_Logger\Logger_Settings_Interface;
12use Psr\Log\LoggerAwareTrait;
13use Psr\Log\LoggerInterface;
14use Psr\Log\NullLogger;
15use WPTRT\AdminNotices\Notices;
16
17/**
18 *
19 *
20 * @see https://github.com/WPTT/admin-notices
21 */
22class Admin_Notices extends Notices {
23
24    use LoggerAwareTrait;
25
26    /** @var Logger_Settings_Interface  */
27    protected Logger_Settings_Interface $settings;
28
29    /** @var API_Interface  */
30    protected API_Interface $api;
31
32    /**
33     * @param API_Interface             $api
34     * @param Logger_Settings_Interface $settings
35     * @param ?LoggerInterface          $logger
36     */
37    public function __construct( API_Interface $api, Logger_Settings_Interface $settings, ?LoggerInterface $logger = null ) {
38
39        $this->setLogger( $logger ?? new NullLogger() );
40        $this->settings = $settings;
41        $this->api      = $api;
42    }
43
44    protected function get_error_detail_option_name(): string {
45        return $this->settings->get_plugin_slug() . '-recent-error-data';
46    }
47
48    /**
49     * The last error is stored in the option `plugin-slug-recent-error-data` as an array with `message` and `timestamp`.
50     *
51     * @see Admin_Notices::get_error_detail_option_name()
52     *
53     * @return ?array{message: string, timestamp: string}
54     */
55    protected function get_last_error(): ?array {
56        $last_error = get_option( $this->get_error_detail_option_name(), null );
57        return $last_error;
58    }
59
60    /**
61     * Show a notice for recent errors in the logs.
62     *
63     * TODO: Do not show on plugin install page.
64     * TODO: Check file exists before linking to it.
65     *
66     * hooked earlier than 10 because Notices::boot() also hooks a function on admin_init that needs to run after this.
67     *
68     * @hooked admin_init
69     */
70    public function admin_notices(): void {
71
72        // We don't need to register the admin notice except to display it and to handle the dismiss button.
73        if ( ! is_admin() && ! wp_doing_ajax() ) {
74            return;
75        }
76
77        // TODO: alwasy return on updater.php
78
79        $error_detail_option_name = $this->get_error_detail_option_name();
80
81        // If we're on the logs page, don't show the admin notice linking to the logs page.
82        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
83        if ( isset( $_GET['page'] ) && $this->settings->get_plugin_slug() . '-logs' === sanitize_key( $_GET['page'] ) ) {
84            delete_option( $error_detail_option_name );
85            return;
86        }
87
88        $last_error = $this->get_last_error();
89
90        $last_log_time       = $this->api->get_last_log_time();
91        $last_logs_view_time = $this->api->get_last_logs_view_time();
92
93        // TODO: This should be comparing $last_error time?
94        if ( ! empty( $last_error ) && ( is_null( $last_logs_view_time ) || $last_log_time > $last_logs_view_time ) ) {
95
96            $is_dismissed_option_name = "wptrt_notice_dismissed_{$this->settings->get_plugin_slug()}-recent-error";
97
98            // wptrt_notice_dismissed_bh-wp-logger-development-plugin-recent-error
99
100            $error_text = isset( $last_error['message'] ) ? trim( $last_error['message'] ) : '';
101            $error_time = isset( $last_error['timestamp'] ) ? (int) $last_error['timestamp'] : '';
102
103            $title   = '';
104            $content = "<strong>{$this->settings->get_plugin_name()}</strong>. Error: ";
105
106            if ( ! empty( $error_text ) ) {
107                $content .= "\"{$error_text}\" ";
108            }
109
110            if ( ! empty( $error_time ) && is_numeric( $error_time ) ) {
111                $content .= ' at ' . gmdate( 'Y-m-d\TH:i:s\Z', $error_time ) . ' UTC.';
112
113                // wp_timezone();
114
115                // Link to logs.
116                $log_link = $this->api->get_log_url( gmdate( 'Y-m-d', $error_time ) );
117
118            } else {
119                $log_link = $this->api->get_log_url();
120            }
121
122            if ( ! empty( $log_link ) ) {
123                $content .= ' <a href="' . $log_link . '">View Logs</a>.</p></div>';
124            }
125
126            // ID must be globally unique because it is the css id that will be used.
127            $this->add(
128                $this->settings->get_plugin_slug() . '-recent-error',
129                $title,   // The title for this notice.
130                $content, // The content for this notice.
131                array(
132                    'scope' => 'global',
133                    'type'  => 'error',
134                )
135            );
136
137            /**
138             * When the notice is dismissed, delete the error detail option (to stop the notice being recreated),
139             * and delete the saved dismissed flag (which would prevent it displaying when the next error occurs).
140             *
141             * @see update_option()
142             */
143            $on_dismiss = function ( $value, $old_value, $option ) use ( $error_detail_option_name ) {
144                delete_option( $error_detail_option_name );
145                delete_option( $option );
146                return $old_value; // When new and old match, it short circuits.
147            };
148            add_filter( "pre_update_option_{$is_dismissed_option_name}", $on_dismiss, 10, 3 );
149
150            // wptrt_notice_dismissed_bh-wp-logger-development-plugin-recent-error
151
152        }
153    }
154}