PluginProbe
ActivityPub / 8.0.1
ActivityPub v8.0.1
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-avatar.php

class-avatar.php in ActivityPub 8.0.1, at includes/cache/class-avatar.php

189 lines 4.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Avatar cache class.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub\Cache;
9
10 use Activitypub\Collection\Remote_Actors;
11
12 /**
13 * Avatar cache class.
14 *
15 * Handles caching of remote actor avatars locally.
16 * Avatars are stored in /wp-content/uploads/activitypub/actors/{actor_id}/
17 * and cleaned up automatically when the actor is deleted.
18 *
19 * @since 5.6.0
20 */
21 class Avatar extends File {
22 /**
23 * Maximum dimension for avatars in pixels.
24 *
25 * @var int
26 */
27 const MAX_DIMENSION = 512;
28
29 /**
30 * Context identifier for the filter.
31 *
32 * @var string
33 */
34 const CONTEXT = 'avatar';
35
36 /**
37 * Get the cache type identifier.
38 *
39 * @return string Cache type.
40 */
41 public static function get_type() {
42 return 'avatar';
43 }
44
45 /**
46 * Get the base directory path relative to uploads.
47 *
48 * @return string Base directory path.
49 */
50 public static function get_base_dir() {
51 return '/activitypub/actors/';
52 }
53
54 /**
55 * Get the context identifier for the filter.
56 *
57 * @return string Context identifier.
58 */
59 public static function get_context() {
60 return self::CONTEXT;
61 }
62
63 /**
64 * Get the maximum dimension for avatars.
65 *
66 * @return int Maximum width/height in pixels.
67 */
68 public static function get_max_dimension() {
69 return self::MAX_DIMENSION;
70 }
71
72 /**
73 * Initialize the cache handler.
74 */
75 public static function init() {
76 if ( ! self::is_enabled() ) {
77 return;
78 }
79
80 // Hook into the universal remote media URL filter for lazy caching.
81 \add_filter( 'activitypub_remote_media_url', array( self::class, 'maybe_cache' ), 10, 4 );
82
83 // Clear cached avatar URL when actor is updated (allows lazy re-caching).
84 \add_action( 'save_post_' . Remote_Actors::POST_TYPE, array( self::class, 'clear_avatar_meta' ) );
85
86 // Clean up files when actor is deleted.
87 \add_action( 'before_delete_post', array( self::class, 'maybe_cleanup' ) );
88 }
89
90 /**
91 * Clear the cached avatar URL meta when an actor is updated.
92 *
93 * This allows lazy re-caching of the avatar on next access,
94 * ensuring updated avatars are fetched.
95 *
96 * @param int $post_id The actor post ID.
97 */
98 public static function clear_avatar_meta( $post_id ) {
99 // Invalidate cached files.
100 self::invalidate_entity( $post_id );
101
102 // Clear the meta so get_avatar_url() will re-cache on next access.
103 \delete_post_meta( $post_id, '_activitypub_avatar_url' );
104 }
105
106 /**
107 * Maybe cache an avatar URL.
108 *
109 * Hooked to the activitypub_remote_media_url filter.
110 * Returns cached URL from meta if available, otherwise downloads and caches.
111 *
112 * @param string $url The remote URL.
113 * @param string $context The context ('avatar', 'media', 'emoji', etc.).
114 * @param string|int $entity_id The entity identifier (actor post ID).
115 * @param array $options Optional. Additional options (unused for avatars).
116 *
117 * @return string The local URL if cached successfully, otherwise the original URL.
118 */
119 public static function maybe_cache( $url, $context, $entity_id = null, $options = array() ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Required for filter signature.
120 if ( self::CONTEXT !== $context || empty( $url ) || empty( $entity_id ) ) {
121 return $url;
122 }
123
124 // Check if we have a cached avatar URL in meta.
125 $cached_url = \get_post_meta( $entity_id, '_activitypub_avatar_url', true );
126 if ( $cached_url ) {
127 return $cached_url;
128 }
129
130 // Download and cache the avatar.
131 $local_url = self::cache(
132 $url,
133 $entity_id,
134 array( 'max_dimension' => self::MAX_DIMENSION )
135 );
136
137 // Store the result in meta (local URL if cached, remote URL if not).
138 $avatar_url = $local_url ?: $url;
139 \update_post_meta( $entity_id, '_activitypub_avatar_url', \esc_url_raw( $avatar_url ) );
140
141 return $avatar_url;
142 }
143
144 /**
145 * Maybe clean up cached avatar when actor is deleted.
146 *
147 * @param int $post_id The post ID being deleted.
148 */
149 public static function maybe_cleanup( $post_id ) {
150 if ( Remote_Actors::POST_TYPE !== \get_post_type( $post_id ) ) {
151 return;
152 }
153
154 self::invalidate_entity( $post_id );
155 }
156
157 /**
158 * Save an avatar for an actor.
159 *
160 * This is a convenience method that wraps get_or_cache with the correct options.
161 * It also invalidates any existing avatar before caching the new one.
162 *
163 * @param int $actor_id The actor post ID.
164 * @param string $avatar_url The remote avatar URL.
165 *
166 * @return string|false The local avatar URL on success, false on failure.
167 */
168 public static function save( $actor_id, $avatar_url ) {
169 // Validate actor_id is a positive integer.
170 $actor_id = (int) $actor_id;
171 if ( $actor_id <= 0 ) {
172 return false;
173 }
174
175 if ( empty( $avatar_url ) || ! \filter_var( $avatar_url, FILTER_VALIDATE_URL ) ) {
176 return false;
177 }
178
179 // Delete existing avatar files before saving new one.
180 self::invalidate_entity( $actor_id );
181
182 return self::cache(
183 $avatar_url,
184 $actor_id,
185 array( 'max_dimension' => self::MAX_DIMENSION )
186 );
187 }
188 }
189