PluginProbe
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO / 1.4.0
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO v1.4.0
1.4.13 1.4.12 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.54 1.3.53 1.3.52 1.3.51 1.3.50 1.3.49 1.3.48 1.3.47 1.3.46 1.3.45 1.3.44 All 176 releases
disco / engine / OutputBuffer.php

OutputBuffer.php in Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO 1.4.0, at engine/OutputBuffer.php

125 lines 4.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php // phpcs:disable
2 /**
3 * Disco
4 *
5 * @package Disco
6 */
7
8 namespace Disco\Engine;
9
10 /**
11 * Prevents PHP debug notices/warnings from corrupting REST and AJAX JSON responses.
12 *
13 * When WP_DEBUG and WP_DEBUG_DISPLAY are both true, notices from plugins (e.g.
14 * early textdomain loading) are printed before the JSON body, breaking JSON
15 * parsing in the browser. We start an output buffer before plugins_loaded fires
16 * so all stray output is captured, then discard it cleanly before the response
17 * is sent. AJAX handlers call clean() manually before wp_send_json_*.
18 */
19 class OutputBuffer {
20
21 /**
22 * Start output buffering for REST/AJAX requests.
23 * Call this immediately after the Composer autoload is required in disco.php.
24 */
25 public static function start(): void {
26 define( 'DISCO_OB_LEVEL', ob_get_level() ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound
27
28 if ( ! self::is_api_request() ) {
29 return;
30 }
31
32 ob_start();
33
34 // Suppress error display for API requests so notices do not corrupt JSON.
35 // Errors continue to be written to debug.log when WP_DEBUG_LOG is enabled.
36 @ini_set( 'display_errors', '0' ); // phpcs:ignore WordPress.PHP.IniSet.display_errors_Disallowed
37
38 if ( self::is_rest_request() ) {
39 add_filter( 'rest_pre_serve_request', array( __CLASS__, 'handle_rest' ), 1, 4 );
40 }
41 }
42
43 /**
44 * Hooked on rest_pre_serve_request at priority 1.
45 *
46 * Strategy:
47 * Level N = whatever PHP had before disco.php loaded (DISCO_OB_LEVEL)
48 * Level N+1 = our buffer, containing stray debug notices
49 * Level N+2 = WordPress's dispatch buffer (if WP uses ob_start in serve_request)
50 *
51 * We pop WP's dispatch buffer, discard our stray-output buffer, open a fresh
52 * buffer, and return false so WP echoes the JSON body into the clean buffer.
53 * WP's ob_get_clean() then collects only the actual JSON response.
54 *
55 * @param bool $served Whether the request has already been served.
56 * @param \WP_REST_Response $result The response object.
57 * @param \WP_REST_Request $request The current REST request.
58 * @param \WP_REST_Server $server The REST server instance.
59 * @return bool
60 */
61 public static function handle_rest( $served, $result, $request, $server ): bool {
62 if ( $served ) {
63 return $served;
64 }
65
66 $initial_level = DISCO_OB_LEVEL;
67
68 // Pop WP's dispatch buffer (level N+2) if it exists, preserving its content.
69 $wp_dispatch = ob_get_level() > ( $initial_level + 1 ) ? (string) ob_get_clean() : '';
70
71 // Discard our stray-notices buffer (level N+1).
72 if ( ob_get_level() > $initial_level ) {
73 ob_end_clean();
74 }
75
76 // Open a fresh, clean buffer at the level WP expects for its ob_get_clean().
77 ob_start();
78
79 // Restore any output WP produced during dispatch (almost always empty).
80 if ( '' !== $wp_dispatch ) {
81 echo $wp_dispatch; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
82 }
83
84 return false; // Let WP echo the JSON body and collect it via ob_get_clean().
85 }
86
87 /**
88 * Discard all output buffered above DISCO_OB_LEVEL.
89 * Call this in AJAX handlers before wp_send_json_* to strip stray debug output.
90 */
91 public static function clean(): void {
92 if ( ! defined( 'DISCO_OB_LEVEL' ) ) {
93 return;
94 }
95 while ( ob_get_level() > DISCO_OB_LEVEL ) {
96 ob_end_clean();
97 }
98 }
99
100 private static function is_api_request(): bool {
101 return self::is_ajax_request() || self::is_rest_request();
102 }
103
104 private static function is_ajax_request(): bool {
105 return defined( 'DOING_AJAX' ) && DOING_AJAX;
106 }
107
108 private static function is_rest_request(): bool {
109 if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
110 return true;
111 }
112
113 if ( empty( $_SERVER['REQUEST_URI'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
114 return false;
115 }
116
117 // REST_REQUEST is not defined yet at plugin-file load time, so we inspect
118 // the URI directly. rest_get_url_prefix() respects custom REST base slugs.
119 $request_uri = wp_unslash( $_SERVER['REQUEST_URI'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
120 $rest_prefix = function_exists( 'rest_get_url_prefix' ) ? rest_get_url_prefix() : 'wp-json';
121
122 return false !== strpos( $request_uri, '/' . $rest_prefix . '/' );
123 }
124 }
125