PluginProbe
Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More / trunk
Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More vtrunk
trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.10 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 2.0.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 All 47 releases
content-control / classes / Base / Stream.php

Stream.php in Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More trunk, at classes/Base/Stream.php

186 lines 4.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin controller.
4 *
5 * @copyright (c) 2021, Code Atlantic LLC.
6 *
7 * @package ContentControl
8 */
9
10 namespace ContentControl\Base;
11
12 defined( 'ABSPATH' ) || exit;
13
14 /**
15 * HTTP Stream class.
16 *
17 * This class writes the `text/event-stream` wire format, not HTML. HTML
18 * escaping would corrupt SSE framing. Event names are reduced to the
19 * characters permitted by this implementation, non-string payloads are JSON
20 * encoded, and every payload line is emitted as a separate `data:` field.
21 * Content Control uses this only from capability- and nonce-protected
22 * administrative upgrade handlers.
23 */
24 class Stream {
25
26 /**
27 * Stream name.
28 *
29 * @var string
30 */
31 protected $stream_name;
32
33 /**
34 * Version.
35 *
36 * @var string
37 */
38 const VERSION = '1.0.0';
39
40 /**
41 * Stream constructor.
42 *
43 * @param string $stream_name Stream name.
44 */
45 public function __construct( $stream_name = 'stream' ) {
46 $this->stream_name = sanitize_key( $stream_name );
47
48 if ( empty( $this->stream_name ) ) {
49 $this->stream_name = 'stream';
50 }
51 }
52
53 /**
54 * Start SSE stream.
55 *
56 * @return void
57 */
58 public function start() {
59 if ( headers_sent() ) {
60 // Do not start the stream if headers have already been sent.
61 return;
62 }
63
64 // Disable default disconnect checks.
65 ignore_user_abort( true );
66
67 // phpcs:disable WordPress.PHP.IniSet.Risky, WordPress.PHP.NoSilencedErrors.Discouraged
68 @ini_set( 'zlib.output_compression', '0' );
69 @ini_set( 'implicit_flush', '1' );
70
71 @ob_end_clean();
72 set_time_limit( 0 );
73 // phpcs:enable WordPress.PHP.IniSet.Risky, WordPress.PHP.NoSilencedErrors.Discouraged
74
75 $this->send_headers();
76 }
77
78 /**
79 * Send SSE headers.
80 *
81 * @return void
82 */
83 public function send_headers() {
84 header( 'Content-Type: text/event-stream' );
85 header( 'Stream-Name: ' . $this->stream_name );
86 header( 'Cache-Control: no-cache' );
87 header( 'Connection: keep-alive' );
88 // Nginx: unbuffered responses suitable for Comet and HTTP streaming applications.
89 header( 'X-Accel-Buffering: no' );
90 $this->flush_buffers();
91 }
92
93 /**
94 * Flush buffers.
95 *
96 * Uses a micro delay to prevent the stream from flushing too quickly.
97 *
98 * @return void
99 */
100 protected function flush_buffers() {
101 // This is for the buffer achieve the minimum size in order to flush data.
102
103 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
104 echo str_repeat( ' ', 1024 * 8 ) . PHP_EOL;
105
106 flush(); // Unless both are called. Some browsers will still cache.
107
108 // Neccessary to prevent the stream from flushing too quickly.
109 usleep( 1000 );
110 }
111
112 /**
113 * Send general message/data to the client.
114 *
115 * @param mixed $data Data to send.
116 *
117 * @return void
118 */
119 public function send_data( $data ) {
120 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
121 echo $this->format_data( $data );
122 echo PHP_EOL;
123
124 $this->flush_buffers();
125 }
126
127 /**
128 * Send an event to the client.
129 *
130 * @param string $event Event name.
131 * @param mixed $data Data to send.
132 *
133 * @return void
134 */
135 public function send_event( $event, $data = '' ) {
136 $event = preg_replace( '/[^a-zA-Z0-9_.:-]/', '', (string) $event );
137 $event = empty( $event ) ? 'message' : $event;
138
139 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
140 echo 'event: ' . $event . PHP_EOL;
141 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
142 echo $this->format_data( $data );
143 echo PHP_EOL;
144
145 $this->flush_buffers();
146 }
147
148 /**
149 * Format a value as one or more SSE data fields.
150 *
151 * @param mixed $data Data to format.
152 *
153 * @return string
154 */
155 protected function format_data( $data ) {
156 $data = is_string( $data ) ? $data : \wp_json_encode( $data );
157 $data = is_string( $data ) ? $data : '';
158 $data = str_replace( [ "\r\n", "\r" ], "\n", $data );
159 $lines = explode( "\n", $data );
160
161 return implode( PHP_EOL, array_map( static function ( $line ) {
162 return 'data: ' . $line;
163 }, $lines ) ) . PHP_EOL;
164 }
165
166 /**
167 * Send an error to the client.
168 *
169 * @param array{message:string}|string $error Error message.
170 *
171 * @return void
172 */
173 public function send_error( $error ) {
174 $this->send_event( 'error', $error );
175 }
176
177 /**
178 * Check if the connection should abort.
179 *
180 * @return bool
181 */
182 public function should_abort() {
183 return (bool) connection_aborted();
184 }
185 }
186