PluginProbe
Auto Alt Text / 3.0.3
Auto Alt Text v3.0.3
3.0.3 2.8.2 1.3.1 1.3.2 2.0.0 2.1.0 2.1.1 2.2.0 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.5.0 2.5.1 2.5.2 2.5.3 2.6.0 2.6.1 2.7.0 2.8.0 2.8.1 All 28 releases
auto-alt-text / src / App / Infrastructure / Http / WordPressImageFetcher.php

WordPressImageFetcher.php in Auto Alt Text 3.0.3, at src/App/Infrastructure/Http/WordPressImageFetcher.php

133 lines 4.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace AATXT\App\Infrastructure\Http;
6
7 use Exception;
8
9 /**
10 * WordPress implementation of the image fetcher.
11 *
12 * Providers that accept only inline image data cannot download the image
13 * themselves, so its bytes have to travel with the request. When the URL points
14 * inside the uploads directory the file is read straight from disk, which keeps
15 * generation working on local installs, password-protected staging sites and
16 * sites behind HTTP auth - none of which are reachable from the outside.
17 * Anything else falls back to an HTTP request.
18 */
19 class WordPressImageFetcher implements ImageFetcherInterface
20 {
21 /**
22 * Timeout for the HTTP fallback, in seconds.
23 */
24 private const DEFAULT_TIMEOUT = 30;
25
26 /**
27 * Read an image and return its raw bytes base64-encoded.
28 *
29 * @param string $imageUrl The image URL
30 *
31 * @return string The base64-encoded image contents
32 *
33 * @throws Exception If the image cannot be read
34 */
35 public function fetchAsBase64(string $imageUrl): string
36 {
37 $contents = $this->readFromUploads($imageUrl);
38
39 if ($contents === null) {
40 $contents = $this->readFromHttp($imageUrl);
41 }
42
43 return base64_encode($contents);
44 }
45
46 /**
47 * Read the file from the uploads directory when the URL belongs to it.
48 *
49 * @param string $imageUrl The image URL
50 *
51 * @return string|null The raw bytes, or null when the URL is not a local
52 * upload or the file is not readable
53 */
54 private function readFromUploads(string $imageUrl): ?string
55 {
56 $uploads = wp_get_upload_dir();
57
58 if (!empty($uploads['error']) || empty($uploads['baseurl']) || empty($uploads['basedir'])) {
59 return null;
60 }
61
62 // Compare without the scheme: a http/https mismatch between the stored
63 // URL and the current request must not force a needless HTTP round trip.
64 $baseUrl = $this->stripScheme($uploads['baseurl']);
65 $url = $this->stripScheme($imageUrl);
66
67 if (strpos($url, $baseUrl) !== 0) {
68 return null;
69 }
70
71 // Drop any query string (e.g. cache busting parameters) before
72 // touching the filesystem.
73 $relativePath = (string) strtok(substr($url, strlen($baseUrl)), '?');
74 $path = realpath($uploads['basedir'] . $relativePath);
75 $baseDir = realpath($uploads['basedir']);
76
77 // Defensive: never read outside the uploads directory, whatever the URL contains.
78 if ($path === false || $baseDir === false || strpos($path, $baseDir) !== 0) {
79 return null;
80 }
81
82 if (!is_file($path) || !is_readable($path)) {
83 return null;
84 }
85
86 $contents = file_get_contents($path);
87
88 return $contents === false ? null : $contents;
89 }
90
91 /**
92 * Download the image over HTTP.
93 *
94 * @param string $imageUrl The image URL
95 *
96 * @return string The raw bytes
97 *
98 * @throws Exception If the download fails or returns an empty body
99 */
100 private function readFromHttp(string $imageUrl): string
101 {
102 $response = wp_remote_get($imageUrl, ['timeout' => self::DEFAULT_TIMEOUT]);
103
104 if (is_wp_error($response)) {
105 throw new Exception(
106 sprintf('Unable to download the image: %s', $response->get_error_message())
107 );
108 }
109
110 $statusCode = wp_remote_retrieve_response_code($response);
111 if ($statusCode < 200 || $statusCode >= 300) {
112 throw new Exception(
113 sprintf('Unable to download the image: HTTP status %d', $statusCode)
114 );
115 }
116
117 $body = wp_remote_retrieve_body($response);
118 if ($body === '') {
119 throw new Exception('The downloaded image is empty');
120 }
121
122 return $body;
123 }
124
125 /**
126 * Remove the http/https scheme from a URL.
127 */
128 private function stripScheme(string $url): string
129 {
130 return (string) preg_replace('#^https?://#i', '', $url);
131 }
132 }
133