Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.
   1  <?php
   2  
   3  declare(strict_types=1);
   4  
   5  namespace GuzzleHttp\Psr7;
   6  
   7  use InvalidArgumentException;
   8  use Psr\Http\Message\ServerRequestInterface;
   9  use Psr\Http\Message\StreamInterface;
  10  use Psr\Http\Message\UploadedFileInterface;
  11  use Psr\Http\Message\UriInterface;
  12  
  13  /**
  14   * Server-side HTTP request
  15   *
  16   * Extends the Request definition to add methods for accessing incoming data,
  17   * specifically server parameters, cookies, matched path parameters, query
  18   * string arguments, body parameters, and upload file information.
  19   *
  20   * "Attributes" are discovered via decomposing the request (and usually
  21   * specifically the URI path), and typically will be injected by the application.
  22   *
  23   * Requests are considered immutable; all methods that might change state are
  24   * implemented such that they retain the internal state of the current
  25   * message and return a new instance that contains the changed state.
  26   */
  27  class ServerRequest extends Request implements ServerRequestInterface
  28  {
  29      /**
  30       * @var array
  31       */
  32      private $attributes = [];
  33  
  34      /**
  35       * @var array
  36       */
  37      private $cookieParams = [];
  38  
  39      /**
  40       * @var array|object|null
  41       */
  42      private $parsedBody;
  43  
  44      /**
  45       * @var array
  46       */
  47      private $queryParams = [];
  48  
  49      /**
  50       * @var array
  51       */
  52      private $serverParams;
  53  
  54      /**
  55       * @var array
  56       */
  57      private $uploadedFiles = [];
  58  
  59      /**
  60       * @param string                               $method       HTTP method
  61       * @param string|UriInterface                  $uri          URI
  62       * @param array<string, string|string[]>       $headers      Request headers
  63       * @param string|resource|StreamInterface|null $body         Request body
  64       * @param string                               $version      Protocol version
  65       * @param array                                $serverParams Typically the $_SERVER superglobal
  66       */
  67      public function __construct(
  68          string $method,
  69          $uri,
  70          array $headers = [],
  71          $body = null,
  72          string $version = '1.1',
  73          array $serverParams = []
  74      ) {
  75          $this->serverParams = $serverParams;
  76  
  77          parent::__construct($method, $uri, $headers, $body, $version);
  78      }
  79  
  80      /**
  81       * Return an UploadedFile instance array.
  82       *
  83       * @param array $files An array which respect $_FILES structure
  84       *
  85       * @throws InvalidArgumentException for unrecognized values
  86       */
  87      public static function normalizeFiles(array $files): array
  88      {
  89          $normalized = [];
  90  
  91          foreach ($files as $key => $value) {
  92              if ($value instanceof UploadedFileInterface) {
  93                  $normalized[$key] = $value;
  94              } elseif (is_array($value) && isset($value['tmp_name'])) {
  95                  $normalized[$key] = self::createUploadedFileFromSpec($value);
  96              } elseif (is_array($value)) {
  97                  $normalized[$key] = self::normalizeFiles($value);
  98                  continue;
  99              } else {
 100                  throw new InvalidArgumentException('Invalid value in files specification');
 101              }
 102          }
 103  
 104          return $normalized;
 105      }
 106  
 107      /**
 108       * Create and return an UploadedFile instance from a $_FILES specification.
 109       *
 110       * If the specification represents an array of values, this method will
 111       * delegate to normalizeNestedFileSpec() and return that return value.
 112       *
 113       * @param array $value $_FILES struct
 114       *
 115       * @return UploadedFileInterface|UploadedFileInterface[]
 116       */
 117      private static function createUploadedFileFromSpec(array $value)
 118      {
 119          if (is_array($value['tmp_name'])) {
 120              return self::normalizeNestedFileSpec($value);
 121          }
 122  
 123          return new UploadedFile(
 124              $value['tmp_name'],
 125              (int) $value['size'],
 126              (int) $value['error'],
 127              $value['name'],
 128              $value['type']
 129          );
 130      }
 131  
 132      /**
 133       * Normalize an array of file specifications.
 134       *
 135       * Loops through all nested files and returns a normalized array of
 136       * UploadedFileInterface instances.
 137       *
 138       * @return UploadedFileInterface[]
 139       */
 140      private static function normalizeNestedFileSpec(array $files = []): array
 141      {
 142          $normalizedFiles = [];
 143  
 144          foreach (array_keys($files['tmp_name']) as $key) {
 145              $spec = [
 146                  'tmp_name' => $files['tmp_name'][$key],
 147                  'size'     => $files['size'][$key],
 148                  'error'    => $files['error'][$key],
 149                  'name'     => $files['name'][$key],
 150                  'type'     => $files['type'][$key],
 151              ];
 152              $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec);
 153          }
 154  
 155          return $normalizedFiles;
 156      }
 157  
 158      /**
 159       * Return a ServerRequest populated with superglobals:
 160       * $_GET
 161       * $_POST
 162       * $_COOKIE
 163       * $_FILES
 164       * $_SERVER
 165       */
 166      public static function fromGlobals(): ServerRequestInterface
 167      {
 168          $method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
 169          $headers = getallheaders();
 170          $uri = self::getUriFromGlobals();
 171          $body = new CachingStream(new LazyOpenStream('php://input', 'r+'));
 172          $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1';
 173  
 174          $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER);
 175  
 176          return $serverRequest
 177              ->withCookieParams($_COOKIE)
 178              ->withQueryParams($_GET)
 179              ->withParsedBody($_POST)
 180              ->withUploadedFiles(self::normalizeFiles($_FILES));
 181      }
 182  
 183      private static function extractHostAndPortFromAuthority(string $authority): array
 184      {
 185          $uri = 'http://' . $authority;
 186          $parts = parse_url($uri);
 187          if (false === $parts) {
 188              return [null, null];
 189          }
 190  
 191          $host = $parts['host'] ?? null;
 192          $port = $parts['port'] ?? null;
 193  
 194          return [$host, $port];
 195      }
 196  
 197      /**
 198       * Get a Uri populated with values from $_SERVER.
 199       */
 200      public static function getUriFromGlobals(): UriInterface
 201      {
 202          $uri = new Uri('');
 203  
 204          $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http');
 205  
 206          $hasPort = false;
 207          if (isset($_SERVER['HTTP_HOST'])) {
 208              [$host, $port] = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']);
 209              if ($host !== null) {
 210                  $uri = $uri->withHost($host);
 211              }
 212  
 213              if ($port !== null) {
 214                  $hasPort = true;
 215                  $uri = $uri->withPort($port);
 216              }
 217          } elseif (isset($_SERVER['SERVER_NAME'])) {
 218              $uri = $uri->withHost($_SERVER['SERVER_NAME']);
 219          } elseif (isset($_SERVER['SERVER_ADDR'])) {
 220              $uri = $uri->withHost($_SERVER['SERVER_ADDR']);
 221          }
 222  
 223          if (!$hasPort && isset($_SERVER['SERVER_PORT'])) {
 224              $uri = $uri->withPort($_SERVER['SERVER_PORT']);
 225          }
 226  
 227          $hasQuery = false;
 228          if (isset($_SERVER['REQUEST_URI'])) {
 229              $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2);
 230              $uri = $uri->withPath($requestUriParts[0]);
 231              if (isset($requestUriParts[1])) {
 232                  $hasQuery = true;
 233                  $uri = $uri->withQuery($requestUriParts[1]);
 234              }
 235          }
 236  
 237          if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) {
 238              $uri = $uri->withQuery($_SERVER['QUERY_STRING']);
 239          }
 240  
 241          return $uri;
 242      }
 243  
 244      public function getServerParams(): array
 245      {
 246          return $this->serverParams;
 247      }
 248  
 249      public function getUploadedFiles(): array
 250      {
 251          return $this->uploadedFiles;
 252      }
 253  
 254      public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface
 255      {
 256          $new = clone $this;
 257          $new->uploadedFiles = $uploadedFiles;
 258  
 259          return $new;
 260      }
 261  
 262      public function getCookieParams(): array
 263      {
 264          return $this->cookieParams;
 265      }
 266  
 267      public function withCookieParams(array $cookies): ServerRequestInterface
 268      {
 269          $new = clone $this;
 270          $new->cookieParams = $cookies;
 271  
 272          return $new;
 273      }
 274  
 275      public function getQueryParams(): array
 276      {
 277          return $this->queryParams;
 278      }
 279  
 280      public function withQueryParams(array $query): ServerRequestInterface
 281      {
 282          $new = clone $this;
 283          $new->queryParams = $query;
 284  
 285          return $new;
 286      }
 287  
 288      /**
 289       * {@inheritdoc}
 290       *
 291       * @return array|object|null
 292       */
 293      public function getParsedBody()
 294      {
 295          return $this->parsedBody;
 296      }
 297  
 298      public function withParsedBody($data): ServerRequestInterface
 299      {
 300          $new = clone $this;
 301          $new->parsedBody = $data;
 302  
 303          return $new;
 304      }
 305  
 306      public function getAttributes(): array
 307      {
 308          return $this->attributes;
 309      }
 310  
 311      /**
 312       * {@inheritdoc}
 313       *
 314       * @return mixed
 315       */
 316      public function getAttribute($attribute, $default = null)
 317      {
 318          if (false === array_key_exists($attribute, $this->attributes)) {
 319              return $default;
 320          }
 321  
 322          return $this->attributes[$attribute];
 323      }
 324  
 325      public function withAttribute($attribute, $value): ServerRequestInterface
 326      {
 327          $new = clone $this;
 328          $new->attributes[$attribute] = $value;
 329  
 330          return $new;
 331      }
 332  
 333      public function withoutAttribute($attribute): ServerRequestInterface
 334      {
 335          if (false === array_key_exists($attribute, $this->attributes)) {
 336              return $this;
 337          }
 338  
 339          $new = clone $this;
 340          unset($new->attributes[$attribute]);
 341  
 342          return $new;
 343      }
 344  }