PluginProbe
ActivityPub / 9.0.2
ActivityPub v9.0.2
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 9.0.2, at includes/cache/class-avatar.php

173 lines 4.4 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 // Invalidate cached avatar when actor is updated so it re-downloads on next access.
84 \add_action( 'save_post_' . Remote_Actors::POST_TYPE, array( self::class, 'clear_cached_avatar' ) );
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 when an actor is updated.
92 *
93 * Invalidates cached files so the avatar is re-downloaded on next access.
94 *
95 * @param int $post_id The actor post ID.
96 */
97 public static function clear_cached_avatar( $post_id ) {
98 // Invalidate cached files so next access re-downloads.
99 self::invalidate_entity( $post_id );
100
101 // Clean up legacy meta from previous versions.
102 \delete_post_meta( $post_id, '_activitypub_avatar_url' );
103 }
104
105 /**
106 * Maybe cache an avatar URL.
107 *
108 * Hooked to the activitypub_remote_media_url filter.
109 * Uses filesystem-based caching via get_or_cache() — no persistent meta storage.
110 *
111 * @param string $url The remote URL.
112 * @param string $context The context ('avatar', 'media', 'emoji', etc.).
113 * @param string|int $entity_id The entity identifier (actor post ID).
114 * @param array $options Optional. Additional options (unused for avatars).
115 *
116 * @return string The local URL if cached successfully, otherwise the original URL.
117 */
118 public static function maybe_cache( $url, $context, $entity_id = null, $options = array() ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Required for filter signature.
119 if ( self::CONTEXT !== $context || empty( $url ) || empty( $entity_id ) ) {
120 return $url;
121 }
122
123 $cached_url = self::get_or_cache( $url, $entity_id, array( 'max_dimension' => self::MAX_DIMENSION ) );
124
125 return $cached_url ?: $url;
126 }
127
128 /**
129 * Maybe clean up cached avatar when actor is deleted.
130 *
131 * @param int $post_id The post ID being deleted.
132 */
133 public static function maybe_cleanup( $post_id ) {
134 if ( Remote_Actors::POST_TYPE !== \get_post_type( $post_id ) ) {
135 return;
136 }
137
138 self::invalidate_entity( $post_id );
139 }
140
141 /**
142 * Save an avatar for an actor.
143 *
144 * This is a convenience method that wraps get_or_cache with the correct options.
145 * It also invalidates any existing avatar before caching the new one.
146 *
147 * @param int $actor_id The actor post ID.
148 * @param string $avatar_url The remote avatar URL.
149 *
150 * @return string|false The local avatar URL on success, false on failure.
151 */
152 public static function save( $actor_id, $avatar_url ) {
153 // Validate actor_id is a positive integer.
154 $actor_id = (int) $actor_id;
155 if ( $actor_id <= 0 ) {
156 return false;
157 }
158
159 if ( empty( $avatar_url ) || ! \filter_var( $avatar_url, FILTER_VALIDATE_URL ) ) {
160 return false;
161 }
162
163 // Delete existing avatar files before saving new one.
164 self::invalidate_entity( $actor_id );
165
166 return self::cache(
167 $avatar_url,
168 $actor_id,
169 array( 'max_dimension' => self::MAX_DIMENSION )
170 );
171 }
172 }
173