PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.3
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.3
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
wordpress-seo / src / helpers / image-helper.php

image-helper.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 28.3, at src/helpers/image-helper.php

419 lines 11.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Yoast\WP\SEO\Helpers;
4
5 use WPSEO_Image_Utils;
6 use Yoast\WP\SEO\Models\SEO_Links;
7 use Yoast\WP\SEO\Repositories\Indexable_Repository;
8 use Yoast\WP\SEO\Repositories\SEO_Links_Repository;
9
10 /**
11 * A helper object for images.
12 */
13 class Image_Helper {
14
15 /**
16 * Image types that are supported by Open Graph.
17 *
18 * @var array
19 */
20 protected static $valid_image_types = [ 'image/jpeg', 'image/gif', 'image/png', 'image/webp' ];
21
22 /**
23 * Image extensions that are supported by Open Graph.
24 *
25 * @var array
26 */
27 protected static $valid_image_extensions = [ 'jpeg', 'jpg', 'gif', 'png', 'webp' ];
28
29 /**
30 * Represents the indexables repository.
31 *
32 * @var Indexable_Repository
33 */
34 protected $indexable_repository;
35
36 /**
37 * Represents the SEO Links repository.
38 *
39 * @var SEO_Links_Repository
40 */
41 protected $seo_links_repository;
42
43 /**
44 * The options helper.
45 *
46 * @var Options_Helper
47 */
48 private $options_helper;
49
50 /**
51 * The URL helper.
52 *
53 * @var Url_Helper
54 */
55 private $url_helper;
56
57 /**
58 * Image_Helper constructor.
59 *
60 * @param Indexable_Repository $indexable_repository The indexable repository.
61 * @param SEO_Links_Repository $seo_links_repository The SEO Links repository.
62 * @param Options_Helper $options The options helper.
63 * @param Url_Helper $url_helper The URL helper.
64 */
65 public function __construct(
66 Indexable_Repository $indexable_repository,
67 SEO_Links_Repository $seo_links_repository,
68 Options_Helper $options,
69 Url_Helper $url_helper
70 ) {
71 $this->indexable_repository = $indexable_repository;
72 $this->seo_links_repository = $seo_links_repository;
73 $this->options_helper = $options;
74 $this->url_helper = $url_helper;
75 }
76
77 /**
78 * Determines whether or not the wanted attachment is considered valid.
79 *
80 * @param int $attachment_id The attachment ID to get the attachment by.
81 *
82 * @return bool Whether or not the attachment is valid.
83 */
84 public function is_valid_attachment( $attachment_id ) {
85 if ( ! \wp_attachment_is_image( $attachment_id ) ) {
86 return false;
87 }
88
89 $post_mime_type = \get_post_mime_type( $attachment_id );
90 if ( $post_mime_type === false ) {
91 return false;
92 }
93
94 return $this->is_valid_image_type( $post_mime_type );
95 }
96
97 /**
98 * Checks if the given extension is a valid extension
99 *
100 * @param string $image_extension The image extension.
101 *
102 * @return bool True when valid.
103 */
104 public function is_extension_valid( $image_extension ) {
105 return \in_array( $image_extension, static::$valid_image_extensions, true );
106 }
107
108 /**
109 * Determines whether the passed mime type is a valid image type.
110 *
111 * @param string $mime_type The detected mime type.
112 *
113 * @return bool Whether or not the attachment is a valid image type.
114 */
115 public function is_valid_image_type( $mime_type ) {
116 return \in_array( $mime_type, static::$valid_image_types, true );
117 }
118
119 /**
120 * Retrieves the image source for an attachment.
121 *
122 * @param int $attachment_id The attachment.
123 * @param string $image_size The image size to retrieve.
124 *
125 * @return string The image url or an empty string when not found.
126 */
127 public function get_attachment_image_source( $attachment_id, $image_size = 'full' ) {
128 $attachment = \wp_get_attachment_image_src( $attachment_id, $image_size );
129
130 if ( ! $attachment ) {
131 return '';
132 }
133
134 return $attachment[0];
135 }
136
137 /**
138 * Retrieves the ID of the featured image.
139 *
140 * @param int $post_id The post id to get featured image id for.
141 *
142 * @return int|bool ID when found, false when not.
143 */
144 public function get_featured_image_id( $post_id ) {
145 if ( ! \has_post_thumbnail( $post_id ) ) {
146 return false;
147 }
148
149 return \get_post_thumbnail_id( $post_id );
150 }
151
152 /**
153 * Gets the image url from the content.
154 *
155 * @param int $post_id The post id to extract the images from.
156 *
157 * @return string The image url or an empty string when not found.
158 */
159 public function get_post_content_image( $post_id ) {
160 $image_url = $this->get_first_usable_content_image_for_post( $post_id );
161
162 if ( $image_url === null ) {
163 return '';
164 }
165
166 return $image_url;
167 }
168
169 /**
170 * Gets the first image url of a gallery.
171 *
172 * @param int $post_id Post ID to use.
173 *
174 * @return string The image url or an empty string when not found.
175 */
176 public function get_gallery_image( $post_id ) {
177 $post = \get_post( $post_id );
178 if ( \strpos( $post->post_content, '[gallery' ) === false ) {
179 return '';
180 }
181
182 $images = \get_post_gallery_images( $post );
183 if ( empty( $images ) ) {
184 return '';
185 }
186
187 return \reset( $images );
188 }
189
190 /**
191 * Gets the image url from the term content.
192 *
193 * @param int $term_id The term id to extract the images from.
194 *
195 * @return string The image url or an empty string when not found.
196 */
197 public function get_term_content_image( $term_id ) {
198 $image_url = $this->get_first_content_image_for_term( $term_id );
199
200 if ( $image_url === null ) {
201 return '';
202 }
203
204 return $image_url;
205 }
206
207 /**
208 * Retrieves the caption for an attachment.
209 *
210 * @param int $attachment_id Attachment ID.
211 *
212 * @return string The caption when found, empty string when no caption is found.
213 */
214 public function get_caption( $attachment_id ) {
215 $caption = \wp_get_attachment_caption( $attachment_id );
216 if ( ! empty( $caption ) ) {
217 return $caption;
218 }
219
220 $caption = \get_post_meta( $attachment_id, '_wp_attachment_image_alt', true );
221 if ( ! empty( $caption ) ) {
222 return $caption;
223 }
224
225 return '';
226 }
227
228 /**
229 * Retrieves the attachment metadata.
230 *
231 * @param int $attachment_id Attachment ID.
232 *
233 * @return array The metadata, empty array when no metadata is found.
234 */
235 public function get_metadata( $attachment_id ) {
236 $metadata = \wp_get_attachment_metadata( $attachment_id );
237 if ( ! $metadata || ! \is_array( $metadata ) ) {
238 return [];
239 }
240
241 return $metadata;
242 }
243
244 /**
245 * Retrieves the attachment image url.
246 *
247 * @param int $attachment_id Attachment ID.
248 * @param string $size The size to get.
249 *
250 * @return string The url when found, empty string otherwise.
251 */
252 public function get_attachment_image_url( $attachment_id, $size ) {
253 $url = \wp_get_attachment_image_url( $attachment_id, $size );
254 if ( ! $url ) {
255 return '';
256 }
257
258 return $url;
259 }
260
261 /**
262 * Find the right version of an image based on size.
263 *
264 * @codeCoverageIgnore - We have to write test when this method contains own code.
265 *
266 * @param int $attachment_id Attachment ID.
267 * @param string $size Size name.
268 *
269 * @return array|false Returns an array with image data on success, false on failure.
270 */
271 public function get_image( $attachment_id, $size ) {
272 return WPSEO_Image_Utils::get_image( $attachment_id, $size );
273 }
274
275 /**
276 * Retrieves the best attachment variation for the given attachment.
277 *
278 * @codeCoverageIgnore - We have to write test when this method contains own code.
279 *
280 * @param int $attachment_id The attachment id.
281 *
282 * @return bool|string The attachment url or false when no variations found.
283 */
284 public function get_best_attachment_variation( $attachment_id ) {
285 $variations = WPSEO_Image_Utils::get_variations( $attachment_id );
286 $variations = WPSEO_Image_Utils::filter_usable_file_size( $variations );
287
288 // If we are left without variations, there is no valid variation for this attachment.
289 if ( empty( $variations ) ) {
290 return false;
291 }
292
293 // The variations are ordered so the first variations is by definition the best one.
294 return \reset( $variations );
295 }
296
297 /**
298 * Find an attachment ID for a given URL.
299 *
300 * @param string $url The URL to find the attachment for.
301 * @param bool $use_link_table Whether the SEO Links table will be used to retrieve the id.
302 *
303 * @return int The found attachment ID, or 0 if none was found.
304 */
305 public function get_attachment_by_url( $url, $use_link_table = true ) {
306 // Don't try to do this for external URLs.
307 $parsed_url = \wp_parse_url( $url );
308 if ( $this->url_helper->get_link_type( $parsed_url ) === SEO_Links::TYPE_EXTERNAL ) {
309 return 0;
310 }
311
312 /** The `wpseo_force_creating_and_using_attachment_indexables` filter is documented in indexable-link-builder.php */
313 if ( ! $this->options_helper->get( 'disable-attachment' ) || \apply_filters( 'wpseo_force_creating_and_using_attachment_indexables', false ) ) {
314 // Strip out the size part of an image URL.
315 $url = \preg_replace( '/(.*)-\d+x\d+\.(jpeg|jpg|png|gif)$/', '$1.$2', $url );
316
317 $indexable = $this->indexable_repository->find_by_permalink( $url );
318
319 if ( $indexable && $indexable->object_type === 'post' && $indexable->object_sub_type === 'attachment' ) {
320 return $indexable->object_id;
321 }
322
323 $post_id = WPSEO_Image_Utils::get_attachment_by_url( $url );
324
325 if ( $post_id !== 0 ) {
326 // Find the indexable, this triggers creating it so it can be found next time.
327 $this->indexable_repository->find_by_id_and_type( $post_id, 'post' );
328 }
329
330 return $post_id;
331 }
332
333 if ( ! $use_link_table ) {
334 return WPSEO_Image_Utils::get_attachment_by_url( $url );
335 }
336 $cache_key = 'attachment_seo_link_object_' . \md5( $url );
337
338 $found = false;
339 $link = \wp_cache_get( $cache_key, 'yoast-seo-attachment-link', false, $found );
340
341 if ( $found === false ) {
342 $link = $this->seo_links_repository->find_one_by_url( $url );
343 \wp_cache_set( $cache_key, $link, 'yoast-seo-attachment-link', \MINUTE_IN_SECONDS );
344 }
345 if ( ! \is_a( $link, SEO_Links::class ) ) {
346 return WPSEO_Image_Utils::get_attachment_by_url( $url );
347 }
348
349 return $link->target_post_id;
350 }
351
352 /**
353 * Retrieves an attachment ID for an image uploaded in the settings.
354 *
355 * Due to self::get_attachment_by_url returning 0 instead of false.
356 * 0 is also a possibility when no ID is available.
357 *
358 * @codeCoverageIgnore - We have to write test when this method contains own code.
359 *
360 * @param string $setting The setting the image is stored in.
361 *
362 * @return int|bool The attachment id, or false or 0 if no ID is available.
363 */
364 public function get_attachment_id_from_settings( $setting ) {
365 return WPSEO_Image_Utils::get_attachment_id_from_settings( $setting );
366 }
367
368 /**
369 * Based on and image ID return array with the best variation of that image. If it's not saved to the DB, save it
370 * to an option.
371 *
372 * @param string $setting The setting name. Should be company or person.
373 *
374 * @return array|bool Array with image details when the image is found, boolean when it's not found.
375 */
376 public function get_attachment_meta_from_settings( $setting ) {
377 $image_meta = $this->options_helper->get( $setting . '_meta', false );
378 if ( ! $image_meta ) {
379 $image_id = $this->options_helper->get( $setting . '_id', false );
380 if ( $image_id ) {
381 // There is not an option to put a URL in an image field in the settings anymore, only to upload it through the media manager.
382 // This means an attachment always exists, so doing this is only needed once.
383 $image_meta = $this->get_best_attachment_variation( $image_id );
384 if ( $image_meta ) {
385 $this->options_helper->set( $setting . '_meta', $image_meta );
386 }
387 }
388 }
389
390 return $image_meta;
391 }
392
393 /**
394 * Retrieves the first usable content image for a post.
395 *
396 * @codeCoverageIgnore - We have to write test when this method contains own code.
397 *
398 * @param int $post_id The post id to extract the images from.
399 *
400 * @return string|null
401 */
402 protected function get_first_usable_content_image_for_post( $post_id ) {
403 return WPSEO_Image_Utils::get_first_usable_content_image_for_post( $post_id );
404 }
405
406 /**
407 * Gets the term's first usable content image. Null if none is available.
408 *
409 * @codeCoverageIgnore - We have to write test when this method contains own code.
410 *
411 * @param int $term_id The term id.
412 *
413 * @return string|null The image URL.
414 */
415 protected function get_first_content_image_for_term( $term_id ) {
416 return WPSEO_Image_Utils::get_first_content_image_for_term( $term_id );
417 }
418 }
419