PluginProbe
Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More / trunk
Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More vtrunk
trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.10 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 2.0.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 All 47 releases
content-control / classes / Services / Restrictions.php

Restrictions.php in Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More trunk, at classes/Services/Restrictions.php

401 lines 10.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Frontend feed setup.
4 *
5 * @copyright (c) 2021, Code Atlantic LLC.
6 * @package ContentControl
7 */
8
9 namespace ContentControl\Services;
10
11 use ContentControl\Models\Restriction;
12
13 use function ContentControl\plugin;
14 use function ContentControl\get_query;
15 use function ContentControl\current_query_context;
16 use function ContentControl\deep_clean_array;
17 use function ContentControl\get_the_content_id;
18
19 defined( 'ABSPATH' ) || exit;
20
21 /**
22 * Restrictions service.
23 */
24 class Restrictions {
25
26 /**
27 * Array of all restrictions sorted by priority.
28 *
29 * @var Restriction[]|null
30 */
31 public $restrictions;
32
33 /**
34 * Array of all restrictions by ID.
35 *
36 * @var array<int,Restriction>|null
37 */
38 public $restrictions_by_id;
39
40 /**
41 * Simple internal request based cache.
42 *
43 * @var array<int,array<string,Restriction[]|false>>
44 */
45 public $restriction_matches_cache = [];
46
47 /**
48 * Initialize the service.
49 *
50 * @since 2.4.0
51 */
52 public function __construct() {
53 // Load restrictions.
54 $this->get_restrictions();
55
56 // Fire action to dependent services to initialize.
57 do_action( 'content_control/services/restrictions/init', $this );
58 }
59
60 /**
61 * Get a list of all restrictions sorted by priority.
62 *
63 * @return array<int,Restriction>
64 */
65 public function get_restrictions() {
66 if ( ! isset( $this->restrictions ) ) {
67 // Passively check for old restrictions, load them if found.
68 if ( \ContentControl\get_data_version( 'restrictions' ) === 1 ) {
69 $old_restrictions = \ContentControl\get_v1_restrictions();
70
71 if ( false !== $old_restrictions ) {
72 foreach ( $old_restrictions as $key => $restriction ) {
73 $restriction['id'] = (int) $key;
74 $this->restrictions_by_id[ $key ] = new Restriction( $restriction );
75 }
76 }
77 }
78
79 // This should run safely if no v1 rules are found, or if they don't exist.
80 if ( empty( $this->restrictions_by_id ) ) {
81 // Query restriction post type.
82 $restrictions = get_posts(
83 [
84 'post_type' => 'cc_restriction',
85 'posts_per_page' => -1,
86 'post_status' => 'publish',
87 // Order by menu order.
88 'orderby' => [
89 'menu_order' => 'ASC',
90 'post_modified' => 'DESC',
91 ],
92 ]
93 );
94
95 // Store the restrictions by ID for quick lookups.
96 foreach ( $restrictions as $restriction ) {
97 $this->restrictions_by_id[ $restriction->ID ] = new Restriction( $restriction );
98 }
99 }
100
101 // Sort by priority.
102 $this->restrictions = $this->sort_restrictions_by_priority( $this->restrictions_by_id ?? [] );
103 }
104
105 return $this->restrictions;
106 }
107
108 /**
109 * Get restriction, by ID, slug or object.
110 *
111 * @param int|string|Restriction $restriction Restriction ID, slug or object.
112 *
113 * @return Restriction|null
114 */
115 public function get_restriction( $restriction ) {
116 if ( $restriction instanceof Restriction ) {
117 return $restriction;
118 }
119
120 // If we don't have restrictions, get them.
121 if ( ! isset( $this->restrictions ) ) {
122 $this->get_restrictions();
123 }
124
125 // If restriction is an ID, get the object.
126 if ( is_numeric( $restriction ) && isset( $this->restrictions_by_id[ $restriction ] ) ) {
127 return $this->restrictions_by_id[ $restriction ];
128 }
129
130 // If restriction is a slug, get the object.
131 if ( is_string( $restriction ) ) {
132 // Check if string is a slug or title.
133 $slug = sanitize_title( $restriction );
134
135 if ( $slug === $restriction ) {
136 foreach ( $this->restrictions as $restriction_obj ) {
137 if ( $restriction_obj->slug === $restriction ) {
138 return $restriction_obj;
139 }
140 }
141 }
142
143 // Check if string is a title.
144 foreach ( $this->restrictions as $restriction_obj ) {
145 if ( $restriction_obj->title === $restriction ) {
146 return $restriction_obj;
147 }
148 }
149 }
150
151 return null;
152 }
153
154 /**
155 * Get applicable restrictions for the given content.
156 *
157 * If $single is true, return the first applicable restriction. If false, return all applicable restrictions.
158 * Sorted by priority and cached internally.
159 *
160 * @param int|null $content_id Content ID.
161 * @param bool $single Whether to return a single match or an array of matches.
162 *
163 * @return Restriction|Restriction[]|false
164 */
165 public function get_applicable_restrictions( $content_id = null, $single = true ) {
166 // Check if we have a match in memory.
167 $matches = $this->get_restriction_matches_from_cache( $content_id, $single );
168
169 if ( is_null( $matches ) ) {
170 $matches = [];
171 $restrictions = $this->get_restrictions();
172
173 if ( ! empty( $restrictions ) ) {
174 foreach ( $restrictions as $restriction ) {
175 if ( $restriction->check_rules() ) {
176 $matches[] = $restriction;
177 if ( $single ) {
178 break;
179 }
180 }
181 }
182 }
183
184 // If no matches, return false.
185 if ( empty( $matches ) ) {
186 $matches = false;
187 }
188
189 // Cache the matches.
190 $this->set_restriction_matches_in_cache( $content_id, $matches, $single );
191 }
192
193 if ( $single ) {
194 return $matches[0] ?? false;
195 }
196
197 return ! empty( $matches ) ? $matches : false;
198 }
199
200 /**
201 * Check if post has applicable restrictions.
202 *
203 * Cached via get_applicable_restriction().
204 *
205 * @param int|null $post_id Post ID.
206 *
207 * @return boolean
208 */
209 public function has_applicable_restrictions( $post_id = null ) {
210 return false !== $this->get_applicable_restrictions( $post_id, true );
211 }
212
213 /**
214 * Check if user meets requirements for given restriction.
215 *
216 * @param int|string|Restriction $restriction Restriction ID, slug or object.
217 *
218 * @return boolean
219 */
220 public function user_meets_requirements( $restriction ) {
221 $restriction = $this->get_restriction( $restriction );
222
223 if ( null === $restriction ) {
224 return false;
225 }
226
227 static $cache = [];
228 $cache_key = $restriction->id;
229
230 // Check cache.
231 if ( isset( $cache[ $cache_key ] ) ) {
232 return $cache[ $cache_key ];
233 }
234
235 $user_meets_requirements = $restriction->user_meets_requirements();
236
237 // Cache result.
238 $cache[ $cache_key ] = $user_meets_requirements;
239
240 return $user_meets_requirements;
241 }
242
243 /**
244 * Sort restrictions based on post sort order.
245 *
246 * MOVE to restrictions file.
247 *
248 * @param \ContentControl\Models\Restriction[] $restrictions Restrictions.
249 *
250 * @return \ContentControl\Models\Restriction[]
251 */
252 public function sort_restrictions_by_priority( $restrictions ) {
253 usort( $restrictions, function ( $a, $b ) {
254 return $a->priority - $b->priority;
255 });
256
257 return $restrictions;
258 }
259
260 /**
261 * Generate cache key for restrictions.
262 *
263 * @param int|null $content_id Content ID.
264 *
265 * @return string
266 */
267 public function generate_restriction_matches_cache_key( $content_id ) {
268 $context = current_query_context();
269 $content_id = $content_id ?? get_the_content_id();
270
271 if ( strpos( $context, 'posts' ) !== false ) {
272 $cache_key = "post-{$content_id}";
273 } elseif ( strpos( $context, 'terms' ) !== false ) {
274 $cache_key = "term-{$content_id}";
275 } elseif ( 'main' === $context || 'restapi' === $context ) {
276 try {
277 $query = get_query();
278 $hash_vars = deep_clean_array( $query->query_vars ?? [] );
279 $query_hash = md5( maybe_serialize( $hash_vars ) );
280 } catch ( \Exception $e ) {
281 $query_hash = md5( (string) wp_rand( 0, 100000 ) );
282 plugin( 'logging' )->log_unique( 'ERROR: ' . $e->getMessage() );
283 }
284
285 // Append query hash to cache key to allow persisting across requests.
286 $cache_key = "{$context}_{$query_hash}";
287
288 if ( $content_id ) {
289 $cache_key .= "_post-{$content_id}";
290 }
291 } else {
292 $cache_key = "unknown-{$context}";
293 }
294
295 /**
296 * Filter the cache key.
297 *
298 * @param string $cache_key Cache key.
299 * @param string $context Context.
300 * @param int|null $content_id Content ID.
301 *
302 * @return string
303 *
304 * @since 2.4.0
305 */
306 return apply_filters( 'content_control/generate_restriction_matches_cache_key', $cache_key, $context, $content_id );
307 }
308
309 /**
310 * Prime restriction matches cache.
311 *
312 * @param array<int,array<string,Restriction[]|false>> $restriction_matches_cache Restriction matches cache.
313 *
314 * @return void
315 *
316 * @since 2.4.0
317 */
318 public function prime_restriction_matches_cache( $restriction_matches_cache = null ) {
319 $this->restriction_matches_cache = $restriction_matches_cache ?? $this->restriction_matches_cache;
320 }
321
322 /**
323 * Get matches from cache.
324 *
325 * @param int|null $content_id Content ID.
326 * @param bool $single Whether to return a single match or an array of matches.
327 *
328 * @return Restriction[]|false|null
329 */
330 public function get_restriction_matches_from_cache( $content_id = null, $single = true ) {
331 // Get cache key.
332 $cache_key = $this->generate_restriction_matches_cache_key( $content_id );
333
334 /**
335 * Restrictions that match the content ID.
336 *
337 * @var Restriction[]|false|null Restriction cache for the given content ID.
338 */
339 $matches = $this->restriction_matches_cache[ $single ][ $cache_key ] ?? null;
340
341 // If no match for single, fallback on non-single and return that instead.
342 if ( $single && is_null( $matches ) && isset( $this->restriction_matches_cache[ false ][ $cache_key ] ) ) {
343 $matches = $this->restriction_matches_cache[ false ][ $cache_key ];
344 }
345
346 if ( is_null( $matches ) ) {
347 /**
348 * Allow loading from persistent cache.
349 *
350 * @param Restriction[]|false|null $matches Restriction cache for the given content ID.
351 * @param string $cache_key Cache key.
352 * @param int|null $content_id Content ID.
353 * @param Restrictions $restriction_service Restrictions instance.
354 *
355 * @return Restriction[]|false|null
356 *
357 * @since 2.4.0
358 */
359 $matches = apply_filters( 'content_control/get_restriction_matches_from_cache', null, $cache_key . ( $single ? '--single' : '' ), $content_id, $this );
360
361 // If we have a match, store it in memory rather than reaching out to object DB again.
362 if ( ! is_null( $matches ) ) {
363 $this->restriction_matches_cache[ $single ][ $cache_key ] = $matches;
364 }
365 }
366
367 return $matches;
368 }
369
370 /**
371 * Set in cache for matches.
372 *
373 * @param int|null $content_id Content ID.
374 * @param Restriction[]|false $matches Value to set.
375 * @param bool $single Whether to return a single match or an array of matches.
376 *
377 * @return void
378 */
379 public function set_restriction_matches_in_cache( $content_id = null, $matches = false, $single = true ) {
380 // Get cache key.
381 $cache_key = $this->generate_restriction_matches_cache_key( $content_id );
382
383 // Store the matches in memory for the remainder of the request.
384 $this->restriction_matches_cache[ $single ][ $cache_key ] = $matches;
385
386 /**
387 * Allow persisting to cache.
388 *
389 * @param string $cache_key Cache key.
390 * @param Restriction[]|false $matches Value to set.
391 * @param int|null $content_id Content ID.
392 * @param Restrictions $restriction_service Restrictions instance.
393 *
394 * @return void
395 *
396 * @since 2.4.0
397 */
398 do_action( 'content_control/set_restriction_matches_in_cache', $cache_key . ( $single ? '--single' : '' ), $matches, $content_id, $this );
399 }
400 }
401