From c7c4d37a601e17eeb3257858e0dad5b04fcf6fa9 Mon Sep 17 00:00:00 2001 From: Lyrisbee Date: Tue, 19 Dec 2023 18:17:54 +0800 Subject: [PATCH 1/5] feat: add media endpoint --- composer.json | 1 + .../CannotOpenResourceException.php | 10 ++ src/Exceptions/InvalidPostIdException.php | 10 ++ src/Facades/WordPress.php | 2 + src/Objects/Media.php | 76 ++++++++++ src/Objects/MediaDetails.php | 22 +++ src/Requests/Media.php | 137 ++++++++++++++++++ src/Requests/Request.php | 18 +++ src/WordPress.php | 10 ++ 9 files changed, 286 insertions(+) create mode 100644 src/Exceptions/CannotOpenResourceException.php create mode 100644 src/Exceptions/InvalidPostIdException.php create mode 100644 src/Objects/Media.php create mode 100644 src/Objects/MediaDetails.php create mode 100644 src/Requests/Media.php diff --git a/composer.json b/composer.json index e37d6a1..36f8926 100644 --- a/composer.json +++ b/composer.json @@ -4,6 +4,7 @@ "license": "MIT", "type": "library", "require": { + "ext-fileinfo": "*", "php": "^8.1", "illuminate/http": "^10.0", "illuminate/support": "^10.0", diff --git a/src/Exceptions/CannotOpenResourceException.php b/src/Exceptions/CannotOpenResourceException.php new file mode 100644 index 0000000..710e1e4 --- /dev/null +++ b/src/Exceptions/CannotOpenResourceException.php @@ -0,0 +1,10 @@ + + */ + public array $meta; + + public string $alt_text; + + public string $media_type; + + public string $mime_type; + + public MediaDetails $media_details; + + public ?int $post; + + public string $source_url; + + public static function from(stdClass $data): static + { + $data->guid = Render::from($data->guid); + + $data->title = Render::from($data->title); + + $data->description = Render::from($data->description); + + $data->caption = Render::from($data->caption); + + $data->media_details = MediaDetails::from($data->media_details); + + return parent::from($data); + } +} diff --git a/src/Objects/MediaDetails.php b/src/Objects/MediaDetails.php new file mode 100644 index 0000000..d68f4d3 --- /dev/null +++ b/src/Objects/MediaDetails.php @@ -0,0 +1,22 @@ + + * + * @throws WordPressException + */ + public function list(): array + { + $data = $this->request('get', '/media'); + + if (!is_array($data)) { + throw $this->unexpectedValueException(); + } + + return array_map( + fn ($data) => MediaObject::from($data), + $data, + ); + } + + /** + * https://developer.wordpress.org/rest-api/reference/media/#create-a-media-item + * + * @param array $arguments + * + * @throws WordPressException + */ + public function create(string $path, array $arguments): MediaObject + { + $mime = mime_content_type($path); + + $filename = basename($path); + + $file = fopen($path, 'r'); + + if ($file === false || $mime === false) { + $error = WordPressError::from((object) [ + 'code' => '400', + 'message' => 'Can\'t open resource.', + 'data' => (object) [], + ]); + + throw new CannotOpenResourceException($error, 400); + } + + $contentDisposition = sprintf('attachment; filename="%s"', $filename); + + $uri = sprintf('/media?%s', http_build_query($arguments)); + + $data = $this->request( + method: 'post', + path: $uri, + headers: [ + 'Content-Disposition' => $contentDisposition, + ], + body: [ + 'resource' => $file, + 'mime' => $mime, + ] + ); + + if (is_array($data)) { + throw $this->unexpectedValueException(); + } + + return MediaObject::from($data); + } + + /** + * https://developer.wordpress.org/rest-api/reference/media/#retrieve-a-media-item + * + * + * @throws WordPressException + */ + public function retrieve(int $mediaId, string $context = 'view'): MediaObject + { + $uri = sprintf('/media/%d', $mediaId); + + $data = $this->request('get', $uri, [ + 'context' => $context, + ]); + + if (is_array($data)) { + throw $this->unexpectedValueException(); + } + + return MediaObject::from($data); + } + + /** + * https://developer.wordpress.org/rest-api/reference/media/#update-a-media-item + * + * @param array $arguments + * + * @throws WordPressException + */ + public function update(int $mediaId, array $arguments): MediaObject + { + $uri = sprintf('/media/%d', $mediaId); + + $data = $this->request('patch', $uri, $arguments); + + if (is_array($data)) { + throw $this->unexpectedValueException(); + } + + return MediaObject::from($data); + } + + /** + * https://developer.wordpress.org/rest-api/reference/media/#delete-a-media-item + * + * @throws WordPressException + */ + public function delete(int $mediaId): bool + { + $uri = sprintf('/media/%s', $mediaId); + + return $this->request('delete', $uri, [ + 'force' => true, + ]); + } +} diff --git a/src/Requests/Request.php b/src/Requests/Request.php index 76fca02..44267a2 100644 --- a/src/Requests/Request.php +++ b/src/Requests/Request.php @@ -13,6 +13,7 @@ use Storipress\WordPress\Exceptions\CannotUpdateException; use Storipress\WordPress\Exceptions\DuplicateTermSlugException; use Storipress\WordPress\Exceptions\ForbiddenException; +use Storipress\WordPress\Exceptions\InvalidPostIdException; use Storipress\WordPress\Exceptions\NoRouteException; use Storipress\WordPress\Exceptions\NotFoundException; use Storipress\WordPress\Exceptions\TermExistsException; @@ -37,6 +38,11 @@ public function __construct( * @param 'get'|'post'|'patch'|'delete' $method * @param non-empty-string $path * @param array $options + * @param array $headers + * @param array{ + * resource: resource, + * mime: string + * }|array{} $body * @return ($method is 'delete' ? bool : stdClass|array) * * @throws UnexpectedValueException|WordPressException @@ -45,6 +51,8 @@ protected function request( string $method, string $path, array $options = [], + array $headers = [], + array $body = [], ): stdClass|array|bool { $http = $this->app->http ->withoutVerifying() @@ -55,6 +63,15 @@ protected function request( $http->withUserAgent($this->app->userAgent()); } + if (!empty($body)) { + // @phpstan-ignore-next-line + $http->withBody($body['resource'], $body['mime']); + } + + if (!empty($headers)) { + $http->withHeaders($headers); + } + $response = $http->{$method}( $this->getUrl( $path, @@ -124,6 +141,7 @@ protected function error(stdClass $payload, string $message, int $status): void 'rest_cannot_create' => new CannotCreateException($error, $status), 'rest_cannot_update' => new CannotUpdateException($error, $status), 'rest_no_route' => new NoRouteException($error, $status), + 'rest_post_invalid_id' => new InvalidPostIdException($error, $status), default => new UnknownException($error, $status), }; } diff --git a/src/WordPress.php b/src/WordPress.php index fb31a66..c2cee5d 100644 --- a/src/WordPress.php +++ b/src/WordPress.php @@ -7,6 +7,7 @@ use Illuminate\Http\Client\Factory; use Storipress\WordPress\Requests\Category; use Storipress\WordPress\Requests\GeneralRequest; +use Storipress\WordPress\Requests\Media; use Storipress\WordPress\Requests\Post; use Storipress\WordPress\Requests\Tag; use Storipress\WordPress\Requests\User; @@ -23,6 +24,8 @@ class WordPress protected readonly Tag $tag; + protected readonly Media $media; + protected string $site; protected string $username; @@ -47,6 +50,8 @@ public function __construct( $this->category = new Category($this); $this->tag = new Tag($this); + + $this->media = new Media($this); } public function instance(): static @@ -150,4 +155,9 @@ public function tag(): Tag { return $this->tag; } + + public function media(): Media + { + return $this->media; + } } From c38b73068da307f001d279fedd59eaa9ea2b7611 Mon Sep 17 00:00:00 2001 From: Lyrisbee Date: Tue, 19 Dec 2023 18:20:17 +0800 Subject: [PATCH 2/5] fix: composer normalize --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 36f8926..bbd3cc4 100644 --- a/composer.json +++ b/composer.json @@ -4,8 +4,8 @@ "license": "MIT", "type": "library", "require": { - "ext-fileinfo": "*", "php": "^8.1", + "ext-fileinfo": "*", "illuminate/http": "^10.0", "illuminate/support": "^10.0", "justinrainbow/json-schema": "^5.2" From 55f8a96f41122c49cd1edc3756cfe7419078564d Mon Sep 17 00:00:00 2001 From: Lyrisbee Date: Wed, 20 Dec 2023 10:06:02 +0800 Subject: [PATCH 3/5] chore: apply changes --- ...on.php => UnexpectedResourceException.php} | 2 +- src/Requests/Media.php | 35 ++++------------- src/Requests/Request.php | 38 ++++++++++++------- 3 files changed, 33 insertions(+), 42 deletions(-) rename src/Exceptions/{CannotOpenResourceException.php => UnexpectedResourceException.php} (59%) diff --git a/src/Exceptions/CannotOpenResourceException.php b/src/Exceptions/UnexpectedResourceException.php similarity index 59% rename from src/Exceptions/CannotOpenResourceException.php rename to src/Exceptions/UnexpectedResourceException.php index 710e1e4..50ccf34 100644 --- a/src/Exceptions/CannotOpenResourceException.php +++ b/src/Exceptions/UnexpectedResourceException.php @@ -4,7 +4,7 @@ namespace Storipress\WordPress\Exceptions; -class CannotOpenResourceException extends WordPressException +class UnexpectedResourceException extends WordPressException { // } diff --git a/src/Requests/Media.php b/src/Requests/Media.php index 6a7b445..2e53c71 100644 --- a/src/Requests/Media.php +++ b/src/Requests/Media.php @@ -4,10 +4,9 @@ namespace Storipress\WordPress\Requests; -use Storipress\WordPress\Exceptions\CannotOpenResourceException; +use Illuminate\Http\UploadedFile; use Storipress\WordPress\Exceptions\WordPressException; use Storipress\WordPress\Objects\Media as MediaObject; -use Storipress\WordPress\Objects\WordPressError; class Media extends Request { @@ -39,38 +38,20 @@ public function list(): array * * @throws WordPressException */ - public function create(string $path, array $arguments): MediaObject + public function create(UploadedFile $file, array $arguments): MediaObject { - $mime = mime_content_type($path); - - $filename = basename($path); - - $file = fopen($path, 'r'); - - if ($file === false || $mime === false) { - $error = WordPressError::from((object) [ - 'code' => '400', - 'message' => 'Can\'t open resource.', - 'data' => (object) [], - ]); - - throw new CannotOpenResourceException($error, 400); - } - - $contentDisposition = sprintf('attachment; filename="%s"', $filename); - $uri = sprintf('/media?%s', http_build_query($arguments)); $data = $this->request( method: 'post', path: $uri, - headers: [ - 'Content-Disposition' => $contentDisposition, + options: [ + 'file' => [ + 'name' => $file->getFilename(), + 'resource' => $file->getContent(), + 'mime' => $file->getMimeType(), + ], ], - body: [ - 'resource' => $file, - 'mime' => $mime, - ] ); if (is_array($data)) { diff --git a/src/Requests/Request.php b/src/Requests/Request.php index 44267a2..393ed0f 100644 --- a/src/Requests/Request.php +++ b/src/Requests/Request.php @@ -18,6 +18,7 @@ use Storipress\WordPress\Exceptions\NotFoundException; use Storipress\WordPress\Exceptions\TermExistsException; use Storipress\WordPress\Exceptions\UnauthorizedException; +use Storipress\WordPress\Exceptions\UnexpectedResourceException; use Storipress\WordPress\Exceptions\UnexpectedValueException; use Storipress\WordPress\Exceptions\UnknownException; use Storipress\WordPress\Exceptions\WordPressException; @@ -37,12 +38,7 @@ public function __construct( /** * @param 'get'|'post'|'patch'|'delete' $method * @param non-empty-string $path - * @param array $options - * @param array $headers - * @param array{ - * resource: resource, - * mime: string - * }|array{} $body + * @param array $options * @return ($method is 'delete' ? bool : stdClass|array) * * @throws UnexpectedValueException|WordPressException @@ -51,8 +47,6 @@ protected function request( string $method, string $path, array $options = [], - array $headers = [], - array $body = [], ): stdClass|array|bool { $http = $this->app->http ->withoutVerifying() @@ -63,13 +57,29 @@ protected function request( $http->withUserAgent($this->app->userAgent()); } - if (!empty($body)) { - // @phpstan-ignore-next-line - $http->withBody($body['resource'], $body['mime']); - } + if (isset($options['file'])) { + $filename = data_get($options, 'file.name'); + + $resource = data_get($options, 'file.resource'); + + $mime = data_get($options, 'file.mime'); + + if (!is_string($filename) || !is_string($resource) || !is_string($mime)) { + throw new UnexpectedResourceException(WordPressError::from((object) [ + 'code' => '400', + 'message' => 'Unexpected resource value.', + 'data' => (object) [ + 'file' => $options['file'], + ], + ]), 400); + } + + $contentDisposition = sprintf('attachment; filename="%s"', $filename); + + $http->withHeader('Content-Disposition', $contentDisposition) + ->withBody($resource, $mime); - if (!empty($headers)) { - $http->withHeaders($headers); + unset($options['file']); } $response = $http->{$method}( From 1a23dbc1bf837a0376d5d190980f5dba771b9087 Mon Sep 17 00:00:00 2001 From: Lyrisbee Date: Wed, 20 Dec 2023 10:21:19 +0800 Subject: [PATCH 4/5] chore: apply changes --- src/Requests/Media.php | 16 +++------------- src/Requests/Request.php | 25 +++---------------------- 2 files changed, 6 insertions(+), 35 deletions(-) diff --git a/src/Requests/Media.php b/src/Requests/Media.php index 2e53c71..18405b4 100644 --- a/src/Requests/Media.php +++ b/src/Requests/Media.php @@ -40,19 +40,9 @@ public function list(): array */ public function create(UploadedFile $file, array $arguments): MediaObject { - $uri = sprintf('/media?%s', http_build_query($arguments)); - - $data = $this->request( - method: 'post', - path: $uri, - options: [ - 'file' => [ - 'name' => $file->getFilename(), - 'resource' => $file->getContent(), - 'mime' => $file->getMimeType(), - ], - ], - ); + $arguments['file'] = $file; + + $data = $this->request('post', '/media', $arguments); if (is_array($data)) { throw $this->unexpectedValueException(); diff --git a/src/Requests/Request.php b/src/Requests/Request.php index 393ed0f..a89608b 100644 --- a/src/Requests/Request.php +++ b/src/Requests/Request.php @@ -5,6 +5,7 @@ namespace Storipress\WordPress\Requests; use Illuminate\Http\Client\Response; +use Illuminate\Http\UploadedFile; use JsonSchema\Constraints\Constraint; use JsonSchema\Validator; use stdClass; @@ -18,7 +19,6 @@ use Storipress\WordPress\Exceptions\NotFoundException; use Storipress\WordPress\Exceptions\TermExistsException; use Storipress\WordPress\Exceptions\UnauthorizedException; -use Storipress\WordPress\Exceptions\UnexpectedResourceException; use Storipress\WordPress\Exceptions\UnexpectedValueException; use Storipress\WordPress\Exceptions\UnknownException; use Storipress\WordPress\Exceptions\WordPressException; @@ -57,27 +57,8 @@ protected function request( $http->withUserAgent($this->app->userAgent()); } - if (isset($options['file'])) { - $filename = data_get($options, 'file.name'); - - $resource = data_get($options, 'file.resource'); - - $mime = data_get($options, 'file.mime'); - - if (!is_string($filename) || !is_string($resource) || !is_string($mime)) { - throw new UnexpectedResourceException(WordPressError::from((object) [ - 'code' => '400', - 'message' => 'Unexpected resource value.', - 'data' => (object) [ - 'file' => $options['file'], - ], - ]), 400); - } - - $contentDisposition = sprintf('attachment; filename="%s"', $filename); - - $http->withHeader('Content-Disposition', $contentDisposition) - ->withBody($resource, $mime); + if (isset($options['file']) && $options['file'] instanceof UploadedFile) { + $http->attach('file', $options['file']->getContent(), $options['file']->getClientOriginalName()); unset($options['file']); } From b6d70a3293ed046369c0edbfc9faeffffc913a2e Mon Sep 17 00:00:00 2001 From: Lyrisbee Date: Wed, 20 Dec 2023 10:22:18 +0800 Subject: [PATCH 5/5] chore: clean code --- src/Exceptions/UnexpectedResourceException.php | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 src/Exceptions/UnexpectedResourceException.php diff --git a/src/Exceptions/UnexpectedResourceException.php b/src/Exceptions/UnexpectedResourceException.php deleted file mode 100644 index 50ccf34..0000000 --- a/src/Exceptions/UnexpectedResourceException.php +++ /dev/null @@ -1,10 +0,0 @@ -