Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
23.71% covered (danger)
23.71%
23 / 97
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Logs_Page
23.71% covered (danger)
23.71%
23 / 97
0.00% covered (danger)
0.00%
0 / 5
197.60
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
 add_page
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
30
 display_page
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 1
72
 enqueue_scripts
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
3.00
 enqueue_styles
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
3.01
1<?php
2/**
3 * The UI around the logs table.
4 *
5 * E.g. /wp-admin/admin.php?page=bh-wp-logger-development-plugin-logs.
6 *
7 * TODO: Add "send to plugin developer" button.
8 * TODO: Add copy to clipboard button.
9 *
10 * @package brianhenryie/bh-wp-logger
11 */
12
13namespace BrianHenryIE\WP_Logger\Admin;
14
15use BrianHenryIE\WP_Logger\API\BH_WP_PSR_Logger;
16use BrianHenryIE\WP_Logger\API_Interface;
17use BrianHenryIE\WP_Logger\Logger_Settings_Interface;
18use Psr\Log\LoggerAwareTrait;
19use Psr\Log\LogLevel;
20use Psr\Log\NullLogger;
21
22/**
23 * Functions for registering a "hidden menu" item, to add the wp-admin page to display the logs.
24 */
25class Logs_Page {
26
27    use LoggerAwareTrait;
28
29    /**
30     * The logger settings. i.e. what is the plugin slug this logger is for?
31     *
32     * @uses \BrianHenryIE\WP_Logger\Logger_Settings_Interface::get_plugin_slug()
33     * @var Logger_Settings_Interface
34     */
35    protected Logger_Settings_Interface $settings;
36
37    /**
38     * Used to get the list of log files.
39     * Needed to instantiate the table.
40     *
41     * @var API_Interface
42     */
43    protected API_Interface $api;
44
45    /**
46     * Logs_Page constructor.
47     *
48     * @param API_Interface             $api The main functions of the logger.
49     * @param Logger_Settings_Interface $settings The configuration used to set up the logger.
50     * @param ?BH_WP_PSR_Logger         $logger The logger itself, for logging.
51     */
52    public function __construct( API_Interface $api, Logger_Settings_Interface $settings, ?BH_WP_PSR_Logger $logger = null ) {
53
54        $this->setLogger( $logger ?? new NullLogger() );
55        $this->settings = $settings;
56        $this->api      = $api;
57    }
58
59    /**
60     * Add a WordPress admin UI page, but without any menu linking to it.
61     *
62     * @hooked admin_menu
63     *
64     * @see wp-admin/menu.php
65     */
66    public function add_page(): void {
67
68        $logs_slug  = "{$this->settings->get_plugin_slug()}-logs";
69        $menu_title = 'Logs';
70
71        $parent_slug = '';
72
73        global $menu;
74        foreach ( $menu as $menu_item ) {
75            if ( stristr( $menu_item[0], 'logs' ) || stristr( $menu_item[2], 'logs' ) || stristr( $menu_item[3], 'logs' ) ) {
76                $parent_slug = $menu_item[2];
77                $menu_title  = $this->settings->get_plugin_name();
78                break;
79            }
80        }
81
82        add_submenu_page(
83            $parent_slug,
84            __( 'Logs', 'bh-wp-logger' ),
85            $menu_title,
86            'manage_options',
87            $logs_slug,
88            array( $this, 'display_page' )
89        );
90    }
91
92    /**
93     * Display the page.
94     * Record the last visited time.
95     *
96     * Registered above.
97     *
98     * @see add_page()
99     */
100    public function display_page(): void {
101
102        echo '<div class="wrap">';
103
104        echo '<h1>';
105        echo esc_html( $this->settings->get_plugin_name() );
106        echo '</h1>';
107
108        $log_files = $this->api->get_log_files();
109
110        if ( empty( $log_files ) ) {
111            // This will occur e.g. immediately after deleting all logs.
112            echo '<p>No logs to display.</p>';
113            echo '</div>';
114            return;
115        }
116
117        $logs_table = new Logs_List_Table( $this->api, $this->settings, $this->logger );
118
119        // Set date here?
120
121        // Show a list of date to switch between dates.
122        echo '<label for="log_date">Log date:</label>';
123        echo '<select name="log_date" id="log_date">';
124
125        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
126        $chosen_date = isset( $_GET['log_date'] ) ? sanitize_key( $_GET['log_date'] ) : array_key_last( $log_files );
127
128        // Maybe should use set file?
129        $logs_table->set_date( $chosen_date );
130
131        // TODO: Allow filtering here to add external log files, e.g. from Authorize.net SDK.
132        foreach ( $log_files as $date => $path ) {
133            $date_formatted = $date;
134            echo '<option value="' . esc_attr( $date ) . '"';
135            if ( $date === $chosen_date ) {
136                echo ' selected';
137            }
138            echo '>' . esc_html( $date_formatted ) . '</option>';
139        }
140        echo '</select>';
141
142        echo '<button name="deleteButton" id="deleteButton" data-date="' . esc_attr( $chosen_date ) . '" class="button logs-page button-primary">Delete ' . esc_html( $chosen_date ) . ' logs</button>';
143        echo '<button name="deleteAllButton" id="deleteAllButton" class="button logs-page button-secondary">Delete all logs</button>';
144
145        wp_nonce_field( 'bh-wp-logger-delete', 'delete_logs_wpnonce' );
146
147        echo '<p>Current log level: <b>' . esc_html( ucfirst( $this->settings->get_log_level() ) ) . '</b></p>';
148
149        // If this is in the logger's private-uploads directory, then it already should be accessible, but if it's in the wc-logs folder, it will not be.
150        $download_url = wp_nonce_url( admin_url( 'admin.php?page=' . $this->settings->get_plugin_slug() . '&date=' . $date . '&download-log=true' ), 'bh-wp-logger-download' );
151        $filepath     = $log_files[ $chosen_date ];
152        $filename     = basename( $filepath );
153        // TODO: Show file size here. Show number of entries.
154        echo '<p>Displaying log file at <a href="' . esc_url( $download_url ) . '" download="' . esc_attr( $filename ) . '"><code>' . esc_html( $filepath ) . '</code></a></p>';
155
156        echo '<p>Display levels: ';
157
158        $log_level_counts = array(
159            LogLevel::ERROR   => 0,
160            LogLevel::WARNING => 0,
161            LogLevel::NOTICE  => 0,
162            LogLevel::INFO    => 0,
163            LogLevel::DEBUG   => 0,
164        );
165        foreach ( $logs_table->get_data() as $datum ) {
166            ++$log_level_counts[ strtolower( $datum['level'] ) ];
167        }
168
169        $checkboxes = array();
170        foreach ( $log_level_counts as $log_level => $log_level_count ) {
171            $disabled        = 0 === $log_level_count ? 'disabled' : '';
172            $friendly_level  = ucfirst( $log_level );
173            $log_level       = esc_attr( $log_level );
174            $log_level_count = intval( $log_level_count );
175            $checkboxes[]    = "<input {$disabled} class=\"log_level_display_checkbox\" type=\"checkbox\" id=\"log_level_display_checkbox_{$log_level}\" name=\"log_level_display_checkbox_{$log_level}\" checked> <label for=\"log_level_display_checkbox_{$log_level}\">$friendly_level ($log_level_count)</label>";
176        }
177
178        echo implode( ' • ', $checkboxes );
179
180        echo '</p>';
181
182        // TODO: Add an action here for other plugins to add controls.
183
184        $logs_table->prepare_items();
185        $logs_table->display();
186
187        echo '</div>';
188
189        $this->api->set_last_logs_view_time();
190    }
191
192    /**
193     * Enqueue the logs page javascript for changing date and deleting logs.
194     * Checks the plugin slug and only adds the script on the logs page for this plugin.
195     *
196     * @hooked admin_enqueue_scripts
197     */
198    public function enqueue_scripts(): void {
199
200        $slug        = $this->settings->get_plugin_slug();
201        $page_suffix = "_{$slug}-logs";
202
203        $current_page = get_current_screen();
204
205        /**
206         * `$current_page->id` will begin with `admin_page` or `$parent_slug` determined in `add_page()`.
207         *
208         * @see Logs_Page::add_page()
209         */
210        if ( is_null( $current_page ) || substr( $current_page->id, -strlen( $page_suffix ) ) !== $page_suffix ) {
211            return;
212        }
213
214        // This is the bh-wp-logger JavaScript version, not the plugin version.
215        $version = '1.1.0';
216
217        $js_path = realpath( __DIR__ . '/../../' ) . '/assets/bh-wp-logger-admin.js';
218        $js_url  = plugin_dir_url( $js_path ) . 'bh-wp-logger-admin.js';
219
220        wp_enqueue_script( 'bh-wp-logger-admin-logs-page-' . $slug, $js_url, array( 'jquery', 'renderjson', 'colresizable' ), $version, true );
221
222        $renderjson_js_path = realpath( __DIR__ . '/../../' ) . '/assets/vendor/renderjson/renderjson.js';
223        $renderjson_js_url  = plugin_dir_url( $renderjson_js_path ) . 'renderjson.js';
224
225        wp_enqueue_script( 'renderjson', $renderjson_js_url, array(), '1.4', true );
226
227        $colresizable_js_path = realpath( __DIR__ . '/../../' ) . '/assets/vendor/colresizable/colResizable-1.6.min.js';
228        $colresizable_js_url  = plugin_dir_url( $colresizable_js_path ) . 'colResizable-1.6.min.js';
229
230        wp_enqueue_script( 'colresizable', $colresizable_js_url, array( 'jquery' ), '1.6', true );
231    }
232
233    /**
234     * Register the stylesheets for the logs page.
235     * (colours the rows with the severity of the log message!).
236     *
237     * @hooked admin_enqueue_scripts
238     */
239    public function enqueue_styles(): void {
240
241        $slug        = $this->settings->get_plugin_slug();
242        $page_suffix = "_{$slug}-logs";
243
244        $current_page = get_current_screen();
245
246        /**
247         * `$current_page->id` will begin with `admin_page` or `$parent_slug` determined in `add_page()`.
248         *
249         * @see Logs_Page::add_page()
250         */
251        if ( is_null( $current_page ) || substr( $current_page->id, -strlen( $page_suffix ) ) !== $page_suffix ) {
252            return;
253        }
254
255        $handle = "{$this->settings->get_plugin_slug()}-logs";
256
257        $version = '1.0.0';
258
259        $css_path = realpath( __DIR__ . '/../../' ) . '/assets/bh-wp-logger.css';
260        $css_url  = plugin_dir_url( $css_path ) . 'bh-wp-logger.css';
261
262        wp_enqueue_style( $handle, $css_url, array(), $version, 'all' );
263    }
264}