1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | /** |
||
6 | * Wrapper around Nyholm\Psr7 library with a few helper methods and a basic emitter. |
||
7 | * |
||
8 | * For use in WordPress during ajax calls. |
||
9 | * |
||
10 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||
11 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||
12 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||
13 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||
14 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||
15 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||
16 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||
17 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||
18 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||
19 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||
20 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||
21 | * |
||
22 | * @author Glynn Quelch <[email protected]> |
||
23 | * @license http://www.opensource.org/licenses/mit-license.html MIT License |
||
24 | * @package PinkCrab\HTTP |
||
25 | */ |
||
26 | |||
27 | namespace PinkCrab\HTTP; |
||
28 | |||
29 | use RuntimeException; |
||
30 | use WP_HTTP_Response; |
||
31 | use Nyholm\Psr7\Stream; |
||
32 | use Nyholm\Psr7\Request; |
||
33 | use Nyholm\Psr7\Response; |
||
34 | use InvalidArgumentException; |
||
35 | use Psr\Http\Message\UriInterface; |
||
36 | use Nyholm\Psr7\Factory\Psr17Factory; |
||
37 | use Psr\Http\Message\StreamInterface; |
||
38 | use Psr\Http\Message\RequestInterface; |
||
39 | use Psr\Http\Message\ResponseInterface; |
||
40 | use Nyholm\Psr7Server\ServerRequestCreator; |
||
41 | use Psr\Http\Message\ServerRequestInterface; |
||
42 | |||
43 | class HTTP { |
||
44 | |||
45 | /** |
||
46 | * Returns the current request from glbals |
||
47 | * |
||
48 | * @uses Psr17Factory::class |
||
49 | * @uses ServerRequestCreator::class |
||
50 | * |
||
51 | * @return ServerRequestInterface |
||
52 | */ |
||
53 | public function request_from_globals(): ServerRequestInterface { |
||
54 | |||
55 | $psr17_factory = new Psr17Factory(); |
||
56 | |||
57 | return ( new ServerRequestCreator( |
||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||
58 | $psr17_factory, |
||
59 | $psr17_factory, |
||
60 | $psr17_factory, |
||
61 | $psr17_factory |
||
62 | ) )->fromGlobals() |
||
63 | ->withBody( $this->stream_from_scalar( $_POST ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing |
||
64 | } |
||
65 | |||
66 | /** |
||
67 | * Wrapper for making a PS7 request. |
||
68 | * |
||
69 | * @uses Nyholm\Psr7::Request() |
||
70 | * @param string $method HTTP method |
||
71 | * @param string|UriInterface $uri URI |
||
72 | * @param array<string, string> $headers Request headers |
||
73 | * @param string|resource|StreamInterface|null $body Request body |
||
74 | * @param string $version Protocol version |
||
75 | * |
||
76 | * @return RequestInterface |
||
77 | */ |
||
78 | public function psr7_request( |
||
79 | string $method, |
||
80 | $uri, |
||
81 | array $headers = array(), |
||
82 | $body = null, |
||
83 | string $version = '1.1' |
||
84 | ): RequestInterface { |
||
85 | return new Request( $method, $uri, $headers, $body, $version ); |
||
86 | } |
||
87 | |||
88 | /** |
||
89 | * Returns a PS7 Response object. |
||
90 | * |
||
91 | * @param array<string, string>|string|resource|StreamInterface|null $body The response body. |
||
92 | * @param integer $status The response status. |
||
93 | * @param array<string, string> $headers The response headers. |
||
94 | * @param string $version The response version. |
||
95 | * @param string|null $reason The response reason. |
||
96 | * |
||
97 | * @return ResponseInterface |
||
98 | */ |
||
99 | public function psr7_response( |
||
100 | $body = null, |
||
101 | int $status = 200, |
||
102 | array $headers = array(), |
||
103 | string $version = '1.1', |
||
104 | ?string $reason = null |
||
105 | ): ResponseInterface { |
||
106 | // Json Encode if body is array or object. |
||
107 | if ( is_array( $body ) || is_object( $body ) ) { |
||
108 | $body = wp_json_encode( $body ); |
||
109 | } |
||
110 | |||
111 | // If body is false, pass as null. @phpstan |
||
112 | return new Response( $status, $headers, $body ?: null, $version, $reason ); |
||
113 | } |
||
114 | |||
115 | /** |
||
116 | * Returns a WP_Rest_Response |
||
117 | * |
||
118 | * @param array<string, string>|object|string|null $data The response data. |
||
119 | * @param integer $status The response status. |
||
120 | * @param array<string, string> $headers The response headers. |
||
121 | * |
||
122 | * @return WP_HTTP_Response |
||
123 | */ |
||
124 | public function wp_response( |
||
125 | $data = null, |
||
126 | int $status = 200, |
||
127 | array $headers = array() |
||
128 | ): WP_HTTP_Response { |
||
129 | return new WP_HTTP_Response( $data, $status, $headers ); |
||
130 | } |
||
131 | |||
132 | /** |
||
133 | * Emits either a PS7 or WP_HTTP Response. |
||
134 | * |
||
135 | * @param ResponseInterface|WP_HTTP_Response|object $response The response to emit. |
||
136 | * |
||
137 | * @return void |
||
138 | * |
||
139 | * @throws InvalidArgumentException If response is not a valid type. |
||
140 | */ |
||
141 | public function emit_response( $response ): void { |
||
142 | |||
143 | // Throw if not a valid response. |
||
144 | if ( ! $response instanceof ResponseInterface |
||
145 | && ! $response instanceof WP_HTTP_Response ) { |
||
146 | throw new InvalidArgumentException( 'Only ResponseInterface & WP_REST_Response responses can be emitted.' ); |
||
147 | } |
||
148 | |||
149 | // Based on type, emit the response. |
||
150 | if ( $response instanceof ResponseInterface ) { |
||
151 | $this->emit_psr7_response( $response ); |
||
152 | } else { |
||
153 | $this->emit_wp_response( $response ); |
||
154 | } |
||
155 | } |
||
156 | |||
157 | /** |
||
158 | * Emits a PSR7 response. |
||
159 | * |
||
160 | * @param ResponseInterface $response |
||
161 | * @return void |
||
162 | */ |
||
163 | public function emit_psr7_response( ResponseInterface $response ): void { |
||
164 | |||
165 | // If headers sent, throw headers already sent. |
||
166 | $this->headers_sent(); |
||
167 | |||
168 | // Set Set status line.. |
||
169 | $status_line = sprintf( |
||
170 | 'HTTP/%s %s %s', |
||
171 | $response->getProtocolVersion(), |
||
172 | $response->getStatusCode(), |
||
173 | $response->getReasonPhrase() |
||
174 | ); |
||
175 | header( $status_line, true ); |
||
176 | |||
177 | // Append headers. |
||
178 | foreach ( $this->headers_with_json( $response->getHeaders() ) |
||
179 | as $name => $values ) { |
||
180 | |||
181 | // If values are an array, join. |
||
182 | $values = is_array( $values ) ? join( ',', $values ) : (string) $values; |
||
183 | |||
184 | $response_header = sprintf( '%s: %s', $name, $values ); |
||
185 | header( $response_header, false ); |
||
186 | } |
||
187 | |||
188 | // Emit body. |
||
189 | echo $response->getBody(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
||
190 | return; // phpcs:ignore Squiz.PHP.NonExecutableCode.ReturnNotRequired |
||
191 | } |
||
192 | |||
193 | /** |
||
194 | * Emits a WP_HTTP Response. |
||
195 | * |
||
196 | * @param WP_HTTP_Response $response |
||
197 | * @return void |
||
198 | */ |
||
199 | public function emit_wp_response( WP_HTTP_Response $response ): void { |
||
200 | |||
201 | // If headers sent, throw headers already sent. |
||
202 | $this->headers_sent(); |
||
203 | |||
204 | // Append headers. |
||
205 | foreach ( $this->headers_with_json( $response->get_headers() ) |
||
206 | as $name => $values ) { |
||
207 | $values = is_array( $values ) ? join( ',', $values ) : (string) $values; |
||
208 | |||
209 | $header = sprintf( '%s: %s', $name, $values ); |
||
210 | |||
211 | // Set the headers. |
||
212 | header( $header, false ); |
||
213 | } |
||
214 | |||
215 | // Emit body. |
||
216 | $body = $response->get_data(); |
||
217 | print is_string( $body ) ? $body : wp_json_encode( $body ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
||
218 | return; // phpcs:ignore Squiz.PHP.NonExecutableCode.ReturnNotRequired |
||
219 | } |
||
220 | |||
221 | /** |
||
222 | * Adds the JSON content type header if no header set. |
||
223 | * |
||
224 | * @param array<string, mixed> $headers |
||
225 | * @return array<string, mixed> |
||
226 | */ |
||
227 | public function headers_with_json( array $headers = array() ): array { |
||
228 | if ( ! array_key_exists( 'Content-Type', $headers ) ) { |
||
229 | $headers['Content-Type'] = 'application/json; charset=' . get_option( 'blog_charset' ); |
||
230 | } |
||
231 | return $headers; |
||
232 | } |
||
233 | |||
234 | /** |
||
235 | * Throws RunTime error if headers sent. |
||
236 | * |
||
237 | * @return void |
||
238 | * @throws RuntimeException |
||
239 | */ |
||
240 | protected function headers_sent(): void { |
||
241 | if ( headers_sent() ) { |
||
242 | throw new RuntimeException( 'Headers were already sent. The response could not be emitted!' ); |
||
243 | } |
||
244 | } |
||
245 | |||
246 | /** |
||
247 | * Wraps any value which can be json encoded in a StreamInterface |
||
248 | * |
||
249 | * @param string|integer|float|object|array<mixed> $data |
||
250 | * @return \Psr\Http\Message\StreamInterface |
||
251 | */ |
||
252 | public function stream_from_scalar( $data ): StreamInterface { |
||
253 | return Stream::create( json_encode( $data ) ?: '' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode |
||
254 | } |
||
255 | } |
||
256 |