PluginProbe
Restrict User Access – Ultimate Membership & Content Protection / 2.7
Restrict User Access – Ultimate Membership & Content Protection v2.7
trunk 1.3 2.0 2.1.3 2.2.3 2.3.2 2.4 2.4.1 2.4.2 2.4.3 2.5 2.6 2.6.1 2.7 2.7.1 2.8 2.8.1
restrict-user-access / level.php

level.php in Restrict User Access – Ultimate Membership & Content Protection 2.7, at level.php

838 lines 25.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package Restrict User Access
4 * @author Joachim Jensen <joachim@dev.institute>
5 * @license GPLv3
6 * @copyright 2024 by Joachim Jensen
7 */
8
9 defined('ABSPATH') || exit;
10
11 final class RUA_Level_Manager
12 {
13 /**
14 * Metadata
15 *
16 * @var WPCACollection
17 */
18 private $metadata;
19
20 public function __construct()
21 {
22 $this->add_actions();
23 $this->add_filters();
24
25 add_shortcode('restrict', [$this,'shortcode_restrict']);
26 add_shortcode('restrict-inner', [$this,'shortcode_restrict']);
27 add_shortcode('rua-user-levels', [$this,'shortcode_user_levels']);
28 }
29
30 /**
31 * Add callbacks to actions queue
32 *
33 * @since 0.5
34 */
35 protected function add_actions()
36 {
37 add_action(
38 'template_redirect',
39 [$this,'authorize_access']
40 );
41 add_action(
42 'init',
43 [$this,'create_restrict_type'],
44 99
45 );
46 add_action(
47 'user_register',
48 [$this,'registered_add_level']
49 );
50 }
51
52 /**
53 * Add callbacks to filters queue
54 *
55 * @since 0.5
56 */
57 protected function add_filters()
58 {
59 if (!is_admin()) {
60 add_filter(
61 'show_admin_bar',
62 [$this,'show_admin_toolbar'],
63 99
64 );
65 add_filter(
66 'rua/auth/page-no-access',
67 [$this, 'set_multilingual_non_member_action_page'],
68 10,
69 2
70 );
71 } else {
72 add_action('auth_redirect', [$this, 'authorize_admin_access']);
73 }
74
75 add_filter(
76 'pre_wp_update_comment_count_now',
77 [$this, 'update_member_count'],
78 10,
79 3
80 );
81
82 add_filter('get_edit_post_link', [$this,'get_edit_post_link'], 10, 3);
83 add_filter('get_delete_post_link', [$this,'get_delete_post_link'], 10, 3);
84 }
85
86 public function update_member_count($new, $old, $post_id)
87 {
88 $post = get_post($post_id);
89 if ($post->post_type !== RUA_App::TYPE_RESTRICT) {
90 return $new;
91 }
92
93 global $wpdb;
94 return (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM $wpdb->comments WHERE comment_type = '%s' AND comment_post_ID = %d", RUA_User_Level::ENTITY_TYPE, $post_id));
95 }
96
97 /**
98 * @param int $user_id
99 * @return void
100 */
101 public function authorize_admin_access($user_id)
102 {
103 if (defined('DOING_AJAX') && DOING_AJAX) {
104 return;
105 }
106
107 $rua_user = rua_get_user($user_id);
108 if ($rua_user->has_global_access()) {
109 return;
110 }
111
112 $user_levels = $rua_user->get_level_ids();
113 if (empty($user_levels)) {
114 return;
115 }
116
117 $metadata = $this->metadata()->get('admin_access');
118 foreach ($user_levels as $level_id) {
119 //bail if user has at least 1 level with admin access
120 if ($metadata->get_data($level_id, true)) {
121 return;
122 }
123 }
124
125 if (apply_filters('rua/auth/admin-access', false, $rua_user)) {
126 return;
127 }
128
129 wp_die(__('Sorry, you are not allowed to access this page.'));
130 }
131
132 /**
133 * Maybe hide admin toolbar for Users
134 *
135 * @since 1.1
136 * @return bool
137 */
138 public function show_admin_toolbar($show)
139 {
140 $user = rua_get_user();
141 if ($user->has_global_access()) {
142 return $show;
143 }
144
145 $levels = $user->get_level_ids();
146 if (empty($levels)) {
147 return $show;
148 }
149
150 $metadata = $this->metadata()->get('hide_admin_bar');
151 //if user has at least 1 level without this option
152 //don't hide the toolbar
153 foreach ($levels as $level_id) {
154 if ($metadata->get_data($level_id) != '1') {
155 return $show;
156 }
157 }
158
159 return false;
160 }
161
162 /**
163 * Get level by name
164 *
165 * @since 0.6
166 * @param string $name
167 * @return WP_Post|bool
168 */
169 public function get_level_by_name($name)
170 {
171 $all_levels = RUA_App::instance()->get_levels();
172 foreach ($all_levels as $id => $level) {
173 if ($level->post_name == $name && $level->post_status == RUA_App::STATUS_ACTIVE) {
174 return $level;
175 }
176 }
177 return false;
178 }
179
180 /**
181 * @param array $atts
182 * @return string
183 */
184 public function shortcode_user_levels($atts)
185 {
186 $a = shortcode_atts([
187 'id' => null
188 ], $atts, 'rua-user-level');
189
190 $user = rua_get_user($a['id']);
191
192 $levels = RUA_App::instance()->get_levels();
193 $level_names = [];
194 foreach ($user->get_level_ids() as $id) {
195 if (isset($levels[$id])) {
196 $level_names[] = $levels[$id]->post_title;
197 }
198 }
199
200 return implode(', ', $level_names);
201 }
202
203 /**
204 * Restrict content in shortcode
205 *
206 * @version 0.1
207 * @param array $atts
208 * @param string $content
209 * @return string
210 */
211 public function shortcode_restrict($atts, $content = null)
212 {
213 $user = rua_get_user();
214 if ($user->has_global_access()) {
215 return do_shortcode($content);
216 }
217
218 $a = shortcode_atts([
219 'role' => '',
220 'level' => '',
221 'page' => 0,
222 'drip_days' => 0
223 ], $atts, 'restrict');
224
225 $has_access = false;
226
227 if ($a['level'] !== '') {
228 $has_negation = strpos($a['level'], '!') !== false;
229 $user_levels = array_flip($user->get_level_ids());
230 if (!empty($user_levels) || $has_negation) {
231 $level_names = explode(',', str_replace(' ', '', $a['level']));
232 $not_found = 0;
233 foreach ($level_names as $level_name) {
234 $level = $this->get_level_by_name(ltrim($level_name, '!'));
235 if (!$level) {
236 $not_found++;
237 continue;
238 }
239 //if level param is negated, give access only if user does not have it
240 if ($level->post_name != $level_name) {
241 $has_access = !isset($user_levels[$level->ID]);
242 } elseif (isset($user_levels[$level->ID])) {
243 $drip = (int)$a['drip_days'];
244 if ($drip > 0 && $user->has_level($level->ID)) {
245 //@todo if extended level drips content, use start date
246 //of level user is member of
247 $start = $user->level_memberships()->get($level->ID)->get_start();
248 if ($start > 0) {
249 $drip_time = strtotime('+' . $drip . ' days 00:00', $start);
250 $should_drip = apply_filters(
251 'rua/auth/content-drip',
252 time() <= $drip_time,
253 $user,
254 $level->ID
255 );
256 if ($should_drip) {
257 continue;
258 }
259 }
260 }
261 $has_access = true;
262 }
263 if ($has_access) {
264 break;
265 }
266 }
267 //if levels do not exist, make content visible
268 if (!$has_access && $not_found && $not_found === count($level_names)) {
269 $has_access = true;
270 }
271 }
272 } elseif ($a['role'] !== '') {
273 $user_roles = array_flip(wp_get_current_user()->roles);
274 if (!empty($user_roles)) {
275 $roles = explode(',', str_replace(' ', '', $a['role']));
276 foreach ($roles as $role_name) {
277 $role = ltrim($role_name, '!');
278 $not = $role != $role_name;
279 //when role is negated, give access if user does not have it
280 //otherwise give access only if user has it
281 if ($not xor isset($user_roles[$role])) {
282 $has_access = true;
283 break;
284 }
285 }
286 }
287 }
288
289 /**
290 * @var bool $has_access
291 * @var RUA_User_Interface $user
292 * @var array $a
293 */
294 $has_access = apply_filters('rua/shortcode/restrict', $has_access, $user, $a);
295
296 if (!$has_access) {
297 $content = '';
298
299 // Only apply the page content if it exists
300 $page = $a['page'] ? get_post($a['page']) : null;
301 if ($page) {
302 setup_postdata($page);
303 $content = get_the_content();
304 wp_reset_postdata();
305 }
306 }
307
308 return do_shortcode($content);
309 }
310
311 /**
312 * Get instance of metadata manager
313 *
314 * @since 1.0
315 * @return WPCACollection
316 */
317 public function metadata()
318 {
319 if (!$this->metadata) {
320 $this->_init_metadata();
321 }
322 return $this->metadata;
323 }
324
325 /**
326 * Create and populate metadata fields
327 *
328 * @since 0.1
329 * @return void
330 */
331 private function _init_metadata()
332 {
333 $options = [
334 new WPCAMeta(
335 'handle',
336 _x('Non-Member Action', 'option', 'restrict-user-access'),
337 0,
338 'select',
339 [
340 0 => __('Redirect', 'restrict-user-access'),
341 1 => __('Tease & Include', 'restrict-user-access')
342 ],
343 __('Redirect to another page or show teaser.', 'restrict-user-access')
344 ),
345 new WPCAMeta(
346 'page',
347 __('Page'),
348 0,
349 'select',
350 [],
351 __('Page to redirect to or display content from under teaser.', 'restrict-user-access')
352 ),
353 new WPCAMeta(
354 'duration',
355 __('Duration', 'restrict-user-access'),
356 'day',
357 'select',
358 [
359 'day' => __('Day(s)', 'restrict-user-access'),
360 'week' => __('Week(s)', 'restrict-user-access'),
361 'month' => __('Month(s)', 'restrict-user-access'),
362 'year' => __('Year(s)', 'restrict-user-access')
363 ],
364 __('Set to 0 for unlimited.', 'restrict-user-access')
365 ),
366 new WPCAMeta(
367 'caps',
368 __('Capabilities', 'restrict-user-access'),
369 [],
370 '',
371 [],
372 '',
373 [$this,'sanitize_capabilities']
374 ),
375 new WPCAMeta(
376 'hide_admin_bar',
377 __('Hide Admin Toolbar', 'restrict-user-access'),
378 '',
379 'checkbox',
380 [],
381 ''
382 ),
383 new WPCAMeta(
384 'default_access',
385 __('Deny Access to Unprotected Content', 'restrict-user-access'),
386 1,
387 'checkbox',
388 [],
389 '',
390 [$this, 'sanitize_checkbox_option']
391 ),
392 new WPCAMeta(
393 'admin_access',
394 __('Deny Access to Admin Area', 'restrict-user-access'),
395 1,
396 'checkbox',
397 [],
398 '',
399 [$this, 'sanitize_checkbox_option']
400 ),
401 new WPCAMeta(
402 'member_automations',
403 __('Member Automation', 'restrict-user-access'),
404 [],
405 'select',
406 [],
407 ''
408 )
409 ];
410
411 $this->metadata = new WPCACollection();
412 foreach ($options as $option) {
413 $this->metadata->put($option->get_id(), $option);
414 }
415
416 apply_filters('rua/metadata', $this->metadata);
417 }
418
419 /**
420 * @param array|mixed $value
421 *
422 * @return array
423 */
424 public function sanitize_capabilities($value)
425 {
426 $existing_capabilities = get_post_meta($_POST['post'], WPCACore::PREFIX . 'caps', false);
427
428 if ((is_array($value) && !empty($value)) || !empty($existing_capabilities)) {
429 $valid_values = [
430 -1 => true,
431 0 => true,
432 1 => true
433 ];
434 $value = (array) $value;
435 $user = rua_get_user();
436 if (!$user->has_global_access()) {
437 $value = array_intersect_key($value, $user->get_caps());
438 }
439
440 $value = array_merge($existing_capabilities, $value);
441 $inherited_caps = isset($_POST['inherited_caps']) ? $_POST['inherited_caps'] : [];
442 foreach ($value as $name => $cap) {
443 if (is_integer($name) || !isset($valid_values[$cap])) {
444 unset($value[$name]);
445 }
446 /**
447 * do not save if:
448 * - value is equal to inherited
449 * - no inherited value and unsetting
450 */
451 elseif (isset($inherited_caps[$name]) ? $inherited_caps[$name] == $cap
452 : $cap == -1) {
453 unset($value[$name]);
454 }
455 }
456 }
457 return $value;
458 }
459
460 /**
461 * ensure "0" is stored when unchecked
462 *
463 * @param mixed $value
464 * @return mixed
465 */
466 public function sanitize_checkbox_option($value)
467 {
468 if (empty($value)) {
469 return '0';
470 }
471 return $value;
472 }
473
474 /**
475 * Populate input fields for metadata
476 *
477 * @since 0.8
478 * @return void
479 */
480 public function populate_metadata()
481 {
482 }
483
484 /**
485 * Create restrict post type and add it to WPCACore
486 *
487 * @since 0.1
488 * @return void
489 */
490 public function create_restrict_type()
491 {
492 $capability_view = 'list_users';
493 $capability_edit = 'promote_users';
494
495 // Register the sidebar type
496 register_post_type(RUA_App::TYPE_RESTRICT, [
497 'labels' => [
498 'name' => __('Access Levels', 'restrict-user-access'),
499 'singular_name' => __('Access Level', 'restrict-user-access'),
500 'add_new' => _x('Add New', 'level', 'restrict-user-access'),
501 'add_new_item' => __('Add New Access Level', 'restrict-user-access'),
502 'edit_item' => __('Edit Access Level', 'restrict-user-access'),
503 'new_item' => __('New Access Level', 'restrict-user-access'),
504 'all_items' => __('Access Levels', 'restrict-user-access'),
505 'view_item' => __('View Access Level', 'restrict-user-access'),
506 'search_items' => __('Search Access Levels', 'restrict-user-access'),
507 'not_found' => __('No Access Levels found', 'restrict-user-access'),
508 'not_found_in_trash' => __('No Access Levels found in Trash', 'restrict-user-access'),
509 'parent_item_colon' => __('Extend Level', 'restrict-user-access'),
510 //wp-content-aware-engine specific
511 'ca_title' => __('Only members can visit these pages', 'restrict-user-access')
512 ],
513 'capabilities' => [
514 'edit_post' => $capability_edit,
515 'read_post' => $capability_view,
516 'delete_post' => $capability_edit,
517 'edit_posts' => $capability_edit,
518 'delete_posts' => $capability_edit,
519 'edit_others_posts' => $capability_edit,
520 'publish_posts' => $capability_edit,
521 'read_private_posts' => $capability_view
522 ],
523 'public' => false,
524 'hierarchical' => true,
525 'exclude_from_search' => true,
526 'publicly_queryable' => false,
527 'show_ui' => false,
528 'show_in_menu' => false,
529 'show_in_nav_menus' => false,
530 'show_in_admin_bar' => false,
531 'menu_icon' => RUA_App::ICON_SVG,
532 'has_archive' => false,
533 'rewrite' => false,
534 'query_var' => false,
535 'supports' => ['title','page-attributes'],
536 'can_export' => false,
537 'delete_with_user' => false
538 ]);
539
540 WPCACore::types()->add(RUA_App::TYPE_RESTRICT);
541 }
542
543 /**
544 * @param string|int $page
545 * @param RUA_User_Interface $rua_user
546 * @return string|int
547 */
548 public function set_multilingual_non_member_action_page($page, $rua_user)
549 {
550 if (!is_numeric($page)) {
551 return $page;
552 }
553
554 if (defined('POLYLANG_VERSION')) {
555 $language_current = pll_current_language();
556 $language_target = $language_current !== false ? $language_current : pll_default_language();
557
558 if ($language_target !== false) {
559 $page_current_language = pll_get_post($page, $language_target);
560 //ensure translated page exists
561 if (!empty($page_current_language)) {
562 return $page_current_language;
563 }
564 }
565 }
566
567 return $page;
568 }
569
570 /**
571 * Get conditional restrictions
572 * and authorize access for user
573 *
574 * @since 0.1
575 * @return void
576 */
577 public function authorize_access()
578 {
579 $rua_user = rua_get_user();
580
581 if ($rua_user->has_global_access()) {
582 return;
583 }
584
585 $authorized_levels = WPCACore::get_posts(RUA_App::TYPE_RESTRICT);
586
587 if ($authorized_levels === false) {
588 return;
589 }
590
591 $user_levels = array_flip(array_reverse($rua_user->get_level_ids()));
592 $kick = false;
593
594 //does user have level to view unrestricted content by default?
595 foreach ($user_levels as $level => $val) {
596 if ($this->metadata()->get('default_access')->get_data($level, true)) {
597 $kick = false;
598 break;
599 }
600 $kick = $level;
601 }
602
603 //does user have authorized level?
604 foreach ($authorized_levels as $level) {
605 if (isset($user_levels[$level->ID])) {
606 $kick = false;
607 break;
608 }
609 $kick = $level->ID;
610 }
611
612 if (!empty($authorized_levels) && $kick === false && $rua_user->get_id() !== 0) {
613 $conditions = WPCACore::get_conditions(RUA_App::TYPE_RESTRICT);
614 foreach ($conditions as $condition => $level) {
615 //Check post type
616 if (!isset($authorized_levels[$level])) {
617 continue;
618 }
619
620 $drip = get_post_meta($condition, RUA_App::META_PREFIX . 'opt_drip', true);
621 //Restrict access to dripped content
622 if ($drip > 0 && $rua_user->has_level($level)) {
623 //@todo if extended level drips content, use start date
624 //of level user is member of
625 $start = $rua_user->level_memberships()->get($level)->get_start();
626 if ($start > 0) {
627 $drip_time = strtotime('+' . $drip . ' days 00:00', $start);
628 $should_drip = apply_filters(
629 'rua/auth/content-drip',
630 time() <= $drip_time,
631 $rua_user,
632 $level
633 );
634 if ($should_drip) {
635 $kick = $level;
636 continue;
637 }
638 }
639 }
640 $kick = false;
641 break;
642 }
643 }
644
645 $kick = apply_filters('rua/auth/content-access', $kick, $rua_user);
646 if ($kick === false || $kick === null) {
647 return;
648 }
649
650 $action = is_archive() || (is_home() && !is_page()) ? 0 : $this->metadata()->get('handle')->get_data($kick);
651
652 self::$page = apply_filters('rua/auth/page-no-access', $this->metadata()->get('page')->get_data($kick), $rua_user);
653 switch ($action) {
654 case 0:
655 $redirect = '';
656
657 $current_path = remove_query_arg('redirect_to', add_query_arg(null, null));
658 $parts = parse_url(get_site_url());
659 $pos = isset($parts['path']) ? stripos($current_path, $parts['path']) : false;
660 if ($pos !== false) {
661 $relative_path = substr($current_path, $pos + strlen($parts['path']));
662 } else {
663 $relative_path = $current_path;
664 }
665
666 if (is_numeric(self::$page)) {
667 if (self::$page != get_the_ID()) {
668 $redirect = get_permalink(self::$page);
669 }
670 } else {
671 /**
672 * WP always appends /
673 * also check case where non-member action does not have it,
674 * which can cause infinite loop
675 */
676 if ($relative_path != self::$page && $relative_path != self::$page . '/') {
677 $redirect = get_site_url() . self::$page;
678 }
679 }
680
681 //only redirect if current page != redirect page
682 if ($redirect) {
683 wp_safe_redirect(add_query_arg(
684 'redirect_to',
685 urlencode($current_path),
686 $redirect
687 ));
688 exit;
689 }
690 break;
691 case 1:
692 add_filter('the_content', [$this,'content_tease'], 8);
693 break;
694 default: break;
695 }
696 }
697
698 /**
699 * Carry over page from restriction metadata
700 * @var integer
701 */
702 public static $page = false;
703
704 /**
705 * Limit content to only show teaser and
706 * page content from restriction metadata
707 *
708 * @since 0.1
709 * @param string $content
710 * @return string
711 */
712 public function content_tease($content)
713 {
714 if (!in_the_loop()) {
715 return $content;
716 }
717
718 if (get_queried_object_id() !== get_the_ID()) {
719 return $content;
720 }
721
722 if (preg_match('/(<span id="more-[0-9]*"><\/span>)/', $content, $matches)) {
723 $teaser = explode($matches[0], $content, 2);
724 $content = $teaser[0];
725 } else {
726 $content = '';
727 }
728
729 if (is_numeric(self::$page)) {
730 setup_postdata(get_post(self::$page));
731 $content .= get_the_content();
732 wp_reset_postdata();
733 }
734
735 remove_filter('the_content', [$this, 'content_tease'], 8);
736 return $content;
737 }
738
739 /**
740 * Get all capabilities of one or multiple levels
741 *
742 * If you pass an array the order of these levels should be set correctly!
743 * The first level caps will be overwritten by the second etc.
744 *
745 * @since 0.13
746 * @param array|int $levels
747 * @return array
748 */
749 public function get_levels_caps($levels)
750 {
751 $levels = (array) $levels;
752 $caps = [];
753 foreach ($levels as $level) {
754 $level_caps = $this->metadata()->get('caps')->get_data($level, true);
755 foreach ($level_caps as $key => $level_cap) {
756 if ($level_cap > -1) {
757 $caps[$key] = (bool)$level_cap;
758 } else {
759 unset($caps[$key]);
760 }
761 }
762 }
763 return $caps;
764 }
765
766 /**
767 * Maybe add level on user register
768 *
769 * @since 0.10
770 * @param int $user_id
771 * @return void
772 */
773 public function registered_add_level($user_id)
774 {
775 try {
776 $level_id = get_option('rua-registration-level', 0);
777 if ($level_id) {
778 rua_get_user($user_id)->add_level($level_id);
779 }
780 } catch (Exception $e) {
781 }
782 }
783
784 /**
785 * Get level edit link
786 * TODO: Consider changing post type _edit_link instead
787 *
788 * @since 0.15
789 * @param string $link
790 * @param int $post_id
791 * @param string $context
792 * @return string
793 */
794 public function get_edit_post_link($link, $post_id, $context)
795 {
796 $post = get_post($post_id);
797 if ($post->post_type == RUA_App::TYPE_RESTRICT) {
798 $sep = '&';
799 if ($context == 'display') {
800 $sep = '&amp;';
801 }
802 $link = admin_url('admin.php?page=wprua-level' . $sep . 'post=' . $post_id);
803
804 //load page in all languages for wpml
805 if (defined('ICL_SITEPRESS_VERSION') || defined('POLYLANG_VERSION')) {
806 $link .= $sep . 'lang=all';
807 }
808 }
809 return $link;
810 }
811
812 /**
813 * Get level delete link
814 * TODO: Consider changing post type _edit_link instead
815 *
816 * @since 0.15
817 * @param string $link
818 * @param int $post_id
819 * @param boolean $force_delete
820 * @return string
821 */
822 public function get_delete_post_link($link, $post_id, $force_delete)
823 {
824 $post = get_post($post_id);
825 if ($post->post_type == RUA_App::TYPE_RESTRICT) {
826 $action = ($force_delete || !EMPTY_TRASH_DAYS) ? 'delete' : 'trash';
827
828 $link = add_query_arg(
829 'action',
830 $action,
831 admin_url('admin.php?page=wprua-level&post=' . $post_id)
832 );
833 $link = wp_nonce_url($link, "$action-post_{$post_id}");
834 }
835 return $link;
836 }
837 }
838