Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
19.30% covered (danger)
19.30%
22 / 114
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Functions
19.30% covered (danger)
19.30%
22 / 114
0.00% covered (danger)
0.00%
0 / 5
188.29
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
 log_deprecated_functions_only_once_per_day
78.57% covered (warning)
78.57%
22 / 28
0.00% covered (danger)
0.00%
0 / 1
4.16
 log_deprecated_arguments_only_once_per_day
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
20
 log_doing_it_wrong_only_once_per_day
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
20
 log_deprecated_hook_only_once_per_day
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2/**
3 * Hook into the behaviour of WordPress functions.php.
4 *
5 * Use transients to log errors only once per day, with more detail than before.
6 * i.e. to prevent logs being flooded with deprecation warnings.
7 *
8 * Doing this here means we can set the log level per plugin without relying on WP_DEBUG being true.
9 *
10 * @since      1.0.0
11 *
12 * @package brianhenryie/bh-wp-logger
13 * @author  Brian Henry <BrianHenryIE@gmail.com>
14 */
15
16namespace BrianHenryIE\WP_Logger\WP_Includes;
17
18use BrianHenryIE\WP_Logger\API_Interface;
19use BrianHenryIE\WP_Logger\Logger_Settings_Interface;
20use Psr\Log\LoggerAwareTrait;
21use Psr\Log\LoggerInterface;
22
23/**
24 * Intercepts WordPress's logging to prevent duplicate logs and to add detail.
25 *
26 * @see _deprecated_function()
27 * @see _deprecated_argument()
28 * @see _doing_it_wrong()
29 * @see _deprecated_hook()
30 */
31class Functions {
32
33    use LoggerAwareTrait;
34
35    /**
36     * Constructor.
37     * No logic, just assignments.
38     *
39     * @param API_Interface             $api The logger's utility functions. Used to check the backtrace to see is it relevant to this plugin.
40     * @param Logger_Settings_Interface $settings The logger settings. Not used here.
41     * @param LoggerInterface           $logger PSR logger.
42     */
43    public function __construct(
44        protected API_Interface $api,
45        protected Logger_Settings_Interface $settings,
46        LoggerInterface $logger
47    ) {
48        $this->setLogger( $logger );
49    }
50
51    /**
52     * Limits logging deprecated functions to once per day.
53     * Logs more detail than usual.
54     *
55     * @hooked deprecated_function_run
56     *
57     * @param string $function_name    The function that was called.
58     * @param string $replacement_function Optional. The function that should have been called. Default empty.
59     * @param string $version     The version of WordPress that deprecated the function.
60     *
61     * @see _deprecated_function()
62     */
63    public function log_deprecated_functions_only_once_per_day( $function_name, $replacement_function, $version ): void {
64
65        if ( ! $this->api->is_backtrace_contains_plugin( implode( '', func_get_args() ) ) ) {
66            return;
67        }
68
69        $plugin_slug = $this->settings->get_plugin_slug();
70
71        $transient_name = "log_deprecated_function_{$function_name}_{$plugin_slug}";
72
73        $recently_logged = get_transient( $transient_name );
74
75        if ( empty( $recently_logged ) ) {
76
77            if ( $replacement_function ) {
78                $log_message =
79                    sprintf(
80                    /* translators: 1: PHP function name, 2: Version number, 3: Alternative function name. */
81                        __( '%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.' ),
82                        $function_name,
83                        $version,
84                        $replacement_function
85                    );
86            } else {
87                $log_message =
88                    sprintf(
89                    /* translators: 1: PHP function name, 2: Version number. */
90                        __( '%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.' ),
91                        $function_name,
92                        $version
93                    );
94            }
95
96            $context = array(
97                'function'             => $function_name,
98                'replacement_function' => $replacement_function,
99                'version'              => $version,
100            );
101
102            $this->logger->warning( $log_message, $context );
103
104            set_transient( $transient_name, $log_message, DAY_IN_SECONDS );
105        }
106
107        // Suppress WordPress's own logging.
108        add_filter( 'deprecated_function_trigger_error', '__return_false' );
109    }
110
111    /**
112     * Limits logging deprecated arguments to once per day.
113     * Logs more detail than usual.
114     *
115     * @hooked deprecated_function_run
116     *
117     * @param string $function_name The function that was called.
118     * @param string $message  A message regarding the change.
119     * @param string $version  The version of WordPress that deprecated the argument used.
120     *
121     * @see _deprecated_argument()
122     */
123    public function log_deprecated_arguments_only_once_per_day( $function_name, $message, $version ): void {
124
125        if ( ! $this->api->is_backtrace_contains_plugin( implode( '', func_get_args() ) ) ) {
126            return;
127        }
128
129        $plugin_slug = $this->settings->get_plugin_slug();
130
131        $transient_name = "log_deprecated_argument_{$function_name}_{$plugin_slug}";
132
133        $recently_logged = get_transient( $transient_name );
134
135        if ( empty( $recently_logged ) ) {
136
137            if ( $message ) {
138                $log_message =
139                    sprintf(
140                    /* translators: 1: PHP function name, 2: Version number, 3: Optional message regarding the change. */
141                        __( '%1$s was called with an argument that is <strong>deprecated</strong> since version %2$s! %3$s' ),
142                        $function_name,
143                        $version,
144                        $message
145                    );
146            } else {
147                $log_message =
148                    sprintf(
149                    /* translators: 1: PHP function name, 2: Version number. */
150                        __( '%1$s was called with an argument that is <strong>deprecated</strong> since version %2$s with no alternative available.' ),
151                        $function_name,
152                        $version
153                    );
154            }
155
156            $context = array(
157                'function' => $function_name,
158                'message'  => $message,
159                'version'  => $version,
160            );
161
162            $this->logger->warning( $log_message, $context );
163
164            set_transient( $transient_name, $log_message, DAY_IN_SECONDS );
165        }
166
167        add_filter( 'deprecated_argument_trigger_error', '__return_false' );
168    }
169
170    /**
171     * `_doing_it_wrong` runs e.g. when a function is called before a required action has run.
172     * This function limits logging `_doing_it_wrong` errors to once per day.
173     * Logs more detail than usual.
174     *
175     * @hooked doing_it_wrong_run
176     * @see _doing_it_wrong()
177     *
178     * @hooked doing_it_wrong_run
179     *
180     * @param string $function_name The function that was called.
181     * @param string $message  A message explaining what has been done incorrectly.
182     * @param string $version  The version of WordPress where the message was added.
183     */
184    public function log_doing_it_wrong_only_once_per_day( $function_name, $message, $version ): void {
185
186        if ( ! $this->api->is_backtrace_contains_plugin( implode( '', func_get_args() ) ) ) {
187            return;
188        }
189
190        $plugin_slug = $this->settings->get_plugin_slug();
191
192        $transient_name = "log_doing_it_wrong_run_{$function_name}_{$plugin_slug}";
193
194        $recently_logged = get_transient( $transient_name );
195
196        if ( empty( $recently_logged ) ) {
197
198            if ( $version ) {
199                /* translators: %s: Version number. */
200                $version = sprintf( __( '(This message was added in version %s.)' ), $version );
201            }
202
203            $message .= ' ' . sprintf(
204                /* translators: %s: Documentation URL. */
205                __( 'Please see <a href="%s">Debugging in WordPress</a> for more information.' ),
206                __( 'https://wordpress.org/support/article/debugging-in-wordpress/' )
207            );
208
209            $log_message =
210                sprintf(
211                /* translators: Developer debugging message. 1: PHP function name, 2: Explanatory message, 3: WordPress version number. */
212                    __( '%1$s was called <strong>incorrectly</strong>. %2$s %3$s' ),
213                    $function_name,
214                    $message,
215                    $version
216                );
217
218            $context = array(
219                'function' => $function_name,
220                'message'  => $message,
221                'version'  => $version,
222            );
223
224            $this->logger->warning( $log_message, $context );
225
226            set_transient( $transient_name, $log_message, DAY_IN_SECONDS );
227        }
228
229        add_filter( 'doing_it_wrong_trigger_error', '__return_false' );
230    }
231
232    /**
233     * Log deprecated hooks called by this plugin only once per day.
234     *
235     * When a deprecated hook is called, WordPress will call this function.
236     * If the backtrace does not contain this logger's plugin, it will return early.
237     * Otherwise:
238     * * Check for a transient indicating we have logged this already
239     * * If absent, create one
240     * * Prevent WordPress from logging the deprecation warning (w/filter)
241     *
242     * @hooked deprecated_hook_run
243     * @see _deprecated_hook()
244     *
245     * @param string $hook        The hook that was called.
246     * @param string $replacement The hook that should be used as a replacement.
247     * @param string $version     The version of WordPress that deprecated the argument used.
248     * @param string $message     A message regarding the change.
249     */
250    public function log_deprecated_hook_only_once_per_day( $hook, $replacement, $version, $message ): void {
251
252        if ( ! $this->api->is_backtrace_contains_plugin( implode( '', func_get_args() ) ) ) {
253            return;
254        }
255
256        $plugin_slug = $this->settings->get_plugin_slug();
257
258        $transient_name = "log_deprecated_hook_run_{$hook}_{$plugin_slug}";
259
260        $recently_logged = get_transient( $transient_name );
261
262        if ( empty( $recently_logged ) ) {
263
264            $message = empty( $message ) ? '' : ' ' . $message;
265
266            if ( $replacement ) {
267                $log_message =
268                    sprintf(
269                    /* translators: 1: WordPress hook name, 2: Version number, 3: Alternative hook name. */
270                        __( '%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.' ),
271                        $hook,
272                        $version,
273                        $replacement
274                    ) . $message;
275
276            } else {
277                $log_message =
278                    sprintf(
279                    /* translators: 1: WordPress hook name, 2: Version number. */
280                        __( '%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.' ),
281                        $hook,
282                        $version
283                    ) . $message;
284            }
285
286            $context = array(
287                'hook'        => $hook,
288                'replacement' => $replacement,
289                'version'     => $version,
290                'message'     => $message,
291            );
292
293            $this->logger->warning( $log_message, $context );
294
295            set_transient( $transient_name, $log_message, DAY_IN_SECONDS );
296        }
297
298        add_filter( 'deprecated_hook_trigger_error', '__return_false' );
299    }
300}