Pink-Crab /
HTTP
| 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
Loading history...
|
|||||
| 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
Loading history...
|
|||||
| 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
Loading history...
|
|||||
| 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 |