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 |
||||
0 ignored issues
–
show
Are you sure
is_string($body) ? $body : wp_json_encode($body) of type false|string can be used in print() ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
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' ); |
||||
0 ignored issues
–
show
Are you sure
get_option('blog_charset') of type false|mixed can be used in concatenation ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
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 |