PluginProbe
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages / 3.3.9
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages v3.3.9
3.4.1 3.4.0 3.3.9 3.3.8 3.3.7 3.3.6 3.3.5 3.3.4 3.3.3 3.3.2 3.3.1 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.2.8 2.2.9 2.3.0 2.3.1 2.3.2 2.3.3 All 194 releases
convertkit / includes / class-convertkit-restrict-content-cache.php

class-convertkit-restrict-content-cache.php in Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages 3.3.9, at includes/class-convertkit-restrict-content-cache.php

396 lines 10.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * ConvertKit Restrict Content Cache class.
4 *
5 * @package ConvertKit
6 * @author ConvertKit
7 */
8
9 /**
10 * Maintains a cached map of Post IDs to their relative permalinks for every
11 * Post/Page/Custom Post Type with Member Content (Restrict Content)
12 * functionality enabled.
13 *
14 * @since 3.3.6
15 */
16 class ConvertKit_Restrict_Content_Cache {
17
18 /**
19 * The option name that stores the cache.
20 *
21 * @since 3.3.6
22 *
23 * @var string
24 */
25 const OPTION_NAME = '_wp_convertkit_restrict_content_posts';
26
27 /**
28 * Constructor. Registers hooks that keep the cache in sync with post
29 * meta changes, post deletions, post status transitions, and permalink
30 * structure changes.
31 *
32 * @since 3.3.6
33 */
34 public function __construct() {
35
36 // Post meta changes (covers ConvertKit_Post::save() and direct
37 // update_post_meta() calls).
38 add_action( 'added_post_meta', array( $this, 'on_meta_change' ), 10, 4 );
39 add_action( 'updated_post_meta', array( $this, 'on_meta_change' ), 10, 4 );
40 add_action( 'deleted_post_meta', array( $this, 'on_meta_delete' ), 10, 4 );
41
42 // Post deletion / trashing / restoring.
43 add_action( 'before_delete_post', array( $this, 'on_post_delete' ) );
44 add_action( 'wp_trash_post', array( $this, 'on_post_delete' ) );
45 add_action( 'untrash_post', array( $this, 'on_post_untrash' ) );
46
47 // Post status transitions (draft <-> publish etc).
48 add_action( 'transition_post_status', array( $this, 'on_post_status_transition' ), 10, 3 );
49
50 // Post updated: refresh cached URL if the permalink for a cached
51 // post has changed (e.g. slug edit, parent change).
52 add_action( 'post_updated', array( $this, 'on_post_updated' ), 10, 3 );
53
54 // Rebuild the cache when the permalink structure or site URL changes.
55 add_action( 'update_option_permalink_structure', array( $this, 'on_permalink_change' ) );
56 add_action( 'update_option_siteurl', array( $this, 'on_permalink_change' ) );
57 add_action( 'update_option_home', array( $this, 'on_permalink_change' ) );
58
59 }
60
61 /**
62 * Returns the cache as an associative array of post_id => relative_url.
63 *
64 * @since 3.3.6
65 *
66 * @return array Associative array of post_id => relative_url.
67 */
68 public function get() {
69
70 // Read cache from the option.
71 $cache = get_option( self::OPTION_NAME );
72
73 // If no cache exists, rebuild it.
74 if ( $cache === false ) {
75 $cache = $this->rebuild();
76 }
77
78 return is_array( $cache ) ? $cache : array();
79
80 }
81
82 /**
83 * Returns post IDs stored in the cache.
84 *
85 * @since 3.3.6
86 *
87 * @return array Array of post IDs.
88 */
89 public function get_post_ids() {
90
91 return array_map( 'intval', array_keys( $this->get() ) );
92
93 }
94
95 /**
96 * Adds or updates a Post ID and relative URL in the cache with its current relative
97 * permalink. Only Posts with a `publish` post_status are stored.
98 *
99 * @since 3.4.0
100 *
101 * @param int $post_id Post ID.
102 * @return bool Whether the option was updated.
103 */
104 public function add( $post_id ) {
105
106 // Validate post ID.
107 $post_id = absint( $post_id );
108 if ( ! $post_id ) {
109 return false;
110 }
111
112 // If the Post is not published, remove it from the cache.
113 if ( get_post_status( $post_id ) !== 'publish' ) {
114 return $this->remove( $post_id );
115 }
116
117 // Get the Post's relative permalink.
118 $permalink = get_permalink( $post_id );
119 $relative = wp_make_link_relative( $permalink );
120
121 // Get the cache.
122 $cache = $this->get();
123
124 // Skip write if the Post ID already exists in the cache and the relative permalink is the same.
125 if ( isset( $cache[ $post_id ] ) && $cache[ $post_id ] === $relative ) {
126 return false;
127 }
128
129 // Update the cache.
130 $cache[ $post_id ] = $relative;
131 return update_option( self::OPTION_NAME, $cache );
132
133 }
134
135 /**
136 * Removes a Post ID and relative URL from the cache.
137 *
138 * @since 3.3.6
139 *
140 * @param int $post_id Post ID.
141 * @return bool
142 */
143 public function remove( $post_id ) {
144
145 // Validate post ID.
146 $post_id = absint( $post_id );
147 if ( ! $post_id ) {
148 return false;
149 }
150
151 // Get the cache.
152 $cache = $this->get();
153
154 // If the Post ID is not in the cache, don't update anything.
155 if ( ! array_key_exists( $post_id, $cache ) ) {
156 return false;
157 }
158
159 // Remove the Post ID and relative URL from the cache.
160 unset( $cache[ $post_id ] );
161 return update_option( self::OPTION_NAME, $cache );
162
163 }
164
165 /**
166 * Rebuilds the cache from scratch by querying the database for every
167 * post whose Kit meta contains a non-empty restrict_content setting,
168 * then validating each via ConvertKit_Post.
169 *
170 * This is a potentially expensive operation, so it should be avoided if possible.
171 *
172 * @since 3.3.6
173 *
174 * @return array Cache as an associative array of post_id => relative_url.
175 */
176 public function rebuild() {
177
178 global $wpdb;
179
180 // Serialized array values for `restrict_content` are of the form
181 // s:N:"..." where N is the string length. Empty values are s:0:"".
182 // Matching s:_ (a single digit followed by ":) narrows to values
183 // with length 1..9 which is enough for form_/tag_/product_ IDs.
184 $post_ids = $wpdb->get_col(
185 "
186 SELECT post_id
187 FROM {$wpdb->postmeta}
188 WHERE meta_key = '_wp_convertkit_post_meta'
189 AND meta_value LIKE '%\"restrict_content\";s:%'
190 AND meta_value NOT LIKE '%\"restrict_content\";s:0:%'
191 "
192 );
193
194 $cache = array();
195
196 // Iterate over the post IDs.
197 foreach ( $post_ids as $post_id ) {
198 // Validate post ID.
199 $post_id = absint( $post_id );
200 if ( ! $post_id ) {
201 continue;
202 }
203
204 // Skip if the Post is not published.
205 if ( get_post_status( $post_id ) !== 'publish' ) {
206 continue;
207 }
208
209 // Check if the Post has restrict_content enabled.
210 $post_settings = new ConvertKit_Post( $post_id );
211 if ( ! $post_settings->restrict_content_enabled() ) {
212 continue;
213 }
214
215 // Get the Post's relative permalink.
216 $permalink = get_permalink( $post_id );
217 $cache[ $post_id ] = wp_make_link_relative( $permalink );
218 }
219
220 // Update the cache.
221 update_option( self::OPTION_NAME, $cache );
222
223 return $cache;
224
225 }
226
227 /**
228 * Hook: fires when post meta is added or updated. Adds/removes the
229 * Post ID from the cache based on whether the new value has
230 * restrict_content enabled.
231 *
232 * @since 3.3.6
233 *
234 * @param int $meta_id Meta ID (unused).
235 * @param int $post_id Post ID.
236 * @param string $meta_key Meta key.
237 * @param mixed $meta_value Meta value.
238 */
239 public function on_meta_change( $meta_id, $post_id, $meta_key, $meta_value ) {
240
241 // Check if the meta key is the ConvertKit post meta key.
242 if ( $meta_key !== '_wp_convertkit_post_meta' ) {
243 return;
244 }
245
246 // WP core passes $meta_value as the unserialized value for these
247 // hooks. Fall back gracefully if it's a string (some plugins may
248 // bypass core's unserialize).
249 if ( is_string( $meta_value ) ) {
250 $meta_value = maybe_unserialize( $meta_value );
251 }
252
253 if ( ! is_array( $meta_value ) || empty( $meta_value['restrict_content'] ) ) {
254 $this->remove( $post_id );
255 return;
256 }
257
258 $this->add( $post_id );
259
260 }
261
262 /**
263 * Hook: fires when post meta is deleted. Removes the Post ID from the
264 * cache if the Kit meta was deleted.
265 *
266 * @since 3.3.6
267 *
268 * @param array $meta_ids Meta IDs (unused).
269 * @param int $post_id Post ID.
270 * @param string $meta_key Meta key.
271 * @param mixed $meta_value Meta value (unused).
272 */
273 public function on_meta_delete( $meta_ids, $post_id, $meta_key, $meta_value ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
274
275 // Check if the meta key is the ConvertKit post meta key.
276 if ( $meta_key !== '_wp_convertkit_post_meta' ) {
277 return;
278 }
279
280 // Remove the Post ID from the cache.
281 $this->remove( $post_id );
282
283 }
284
285 /**
286 * Hook: fires when a Post is deleted or trashed. Removes the Post ID
287 * from the cache.
288 *
289 * @since 3.3.6
290 *
291 * @param int $post_id Post ID.
292 */
293 public function on_post_delete( $post_id ) {
294
295 // Remove the Post ID from the cache.
296 $this->remove( $post_id );
297
298 }
299
300 /**
301 * Hook: fires when a Post is restored from trash. Re-adds the Post ID
302 * to the cache if the Post has restrict_content enabled.
303 *
304 * @since 3.4.0
305 *
306 * @param int $post_id Post ID.
307 */
308 public function on_post_untrash( $post_id ) {
309
310 $post_settings = new ConvertKit_Post( $post_id );
311
312 // If the Post has restrict_content enabled, add it to the cache.
313 if ( $post_settings->restrict_content_enabled() ) {
314 // Add the Post ID to the cache.
315 $this->add( $post_id );
316 }
317
318 }
319
320 /**
321 * Hook: fires on any post status transition. Adds/removes the Post ID
322 * from the cache depending on whether the Post is transitioning to or
323 * from `publish`.
324 *
325 * @since 3.3.6
326 *
327 * @param string $new_status New post status.
328 * @param string $old_status Old post status.
329 * @param WP_Post $post Post object.
330 */
331 public function on_post_status_transition( $new_status, $old_status, $post ) {
332
333 // Skip if the post status is the same.
334 if ( $new_status === $old_status ) {
335 return;
336 }
337
338 // Check if the Post has restrict_content enabled.
339 $post_settings = new ConvertKit_Post( $post->ID );
340 if ( ! $post_settings->restrict_content_enabled() ) {
341 return;
342 }
343
344 // Add or remove the Post ID from the cache based on the new status.
345 if ( $new_status === 'publish' ) {
346 $this->add( $post->ID );
347 } elseif ( $old_status === 'publish' ) {
348 $this->remove( $post->ID );
349 }
350
351 }
352
353 /**
354 * Hook: fires when a Post is updated. If the Post is in the cache,
355 * refresh its cached URL in case the permalink changed (e.g. slug edit,
356 * parent change).
357 *
358 * @since 3.3.6
359 *
360 * @param int $post_id Post ID.
361 * @param WP_Post $post_after Post object after update (unused).
362 * @param WP_Post $post_before Post object before update (unused).
363 */
364 public function on_post_updated( $post_id, $post_after, $post_before ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
365
366 // Get the cache.
367 $cache = $this->get();
368
369 // Skip if the Post ID is not in the cache.
370 if ( ! array_key_exists( $post_id, $cache ) ) {
371 return;
372 }
373
374 // Update the cache.
375 $this->add( $post_id );
376
377 }
378
379 /**
380 * Hook: fires when the permalink structure, site URL or home URL changes.
381 * Rebuilds the cache so cached relative URLs reflect the new structure.
382 *
383 * This is a void wrapper around rebuild() so it can be safely used as an
384 * action callback (rebuild() returns the rebuilt cache for programmatic
385 * callers).
386 *
387 * @since 3.3.6
388 */
389 public function on_permalink_change() {
390
391 $this->rebuild();
392
393 }
394
395 }
396