[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/php-ai-client/src/Files/DTO/ -> File.php (source)

   1  <?php
   2  
   3  declare (strict_types=1);
   4  namespace WordPress\AiClient\Files\DTO;
   5  
   6  use WordPress\AiClient\Common\AbstractDataTransferObject;
   7  use WordPress\AiClient\Common\Exception\InvalidArgumentException;
   8  use WordPress\AiClient\Common\Exception\RuntimeException;
   9  use WordPress\AiClient\Files\Enums\FileTypeEnum;
  10  use WordPress\AiClient\Files\ValueObjects\MimeType;
  11  /**
  12   * Represents a file in the AI client.
  13   *
  14   * This DTO automatically detects whether a file is a URL, base64 data, or local file path
  15   * and handles them appropriately.
  16   *
  17   * @since 0.1.0
  18   *
  19   * @phpstan-type FileArrayShape array{
  20   *     fileType: string,
  21   *     url?: string,
  22   *     mimeType: string,
  23   *     base64Data?: string
  24   * }
  25   *
  26   * @extends AbstractDataTransferObject<FileArrayShape>
  27   */
  28  class File extends AbstractDataTransferObject
  29  {
  30      public const KEY_FILE_TYPE = 'fileType';
  31      public const KEY_MIME_TYPE = 'mimeType';
  32      public const KEY_URL = 'url';
  33      public const KEY_BASE64_DATA = 'base64Data';
  34      /**
  35       * @var MimeType The MIME type of the file.
  36       */
  37      private MimeType $mimeType;
  38      /**
  39       * @var FileTypeEnum The type of file storage.
  40       */
  41      private FileTypeEnum $fileType;
  42      /**
  43       * @var string|null The URL for remote files.
  44       */
  45      private ?string $url = null;
  46      /**
  47       * @var string|null The base64 data for inline files.
  48       */
  49      private ?string $base64Data = null;
  50      /**
  51       * Constructor.
  52       *
  53       * @since 0.1.0
  54       *
  55       * @param string $file The file string (URL, base64 data, or local path).
  56       * @param string|null $mimeType The MIME type of the file (optional).
  57       * @throws InvalidArgumentException If the file format is invalid or MIME type cannot be determined.
  58       */
  59      public function __construct(string $file, ?string $mimeType = null)
  60      {
  61          // Detect and process the file type (will set MIME type if possible)
  62          $this->detectAndProcessFile($file, $mimeType);
  63      }
  64      /**
  65       * Detects the file type and processes it accordingly.
  66       *
  67       * @since 0.1.0
  68       *
  69       * @param string $file The file string to process.
  70       * @param string|null $providedMimeType The explicitly provided MIME type.
  71       * @throws InvalidArgumentException If the file format is invalid or MIME type cannot be determined.
  72       */
  73      private function detectAndProcessFile(string $file, ?string $providedMimeType): void
  74      {
  75          // Check if it's a URL
  76          if ($this->isUrl($file)) {
  77              $this->fileType = FileTypeEnum::remote();
  78              $this->url = $file;
  79              $this->mimeType = $this->determineMimeType($providedMimeType, null, $file);
  80              return;
  81          }
  82          // Data URI pattern.
  83          $dataUriPattern = '/^data:(?:([a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*\/[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*' . '(?:;[a-zA-Z0-9\-]+=[a-zA-Z0-9\-]+)*)?;)?base64,([A-Za-z0-9+\/]*={0,2})$/';
  84          // Check if it's a data URI.
  85          if (preg_match($dataUriPattern, $file, $matches)) {
  86              $this->fileType = FileTypeEnum::inline();
  87              $this->base64Data = $matches[2];
  88              // Extract just the base64 data
  89              $extractedMimeType = empty($matches[1]) ? null : $matches[1];
  90              $this->mimeType = $this->determineMimeType($providedMimeType, $extractedMimeType, null);
  91              return;
  92          }
  93          // Check if it's a local file path (before base64 check)
  94          if (file_exists($file) && is_file($file)) {
  95              $this->fileType = FileTypeEnum::inline();
  96              $this->base64Data = $this->convertFileToBase64($file);
  97              $this->mimeType = $this->determineMimeType($providedMimeType, null, $file);
  98              return;
  99          }
 100          // Check if it's plain base64
 101          if (preg_match('/^[A-Za-z0-9+\/]*={0,2}$/', $file)) {
 102              if ($providedMimeType === null) {
 103                  throw new InvalidArgumentException('MIME type is required when providing plain base64 data without data URI format.');
 104              }
 105              $this->fileType = FileTypeEnum::inline();
 106              $this->base64Data = $file;
 107              $this->mimeType = new MimeType($providedMimeType);
 108              return;
 109          }
 110          throw new InvalidArgumentException('Invalid file provided. Expected URL, base64 data, or valid local file path.');
 111      }
 112      /**
 113       * Checks if a string is a valid URL.
 114       *
 115       * @since 0.1.0
 116       *
 117       * @param string $string The string to check.
 118       * @return bool True if the string is a URL.
 119       */
 120      private function isUrl(string $string): bool
 121      {
 122          return filter_var($string, \FILTER_VALIDATE_URL) !== \false && preg_match('/^https?:\/\//i', $string);
 123      }
 124      /**
 125       * Converts a local file to base64.
 126       *
 127       * @since 0.1.0
 128       *
 129       * @param string $filePath The path to the local file.
 130       * @return string The base64-encoded file data.
 131       * @throws RuntimeException If the file cannot be read.
 132       */
 133      private function convertFileToBase64(string $filePath): string
 134      {
 135          $fileContent = @file_get_contents($filePath);
 136          if ($fileContent === \false) {
 137              throw new RuntimeException(sprintf('Unable to read file: %s', $filePath));
 138          }
 139          return base64_encode($fileContent);
 140      }
 141      /**
 142       * Gets the file type.
 143       *
 144       * @since 0.1.0
 145       *
 146       * @return FileTypeEnum The file type.
 147       */
 148      public function getFileType(): FileTypeEnum
 149      {
 150          return $this->fileType;
 151      }
 152      /**
 153       * Checks if the file is an inline file.
 154       *
 155       * @since 0.1.0
 156       *
 157       * @return bool True if the file is inline (base64/data URI).
 158       */
 159      public function isInline(): bool
 160      {
 161          return $this->fileType->isInline();
 162      }
 163      /**
 164       * Checks if the file is a remote file.
 165       *
 166       * @since 0.1.0
 167       *
 168       * @return bool True if the file is remote (URL).
 169       */
 170      public function isRemote(): bool
 171      {
 172          return $this->fileType->isRemote();
 173      }
 174      /**
 175       * Gets the URL for remote files.
 176       *
 177       * @since 0.1.0
 178       *
 179       * @return string|null The URL, or null if not a remote file.
 180       */
 181      public function getUrl(): ?string
 182      {
 183          return $this->url;
 184      }
 185      /**
 186       * Gets the base64-encoded data for inline files.
 187       *
 188       * @since 0.1.0
 189       *
 190       * @return string|null The plain base64-encoded data (without data URI prefix), or null if not an inline file.
 191       */
 192      public function getBase64Data(): ?string
 193      {
 194          return $this->base64Data;
 195      }
 196      /**
 197       * Gets the data as a data URI for inline files.
 198       *
 199       * @since 0.1.0
 200       *
 201       * @return string|null The data URI in format: data:[mimeType];base64,[data], or null if not an inline file.
 202       */
 203      public function getDataUri(): ?string
 204      {
 205          if ($this->base64Data === null) {
 206              return null;
 207          }
 208          return sprintf('data:%s;base64,%s', $this->getMimeType(), $this->base64Data);
 209      }
 210      /**
 211       * Gets the MIME type of the file as a string.
 212       *
 213       * @since 0.1.0
 214       *
 215       * @return string The MIME type string value.
 216       */
 217      public function getMimeType(): string
 218      {
 219          return (string) $this->mimeType;
 220      }
 221      /**
 222       * Gets the MIME type object.
 223       *
 224       * @since 0.1.0
 225       *
 226       * @return MimeType The MIME type object.
 227       */
 228      public function getMimeTypeObject(): MimeType
 229      {
 230          return $this->mimeType;
 231      }
 232      /**
 233       * Checks if the file is a video.
 234       *
 235       * @since 0.1.0
 236       *
 237       * @return bool True if the file is a video.
 238       */
 239      public function isVideo(): bool
 240      {
 241          return $this->mimeType->isVideo();
 242      }
 243      /**
 244       * Checks if the file is an image.
 245       *
 246       * @since 0.1.0
 247       *
 248       * @return bool True if the file is an image.
 249       */
 250      public function isImage(): bool
 251      {
 252          return $this->mimeType->isImage();
 253      }
 254      /**
 255       * Checks if the file is audio.
 256       *
 257       * @since 0.1.0
 258       *
 259       * @return bool True if the file is audio.
 260       */
 261      public function isAudio(): bool
 262      {
 263          return $this->mimeType->isAudio();
 264      }
 265      /**
 266       * Checks if the file is text.
 267       *
 268       * @since 0.1.0
 269       *
 270       * @return bool True if the file is text.
 271       */
 272      public function isText(): bool
 273      {
 274          return $this->mimeType->isText();
 275      }
 276      /**
 277       * Checks if the file is a document.
 278       *
 279       * @since 0.1.0
 280       *
 281       * @return bool True if the file is a document.
 282       */
 283      public function isDocument(): bool
 284      {
 285          return $this->mimeType->isDocument();
 286      }
 287      /**
 288       * Checks if the file is a specific MIME type.
 289       *
 290       * @since 0.1.0
 291       *
 292       * @param string $type The mime type to check (e.g. 'image', 'text', 'video', 'audio').
 293       *
 294       * @return bool True if the file is of the specified type.
 295       */
 296      public function isMimeType(string $type): bool
 297      {
 298          return $this->mimeType->isType($type);
 299      }
 300      /**
 301       * Determines the MIME type from various sources.
 302       *
 303       * @since 0.1.0
 304       *
 305       * @param string|null $providedMimeType The explicitly provided MIME type.
 306       * @param string|null $extractedMimeType The MIME type extracted from data URI.
 307       * @param string|null $pathOrUrl The file path or URL to extract extension from.
 308       * @return MimeType The determined MIME type.
 309       * @throws InvalidArgumentException If MIME type cannot be determined.
 310       */
 311      private function determineMimeType(?string $providedMimeType, ?string $extractedMimeType, ?string $pathOrUrl): MimeType
 312      {
 313          // Prefer explicitly provided MIME type
 314          if ($providedMimeType !== null) {
 315              return new MimeType($providedMimeType);
 316          }
 317          // Use extracted MIME type from data URI
 318          if ($extractedMimeType !== null) {
 319              return new MimeType($extractedMimeType);
 320          }
 321          // Try to determine from file extension
 322          if ($pathOrUrl !== null) {
 323              $parsedUrl = parse_url($pathOrUrl);
 324              $path = $parsedUrl['path'] ?? $pathOrUrl;
 325              // Remove query string and fragment if present
 326              $cleanPath = strtok($path, '?#');
 327              if ($cleanPath === \false) {
 328                  $cleanPath = $path;
 329              }
 330              $extension = pathinfo($cleanPath, \PATHINFO_EXTENSION);
 331              if (!empty($extension)) {
 332                  try {
 333                      return MimeType::fromExtension($extension);
 334                  } catch (InvalidArgumentException $e) {
 335                      // Extension not recognized, continue to error
 336                      unset($e);
 337                  }
 338              }
 339          }
 340          throw new InvalidArgumentException('Unable to determine MIME type. Please provide it explicitly.');
 341      }
 342      /**
 343       * {@inheritDoc}
 344       *
 345       * @since 0.1.0
 346       */
 347      public static function getJsonSchema(): array
 348      {
 349          return ['type' => 'object', 'oneOf' => [['properties' => [self::KEY_FILE_TYPE => ['type' => 'string', 'const' => FileTypeEnum::REMOTE, 'description' => 'The file type.'], self::KEY_MIME_TYPE => ['type' => 'string', 'description' => 'The MIME type of the file.', 'pattern' => '^[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*\/[a-zA-Z0-9]' . '[a-zA-Z0-9!#$&\-\^_+.]*$'], self::KEY_URL => ['type' => 'string', 'format' => 'uri', 'description' => 'The URL to the remote file.']], 'required' => [self::KEY_FILE_TYPE, self::KEY_MIME_TYPE, self::KEY_URL]], ['properties' => [self::KEY_FILE_TYPE => ['type' => 'string', 'const' => FileTypeEnum::INLINE, 'description' => 'The file type.'], self::KEY_MIME_TYPE => ['type' => 'string', 'description' => 'The MIME type of the file.', 'pattern' => '^[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*\/[a-zA-Z0-9]' . '[a-zA-Z0-9!#$&\-\^_+.]*$'], self::KEY_BASE64_DATA => ['type' => 'string', 'description' => 'The base64-encoded file data.']], 'required' => [self::KEY_FILE_TYPE, self::KEY_MIME_TYPE, self::KEY_BASE64_DATA]]]];
 350      }
 351      /**
 352       * {@inheritDoc}
 353       *
 354       * @since 0.1.0
 355       *
 356       * @return FileArrayShape
 357       */
 358      public function toArray(): array
 359      {
 360          $data = [self::KEY_FILE_TYPE => $this->fileType->value, self::KEY_MIME_TYPE => $this->getMimeType()];
 361          if ($this->url !== null) {
 362              $data[self::KEY_URL] = $this->url;
 363          } elseif (!$this->fileType->isRemote() && $this->base64Data !== null) {
 364              $data[self::KEY_BASE64_DATA] = $this->base64Data;
 365          } else {
 366              throw new RuntimeException('File requires either url or base64Data. This should not be a possible condition.');
 367          }
 368          return $data;
 369      }
 370      /**
 371       * {@inheritDoc}
 372       *
 373       * @since 0.1.0
 374       */
 375      public static function fromArray(array $array): self
 376      {
 377          static::validateFromArrayData($array, [self::KEY_FILE_TYPE]);
 378          // Check which properties are set to determine how to construct the File
 379          $mimeType = $array[self::KEY_MIME_TYPE] ?? null;
 380          if (isset($array[self::KEY_URL])) {
 381              return new self($array[self::KEY_URL], $mimeType);
 382          } elseif (isset($array[self::KEY_BASE64_DATA])) {
 383              return new self($array[self::KEY_BASE64_DATA], $mimeType);
 384          } else {
 385              throw new InvalidArgumentException('File requires either url or base64Data.');
 386          }
 387      }
 388      /**
 389       * Performs a deep clone of the file.
 390       *
 391       * This method ensures that the MimeType value object is cloned to prevent
 392       * any shared references between the original and cloned file.
 393       *
 394       * @since 0.4.2
 395       */
 396      public function __clone()
 397      {
 398          $this->mimeType = clone $this->mimeType;
 399      }
 400  }


Generated : Sat Jun 13 09:38:55 2026 Cross-referenced by PHPXref