PluginProbe
Embed Privacy / 1.11.2
Embed Privacy v1.11.2
1.14.0 1.13.0 trunk 0.1 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.1.3 1.10.0 1.10.1 1.10.10 1.10.2 1.10.3 1.10.4 1.10.5 1.10.6 1.10.7 1.10.8 1.10.9 1.11.0 1.11.1 1.11.2 All 68 releases
embed-privacy / inc / thumbnail / class-thumbnail.php

class-thumbnail.php in Embed Privacy 1.11.2, at inc/thumbnail/class-thumbnail.php

558 lines 15.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace epiphyt\Embed_Privacy\thumbnail;
3
4 use epiphyt\Embed_Privacy\thumbnail\provider\SlideShare;
5 use epiphyt\Embed_Privacy\thumbnail\provider\Thumbnail_Provider;
6 use epiphyt\Embed_Privacy\thumbnail\provider\Vimeo;
7 use epiphyt\Embed_Privacy\thumbnail\provider\WordPress_TV;
8 use epiphyt\Embed_Privacy\thumbnail\provider\YouTube;
9 use WP_Post;
10
11 /**
12 * Thumbnail related functionality.
13 *
14 * @author Epiphyt
15 * @license GPL2
16 * @package epiphyt\Embed_Privacy
17 */
18 final class Thumbnail {
19 const METADATA_PREFIX = 'embed_privacy_thumbnail';
20
21 /**
22 * @var \epiphyt\Embed_Privacy\thumbnail\provider\Thumbnail_Provider[] List of thumbnail provider classes
23 */
24 public $providers = [
25 SlideShare::class,
26 Vimeo::class,
27 WordPress_TV::class,
28 YouTube::class,
29 ];
30
31 /**
32 * Initialize functionality.
33 */
34 public function init() {
35 if ( ! \get_option( 'embed_privacy_download_thumbnails' ) ) {
36 return;
37 }
38
39 \add_action( 'before_delete_post', [ self::class, 'delete_thumbnails' ] );
40 \add_action( 'init', [ $this, 'register_providers' ] );
41 \add_action( 'post_updated', [ $this, 'delete_orphaned' ], 10, 2 );
42 \add_filter( 'oembed_dataparse', [ $this, 'get_from_provider' ], 10, 3 );
43 }
44
45 /**
46 * Check and delete orphaned thumbnails.
47 *
48 * @param int $post_id The post ID
49 * @param \WP_Post $post The post object
50 */
51 public function delete_orphaned( $post_id, $post ) {
52 // don't check for orphaned if it's not a proper post update
53 // e.g. via REST API, where not all fields are updated
54 if ( empty( $_POST ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
55 return;
56 }
57
58 $global_metadata = self::get_metadata();
59 $metadata = \get_post_meta( $post_id );
60 $supported_providers = \array_map(
61 static function( $provider ) {
62 $provider_obj = new $provider(); // phpcs:ignore NeutronStandard.Functions.VariableFunctions.VariableFunction
63
64 return $provider_obj::$name;
65 },
66 $this->providers
67 );
68
69 /**
70 * Filter the supported provider names.
71 *
72 * @since 1.7.0
73 *
74 * @param \epiphyt\Embed_Privacy\thumbnail\provider\Thumbnail_Provider[] $supported_providers Current supported provider names
75 */
76 $supported_providers = (array) \apply_filters( 'embed_privacy_thumbnail_supported_provider_names', $supported_providers );
77
78 foreach ( $metadata as $meta_key => $meta_value ) {
79 if ( ! \str_contains( $meta_key, self::METADATA_PREFIX . '_' ) ) {
80 continue;
81 }
82
83 if ( \is_array( $meta_value ) ) {
84 $meta_value = \reset( $meta_value );
85 }
86
87 foreach ( $supported_providers as $provider ) {
88 if ( \str_contains( $meta_key, '_' . $provider . '_' ) && ! \str_contains( $meta_key, '_url' ) ) {
89 $id = \str_replace( self::METADATA_PREFIX . '_' . $provider . '_', '', $meta_key );
90 $missing_id = ! \str_contains( $post->post_content, $id );
91 $missing_url = true;
92 $url = '';
93
94 if ( $missing_id && self::is_in_acf_fields( $post_id, $id ) ) {
95 $missing_id = false;
96 }
97
98 if ( $missing_id && isset( $metadata[ $meta_key . '_url' ] ) ) {
99 $url = $metadata[ $meta_key . '_url' ];
100
101 if ( \is_array( $url ) ) {
102 $url = \reset( $url );
103 }
104
105 $missing_url = ! \str_contains( $post->post_content, $url );
106
107 if ( $missing_url && self::is_in_acf_fields( $post_id, $url ) ) {
108 $missing_url = false;
109 }
110 }
111
112 $should_delete = $missing_id && $missing_url && ! self::is_in_use( $meta_value, $post_id, $global_metadata );
113
114 /**
115 * Filters whether an thumbnail marked as orphaned should be deleted.
116 *
117 * @since 1.10.0
118 *
119 * @param bool $should_delete Whether the thumbnail should be deleted
120 * @param string $id The thumbnail ID
121 * @param string $url The thumbnail URL
122 * @param int $post_id The post ID
123 * @param string $provider The provider
124 */
125 $should_delete = \apply_filters( 'embed_privacy_thumbnail_delete_orphaned', $should_delete, $id, $url, $post_id, $provider );
126
127 if ( $should_delete ) {
128 /**
129 * Fires before orphaned data are deleted.
130 *
131 * @deprecated 1.10.0 Use filter embed_privacy_thumbnail_delete_orphaned instead
132 * @since 1.8.0
133 *
134 * @param string $id The thumbnail ID
135 * @param string $url The thumbnail URL
136 * @param int $post_id The post ID
137 * @param string $provider The provider name
138 */
139 \do_action_deprecated(
140 'embed_privacy_pre_thumbnail_delete_orphaned_delete',
141 [
142 $id,
143 $url,
144 $post_id,
145 $provider,
146 ],
147 '1.10.0',
148 'embed_privacy_thumbnail_delete_orphaned'
149 );
150
151 $should_delete = ! \has_action( 'embed_privacy_pre_thumbnail_delete_orphaned_delete' );
152
153 if ( $should_delete ) {
154 self::delete( $meta_value );
155 \delete_post_meta( $post_id, $meta_key );
156 \delete_post_meta( $post_id, $meta_key . '_url' );
157 }
158 }
159
160 /**
161 * Fires after orphaned data have been checked.
162 *
163 * @since 1.7.0
164 *
165 * @param string $provider Provider name
166 * @param string $id The ID of the embedded content
167 * @param string $url The embed URL
168 * @param bool $missing_id Whether the ID is missing
169 * @param bool $missing_url Whether the URL is missing
170 * @param string $meta_value The thumbnail filename
171 * @param string $meta_key The thumbnail meta key
172 * @param \WP_Post $post The post object
173 * @param int $post_id The post ID
174 */
175 \do_action( 'embed_privacy_thumbnail_checked_orphaned', $provider, $id, $url, $missing_id, $missing_url, $meta_value, $meta_key, $post, $post_id );
176 }
177 }
178 }
179 }
180
181 /**
182 * Delete a thumbnail.
183 *
184 * @param string $filename The thumbnail filename
185 */
186 private static function delete( $filename ) {
187 $directory = self::get_directory();
188
189 if ( ! \file_exists( $directory['base_dir'] . '/' . $filename ) ) {
190 return;
191 }
192
193 \wp_delete_file( $directory['base_dir'] . '/' . $filename );
194 }
195
196 /**
197 * Delete thumbnails for a given post ID.
198 *
199 * @param int $post_id Post ID
200 */
201 public static function delete_thumbnails( $post_id ) {
202 $global_metadata = self::get_metadata();
203 $metadata = \get_post_meta( $post_id );
204
205 foreach ( $metadata as $meta_key => $meta_value ) {
206 if ( ! \str_contains( $meta_key, self::METADATA_PREFIX . '_' ) ) {
207 continue;
208 }
209
210 if ( \is_array( $meta_value ) ) {
211 $meta_value = \reset( $meta_value );
212 }
213
214 if ( ! self::is_in_use( $meta_value, $post_id, $global_metadata ) ) {
215 self::delete( $meta_value );
216 }
217 }
218 }
219
220 /**
221 * Get path and URL to an embed thumbnail.
222 *
223 * @param \WP_Post $post Post object
224 * @param string $url Embedded URL
225 * @return array Thumbnail path and URL
226 */
227 public function get_data( $post, $url ) {
228 $empty = [
229 'thumbnail_path' => '',
230 'thumbnail_url' => '',
231 ];
232
233 if ( ! $post instanceof WP_Post ) {
234 return $empty;
235 }
236
237 $id = '';
238 $provider = $this->get_provider_by_url( $url );
239 $thumbnail = '';
240 $thumbnail_path = '';
241 $thumbnail_url = '';
242
243 if ( ! $provider instanceof Thumbnail_Provider ) {
244 return $empty;
245 }
246
247 $id = $provider->get_id( $url );
248 $thumbnail = \get_post_meta( $post->ID, self::METADATA_PREFIX . '_' . $provider::$name . '_' . $id, true );
249
250 /**
251 * Filter the thumbnail ID.
252 *
253 * @since 1.7.0
254 *
255 * @param string $id The thumbnail ID
256 * @param \WP_Post $post The post object
257 * @param string $url The embed URL
258 */
259 $id = \apply_filters( 'embed_privacy_thumbnail_data_id', $id, $post, $url );
260
261 /**
262 * Filter the thumbnail filename.
263 *
264 * @since 1.7.0
265 *
266 * @param string $thumbnail The thumbnail filename
267 * @param \WP_Post $post The post object
268 * @param string $url The embed URL
269 */
270 $thumbnail = \apply_filters( 'embed_privacy_thumbnail_data_filename', $thumbnail, $post, $url );
271
272 if ( $thumbnail ) {
273 $directory = self::get_directory();
274 $thumbnail_path = $directory['base_dir'] . '/' . $thumbnail;
275
276 if ( \file_exists( $thumbnail_path ) ) {
277 $thumbnail_url = $directory['base_url'] . '/' . $thumbnail;
278 }
279 }
280
281 /**
282 * Filter the thumbnail path.
283 *
284 * @since 1.7.0
285 *
286 * @param string $thumbnail The thumbnail path
287 * @param \WP_Post $post The post object
288 * @param string $url The embed URL
289 */
290 $thumbnail_path = \apply_filters( 'embed_privacy_thumbnail_data_path', $thumbnail_path, $post, $url );
291
292 /**
293 * Filter the thumbnail URL.
294 *
295 * @since 1.7.0
296 *
297 * @param string $thumbnail The thumbnail URL
298 * @param \WP_Post $post The post object
299 * @param string $url The embed URL
300 */
301 $thumbnail_url = \apply_filters( 'embed_privacy_thumbnail_data_url', $thumbnail_url, $post, $url );
302
303 return [
304 'thumbnail_path' => $thumbnail_path,
305 'thumbnail_url' => $thumbnail_url,
306 ];
307 }
308
309 /**
310 * Get the thumbnail directory and URL.
311 * Since we don't want to have a directory per site in a network, we need to
312 * get rid of the site ID in the path.
313 *
314 * @return string[] Thumbnail directory and URL
315 */
316 public static function get_directory() {
317 $upload_dir = \wp_get_upload_dir();
318
319 if ( ! $upload_dir || $upload_dir['error'] !== false ) {
320 return [
321 'base_dir' => '',
322 'base_url' => '',
323 ];
324 }
325
326 if ( ! \file_exists( $upload_dir['basedir'] . '/embed-privacy/thumbnails' ) ) {
327 \wp_mkdir_p( $upload_dir['basedir'] . '/embed-privacy/thumbnails' );
328 }
329
330 return [
331 'base_dir' => $upload_dir['basedir'] . '/embed-privacy/thumbnails',
332 'base_url' => $upload_dir['baseurl'] . '/embed-privacy/thumbnails',
333 ];
334 }
335
336 /**
337 * Get embed thumbnails from the embed provider.
338 *
339 * @param string $output The returned oEmbed HTML
340 * @param object $data A data object result from an oEmbed provider
341 * @param string $url The URL of the content to be embedded
342 * @return string The returned oEmbed HTML
343 */
344 public function get_from_provider( $output, $data, $url ) {
345 foreach ( $this->providers as $provider ) {
346 $provider_obj = new $provider(); // phpcs:ignore NeutronStandard.Functions.VariableFunctions.VariableFunction
347 $provider_obj->get( $data, $url );
348 }
349
350 /**
351 * Fires after getting data from provider.
352 *
353 * @since 1.7.0
354 *
355 * @param string $output The returned oEmbed HTML
356 * @param object $data A data object result from an oEmbed provider
357 * @param string $url The URL of the content to be embedded
358 */
359 \do_action( 'embed_privacy_thumbnail_get_from_provider', $output, $data, $url );
360
361 return $output;
362 }
363
364 /**
365 * Get all thumbnail metadata of all posts.
366 *
367 * @param string $provider Optional provider name to limit metadata
368 * @return array All thumbnail metadata
369 */
370 public static function get_metadata( $provider = '' ) {
371 global $wpdb;
372
373 if ( ! empty( $provider ) ) {
374 $key = self::METADATA_PREFIX . '_' . $provider . '_%';
375 }
376 else {
377 $key = self::METADATA_PREFIX . '_%';
378 }
379
380 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
381 return $wpdb->get_results(
382 $wpdb->prepare(
383 "SELECT DISTINCT post_id,
384 meta_key,
385 meta_value
386 FROM $wpdb->postmeta
387 WHERE meta_key LIKE %s",
388 $key
389 ),
390 \ARRAY_A
391 );
392 // phpcs:enable
393 }
394
395 /**
396 * Get a thumbnail provider by an URL.
397 *
398 * @param string $url Embed URL
399 * @return \epiphyt\Embed_Privacy\thumbnail\provider\Thumbnail_Provider|null Thumbnail provider object or null
400 */
401 public function get_provider_by_url( $url ) {
402 foreach ( $this->providers as $provider ) {
403 $provider_obj = new $provider(); // phpcs:ignore NeutronStandard.Functions.VariableFunctions.VariableFunction
404
405 foreach ( $provider_obj::$domains as $domain ) {
406 if ( \str_contains( $url, $domain ) ) {
407 return $provider_obj;
408 }
409 }
410 }
411
412 return null;
413 }
414
415 /**
416 * Get the provider titles
417 *
418 * @return string[] List of provider titles
419 */
420 public function get_provider_titles() {
421 $titles = \array_map(
422 static function( $provider ) {
423 $provider_obj = new $provider(); // phpcs:ignore NeutronStandard.Functions.VariableFunctions.VariableFunction
424
425 return $provider_obj->get_title();
426 },
427 $this->providers
428 );
429
430 \asort( $titles, \SORT_NATURAL );
431
432 return $titles;
433 }
434
435 /**
436 * Check whether a thumbnail is in use in ACF fields.
437 *
438 * @param int $post_id Current post ID
439 * @param string $content Content to search for
440 * @param array $fields List of ACF fields
441 * @return bool Whether thumbnail is in use in ACF fields
442 */
443 private function is_in_acf_fields( $post_id, $content, array $fields = [] ) {
444 // phpcs:disable WordPress.Security.NonceVerification.Missing
445 if ( ! \function_exists( 'get_fields' ) ) {
446 return false;
447 }
448
449 if ( empty( $fields ) ) {
450 if ( empty( $_POST['acf'] ) ) {
451 return false;
452 }
453
454 // we need to use the post fields since get_fields() doesn't contain
455 // the actual value since it will be stored after the post is saved
456 $fields = \wp_unslash( $_POST['acf'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
457 }
458 // phpcs:enable
459
460 $is_in_fields = false;
461
462 foreach ( $fields as $field ) {
463 if ( \is_array( $field ) || \is_object( $field ) ) {
464 if ( self::is_in_acf_fields( $post_id, $content, (array) $field ) ) {
465 $is_in_fields = true;
466 break;
467 }
468 }
469 else if ( \str_contains( (string) $field, $content ) ) {
470 $is_in_fields = true;
471 break;
472 }
473 }
474
475 return $is_in_fields;
476 }
477
478 /**
479 * Check whether a thumbnail is in use in another post.
480 *
481 * @param string $meta_value The thumbnail filename
482 * @param int $post_id The post ID of the current post
483 * @param array $global_metadata Global metadata to check in
484 * @return bool Whether a thumbnail is in use in another post
485 */
486 private static function is_in_use( $meta_value, $post_id, $global_metadata ) {
487 $is_in_use = false;
488
489 foreach ( $global_metadata as $global_meta_value ) {
490 if ( (int) $global_meta_value['post_id'] === $post_id ) {
491 continue;
492 }
493
494 if ( $global_meta_value['meta_value'] === $meta_value ) {
495 $is_in_use = true;
496 break;
497 }
498 }
499
500 return $is_in_use;
501 }
502
503 /**
504 * Register thumbnail providers.
505 */
506 public function register_providers() {
507 /**
508 * Filter the supported providers.
509 *
510 * @since 1.9.0
511 *
512 * @param \epiphyt\Embed_Privacy\thumbnail\provider\Thumbnail_Provider[] $supported_providers Current supported providers
513 */
514 $this->providers = (array) \apply_filters( 'embed_privacy_thumbnail_providers', $this->providers );
515
516 $providers = [
517 \_x( 'SlideShare', 'embed provider', 'embed-privacy' ),
518 \_x( 'Vimeo', 'embed provider', 'embed-privacy' ),
519 \_x( 'YouTube', 'embed provider', 'embed-privacy' ),
520 ];
521
522 /**
523 * Filter the supported providers.
524 *
525 * @deprecated 1.9.0
526 * @since 1.7.0
527 *
528 * @param array $supported_providers Current supported providers
529 */
530 $providers = \apply_filters_deprecated( 'embed_privacy_thumbnail_supported_providers', $providers, '1.9.0', 'embed_privacy_thumbnail_providers' );
531
532 foreach ( $this->providers as $provider ) {
533 try {
534 $provider_obj = new $provider(); // phpcs:ignore NeutronStandard.Functions.VariableFunctions.VariableFunction
535
536 if ( ! $provider_obj instanceof Thumbnail_Provider ) {
537 \wp_die(
538 \sprintf(
539 /* translators: PHP class */
540 \esc_html__( 'Thumbnail provider is not an instance of %s', 'embed-privacy' ),
541 'epiphyt\Embed_Privacy\thumbnail\provider\Thumbnail_Provider'
542 )
543 );
544 }
545 }
546 catch ( \Exception $e ) {
547 \wp_die(
548 \sprintf(
549 /* translators: PHP class */
550 \esc_html__( 'Thumbnail provider is not an instance of %s', 'embed-privacy' ),
551 'epiphyt\Embed_Privacy\thumbnail\provider\Thumbnail_Provider'
552 )
553 );
554 }
555 }
556 }
557 }
558