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