PluginProbe
ActivityPub / trunk
ActivityPub vtrunk
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
activitypub / includes / cache / class-media.php

class-media.php in ActivityPub trunk, at includes/cache/class-media.php

192 lines 4.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Media cache class.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub\Cache;
9
10 use Activitypub\Collection\Remote_Posts;
11
12 /**
13 * Media cache class.
14 *
15 * Handles lazy caching of remote post and comment media locally.
16 * Media is cached on-demand when URLs pass through the `activitypub_remote_media_url` filter.
17 *
18 * Storage locations:
19 * - Posts: /wp-content/uploads/activitypub/posts/{post_id}/
20 * - Comments: /wp-content/uploads/activitypub/comments/{comment_id}/
21 *
22 * Files are cleaned up automatically when the parent post is deleted.
23 *
24 * @since 5.6.0
25 */
26 class Media extends File {
27 /**
28 * Maximum dimension for media images in pixels.
29 *
30 * @var int
31 */
32 const MAX_DIMENSION = 1200;
33
34 /**
35 * Context identifier for post media.
36 *
37 * @var string
38 */
39 const CONTEXT = 'media';
40
41 /**
42 * Context identifier for comment media.
43 *
44 * Reserved for future use when comment media caching is implemented.
45 * Currently, only post media caching is active via maybe_cache().
46 *
47 * @var string
48 */
49 const CONTEXT_COMMENT = 'comment_media';
50
51 /**
52 * Base directory for post media.
53 *
54 * @var string
55 */
56 const BASE_DIR_POSTS = '/activitypub/posts/';
57
58 /**
59 * Base directory for comment media.
60 *
61 * Reserved for future use when comment media caching is implemented.
62 *
63 * @var string
64 */
65 const BASE_DIR_COMMENTS = '/activitypub/comments/';
66
67 /**
68 * Get the cache type identifier.
69 *
70 * @return string Cache type.
71 */
72 public static function get_type() {
73 return 'media';
74 }
75
76 /**
77 * Get the base directory path relative to uploads.
78 *
79 * Default to post media directory. Use get_storage_paths_for_context()
80 * for context-aware path resolution.
81 *
82 * @return string Base directory path.
83 */
84 public static function get_base_dir() {
85 return self::BASE_DIR_POSTS;
86 }
87
88 /**
89 * Get the context identifier for the filter.
90 *
91 * @return string Context identifier.
92 */
93 public static function get_context() {
94 return self::CONTEXT;
95 }
96
97 /**
98 * Get the maximum dimension for media images.
99 *
100 * @return int Maximum width/height in pixels.
101 */
102 public static function get_max_dimension() {
103 return self::MAX_DIMENSION;
104 }
105
106 /**
107 * Get storage paths based on context.
108 *
109 * @param string|int $entity_id The entity identifier.
110 * @param string $context The context ('media' or 'comment_media').
111 *
112 * @return array {
113 * Storage paths for the entity.
114 *
115 * @type string $basedir Base directory path.
116 * @type string $baseurl Base URL.
117 * }
118 */
119 public static function get_storage_paths_for_context( $entity_id, $context = self::CONTEXT ) {
120 $upload_dir = \wp_upload_dir();
121 $entity_id = \sanitize_file_name( (string) $entity_id );
122 $base_dir = self::CONTEXT_COMMENT === $context ? self::BASE_DIR_COMMENTS : self::BASE_DIR_POSTS;
123
124 return array(
125 'basedir' => $upload_dir['basedir'] . $base_dir . $entity_id,
126 'baseurl' => $upload_dir['baseurl'] . $base_dir . $entity_id,
127 );
128 }
129
130 /**
131 * Initialize the cache handler.
132 */
133 public static function init() {
134 // Only register local caching filter when caching is enabled.
135 if ( self::is_enabled() ) {
136 \add_filter( 'activitypub_remote_media_url', array( self::class, 'maybe_cache' ), 10, 4 );
137
138 // Clean up when post is deleted.
139 \add_action( 'before_delete_post', array( self::class, 'maybe_cleanup' ) );
140 }
141 }
142
143 /**
144 * Maybe cache a media URL.
145 *
146 * Hooked to the activitypub_remote_media_url filter.
147 * Downloads and caches the file locally if not already cached.
148 *
149 * @param string $url The remote URL.
150 * @param string $context The context ('avatar', 'media', 'emoji', etc.).
151 * @param string|int $entity_id The entity identifier (post ID).
152 * @param array $options Optional. Additional options.
153 *
154 * @return string The local URL if cached successfully, otherwise the original URL.
155 */
156 public static function maybe_cache( $url, $context, $entity_id = null, $options = array() ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Required for filter signature.
157 if ( self::CONTEXT !== $context || empty( $url ) || empty( $entity_id ) ) {
158 return $url;
159 }
160
161 $cached_url = self::get_or_cache( $url, $entity_id );
162
163 return $cached_url ?: $url;
164 }
165
166 /**
167 * Maybe clean up cached media when post is deleted.
168 *
169 * @param int $post_id The post ID being deleted.
170 */
171 public static function maybe_cleanup( $post_id ) {
172 if ( Remote_Posts::POST_TYPE !== \get_post_type( $post_id ) ) {
173 return;
174 }
175
176 self::invalidate_entity( $post_id );
177 }
178
179 /**
180 * Invalidate cached media for a comment.
181 *
182 * @param int $comment_id The comment ID.
183 *
184 * @return bool True on success, false on failure.
185 */
186 public static function invalidate_comment( $comment_id ) {
187 $paths = self::get_storage_paths_for_context( $comment_id, self::CONTEXT_COMMENT );
188
189 return static::delete_directory( $paths['basedir'] );
190 }
191 }
192