| @@ -28,28 +28,308 @@ | ||
| 28 | 28 | */ |
| 29 | 29 | class c_ws_plugin__s2member_security_rest |
| 30 | 30 | { |
| 31 | 31 | /** |
| 32 | - * s2Member's Security Gate (protects WordPress content). | |
| 32 | + * Protects the resource selected by WordPress after route matching and validation. | |
| 33 | 33 | * |
| 34 | 34 | * @package s2Member\Security |
| 35 | 35 | * @since 240312 |
| 36 | 36 | * |
| 37 | - * @attaches-to ``add_filter('rest_pre_dispatch');`` | |
| 37 | + * @attaches-to ``add_filter('rest_request_before_callbacks');`` | |
| 38 | 38 | * |
| 39 | - * @return null May give a 403 error *(exiting script execution)*, when/if content is NOT available to the current User/Member. | |
| 39 | + * @param mixed $response Original response from earlier filters. | |
| 40 | + * @param array $handler Matched WordPress REST endpoint handler. | |
| 41 | + * @param WP_REST_Request $request Validated REST request. | |
| 42 | + * | |
| 43 | + * @return mixed Original response, or a membership WP_Error with HTTP 403. | |
| 40 | 44 | */ |
| 41 | - public static function security_gate($response, $handler, $request) // s2Member's Security Gate. | |
| 45 | + public static function security_gate($response, $handler, $request) | |
| 42 | 46 | { |
| 43 | - $route = $request->get_route(); // e.g. /wp/v2/pages | |
| 44 | - $route = explode('/', trim($route, '/')); | |
| 47 | + if(is_wp_error($response) || empty($handler['callback']) || !is_array($handler['callback'])) | |
| 48 | + return $response; | |
| 45 | 49 | |
| 46 | - if (empty($route[2]) || empty($route[3])) | |
| 47 | - return $response; | |
| 48 | - | |
| 49 | - $type = sanitize_key($route[2]); // e.g. pages | |
| 50 | - $id = (int)$route[3]; | |
| 51 | - | |
| 50 | + //261005.2320 Use the matched controller and validated ID; route segments and query IDs can describe different resources. | |
| 51 | + $controller = $handler['callback'][0]; | |
| 52 | + $id = (int)$request->get_param('id'); | |
| 53 | + if(!$id) | |
| 54 | + return $response; | |
| 55 | + | |
| 56 | + if($controller instanceof WP_REST_Posts_Controller) | |
| 57 | + { | |
| 58 | + $post = get_post($id); | |
| 59 | + $type = $post ? get_post_type_object($post->post_type) : NULL; | |
| 60 | + if($type && self::controller_owns_resource($type, $controller, $request)) | |
| 61 | + return self::check_post_access($post, $response); | |
| 62 | + } | |
| 63 | + else if($controller instanceof WP_REST_Terms_Controller) | |
| 64 | + { | |
| 65 | + $term = get_term($id); | |
| 66 | + $taxonomy = $term && !is_wp_error($term) ? get_taxonomy($term->taxonomy) : NULL; | |
| 67 | + if($taxonomy && self::controller_owns_resource($taxonomy, $controller, $request)) | |
| 68 | + return self::check_resource_access($term->taxonomy === 'category' ? 'categories' : ($term->taxonomy === 'post_tag' ? 'tags' : 'terms'), $id, $response); | |
| 69 | + } | |
| 70 | + else if($controller instanceof WP_REST_Comments_Controller && ($comment = get_comment($id)) && ($post = get_post($comment->comment_post_ID))) | |
| 71 | + return self::check_post_access($post, $response); | |
| 72 | + | |
| 73 | + return $response; | |
| 74 | + } | |
| 75 | + | |
| 76 | + /** | |
| 77 | + * Determines whether the matched REST controller owns the requested resource. | |
| 78 | + * | |
| 79 | + * @package s2Member\Security | |
| 80 | + * @since 261005 | |
| 81 | + * | |
| 82 | + * @param WP_Post_Type|WP_Taxonomy $resource Registered post type or taxonomy. | |
| 83 | + * @param WP_REST_Controller $controller Matched endpoint controller. | |
| 84 | + * @param WP_REST_Request $request Validated REST request. | |
| 85 | + * | |
| 86 | + * @return bool True if the controller owns the resource; false otherwise. | |
| 87 | + */ | |
| 88 | + private static function controller_owns_resource($resource, $controller, $request) | |
| 89 | + { | |
| 90 | + if(method_exists($resource, 'get_rest_controller')) | |
| 91 | + return $resource->get_rest_controller() === $controller; | |
| 92 | + //261005.2335 Older REST-enabled WordPress releases expose the registered base but lack these accessors. | |
| 93 | + if(empty($resource->show_in_rest)) | |
| 94 | + return FALSE; | |
| 95 | + $namespace = !empty($resource->rest_namespace) ? $resource->rest_namespace : 'wp/v2'; | |
| 96 | + $base = !empty($resource->rest_base) ? $resource->rest_base : $resource->name; | |
| 97 | + return (bool)preg_match('/^'.preg_quote('/'.$namespace.'/'.$base, '/').'\/\d+\/?$/', $request->get_route()); | |
| 98 | + } | |
| 99 | + | |
| 100 | + /** | |
| 101 | + * Checks REST access to a post, page, or comment parent. | |
| 102 | + * | |
| 103 | + * @package s2Member\Security | |
| 104 | + * @since 261005 | |
| 105 | + * | |
| 106 | + * @param WP_Post $post Existing post whose membership restrictions are checked. | |
| 107 | + * @param mixed $response Original response to preserve when access is allowed. | |
| 108 | + * | |
| 109 | + * @return mixed Original response, or a membership WP_Error with HTTP 403. | |
| 110 | + */ | |
| 111 | + private static function check_post_access($post, $response = NULL) | |
| 112 | + { | |
| 113 | + $type = $post->post_type === 'page' ? 'pages' : 'posts'; | |
| 114 | + return self::check_resource_access($type, (int)$post->ID, $response, $post->post_type); | |
| 115 | + } | |
| 116 | + | |
| 117 | + /** | |
| 118 | + * Registers REST collection and search access filters for exposed resources. | |
| 119 | + * | |
| 120 | + * @package s2Member\Security | |
| 121 | + * @since 261005 | |
| 122 | + * | |
| 123 | + * @attaches-to ``add_action('rest_api_init');`` | |
| 124 | + * | |
| 125 | + * @return void | |
| 126 | + */ | |
| 127 | + public static function register_collection_filters() | |
| 128 | + { | |
| 129 | + foreach(get_post_types(array('show_in_rest' => TRUE), 'names') as $post_type) | |
| 130 | + add_filter('rest_'.$post_type.'_query', array(__CLASS__, 'filter_post_query'), PHP_INT_MAX, 2); | |
| 131 | + foreach(get_taxonomies(array('show_in_rest' => TRUE), 'names') as $taxonomy) | |
| 132 | + add_filter('rest_'.$taxonomy.'_query', array(__CLASS__, 'filter_term_query'), PHP_INT_MAX, 2); | |
| 133 | + add_filter('rest_post_search_query', array(__CLASS__, 'filter_post_query'), PHP_INT_MAX, 2); | |
| 134 | + add_filter('rest_term_search_query', array(__CLASS__, 'filter_term_query'), PHP_INT_MAX, 2); | |
| 135 | + add_filter('rest_comment_query', array(__CLASS__, 'filter_comment_query'), PHP_INT_MAX, 2); | |
| 136 | + } | |
| 137 | + | |
| 138 | + /** | |
| 139 | + * Determines whether configured restrictions require a REST collection access check. | |
| 140 | + * | |
| 141 | + * @package s2Member\Security | |
| 142 | + * @since 261005 | |
| 143 | + * | |
| 144 | + * @param bool $posts Include post/page, Specific Post/Page, and Custom Capability rules? | |
| 145 | + * | |
| 146 | + * @return bool True if an applicable restriction may deny access to collection items. | |
| 147 | + */ | |
| 148 | + private static function needs_query_check($posts = FALSE) | |
| 149 | + { | |
| 150 | + $o = $GLOBALS['WS_PLUGIN__']['s2member']['o']; | |
| 151 | + if(empty($o['membership_options_page'])) | |
| 152 | + return FALSE; | |
| 153 | + $user = wp_get_current_user(); | |
| 154 | + for($n = $GLOBALS['WS_PLUGIN__']['s2member']['c']['levels']; $n >= 0; $n--) | |
| 155 | + foreach($posts ? array('posts', 'pages', 'catgs', 'ptags', 'ruris') : array('catgs', 'ptags', 'ruris') as $kind) | |
| 156 | + if(!empty($o['level'.$n.'_'.$kind]) && !$user->has_cap('access_s2member_level'.$n)) | |
| 157 | + return TRUE; | |
| 158 | + if(!empty($o['login_redirection_override']) && !$user->has_cap('access_s2member_level0')) | |
| 159 | + return TRUE; | |
| 160 | + if($posts) | |
| 161 | + { | |
| 162 | + if((!empty($o['login_welcome_page']) || !empty($o['file_download_limit_exceeded_page'])) && !$user->has_cap('access_s2member_level0')) | |
| 163 | + return TRUE; | |
| 164 | + if(!empty($o['specific_ids']) || c_ws_plugin__s2member_utils_gets::get_unavailable_singular_ids_with_ccaps($user->ID ? $user : FALSE)) | |
| 165 | + return TRUE; | |
| 166 | + } | |
| 167 | + return FALSE; | |
| 168 | + } | |
| 169 | + | |
| 170 | + /** | |
| 171 | + * Excludes protected posts before REST collection/search pagination and counts. | |
| 172 | + * | |
| 173 | + * @package s2Member\Security | |
| 174 | + * @since 261005 | |
| 175 | + * | |
| 176 | + * @attaches-to ``add_filter('rest_{$post_type}_query'); add_filter('rest_post_search_query');`` | |
| 177 | + * | |
| 178 | + * @param array $args Prepared WP_Query arguments. | |
| 179 | + * @param WP_REST_Request $request Validated collection or search request. | |
| 180 | + * | |
| 181 | + * @return array Query arguments with inaccessible posts excluded. | |
| 182 | + */ | |
| 183 | + public static function filter_post_query($args, $request) | |
| 184 | + { | |
| 185 | + if(!self::needs_query_check(TRUE)) | |
| 186 | + return $args; | |
| 187 | + $scan = $args; | |
| 188 | + //261005.2324 Scan only matching candidates in bounded batches; presentation filters must not decide REST access. | |
| 189 | + $scan['fields'] = 'ids'; | |
| 190 | + $scan['posts_per_page'] = 200; | |
| 191 | + $scan['nopaging'] = FALSE; | |
| 192 | + $scan['paged'] = 1; | |
| 193 | + $scan['orderby'] = 'ID'; | |
| 194 | + $scan['order'] = 'ASC'; | |
| 195 | + $scan['suppress_filters'] = TRUE; | |
| 196 | + $scan['no_found_rows'] = TRUE; | |
| 197 | + $excluded = array(); | |
| 198 | + for($offset = 0; ; $offset += 200) | |
| 199 | + { | |
| 200 | + $scan['offset'] = $offset; | |
| 201 | + $ids = get_posts($scan); | |
| 202 | + if(!$ids) | |
| 203 | + break; | |
| 204 | + _prime_post_caches($ids, TRUE, TRUE); | |
| 205 | + foreach($ids as $id) | |
| 206 | + if(($post = get_post($id)) && is_wp_error(self::check_post_access($post))) | |
| 207 | + $excluded[] = (int)$id; | |
| 208 | + if(count($ids) < 200) | |
| 209 | + break; | |
| 210 | + } | |
| 211 | + return self::exclude_query_ids($args, $excluded, 'post__in', 'post__not_in'); | |
| 212 | + } | |
| 213 | + | |
| 214 | + /** | |
| 215 | + * Excludes protected terms from REST taxonomy collections and term search. | |
| 216 | + * | |
| 217 | + * @package s2Member\Security | |
| 218 | + * @since 261005 | |
| 219 | + * | |
| 220 | + * @attaches-to ``add_filter('rest_{$taxonomy}_query'); add_filter('rest_term_search_query');`` | |
| 221 | + * | |
| 222 | + * @param array $args Prepared WP_Term_Query arguments. | |
| 223 | + * @param WP_REST_Request $request Validated collection or search request. | |
| 224 | + * | |
| 225 | + * @return array Query arguments with inaccessible terms excluded. | |
| 226 | + */ | |
| 227 | + public static function filter_term_query($args, $request) | |
| 228 | + { | |
| 229 | + if(!self::needs_query_check()) | |
| 230 | + return $args; | |
| 231 | + $scan = $args; | |
| 232 | + $scan['number'] = 0; | |
| 233 | + $scan['offset'] = 0; | |
| 234 | + $scan['fields'] = 'all'; | |
| 235 | + if(!empty($scan['post'])) | |
| 236 | + $scan['object_ids'] = $scan['post']; | |
| 237 | + $terms = get_terms($scan); | |
| 238 | + $excluded = array(); | |
| 239 | + if(!is_wp_error($terms)) | |
| 240 | + foreach($terms as $term) | |
| 241 | + if(is_wp_error(self::check_resource_access($term->taxonomy === 'category' ? 'categories' : ($term->taxonomy === 'post_tag' ? 'tags' : 'terms'), (int)$term->term_id))) | |
| 242 | + $excluded[] = (int)$term->term_id; | |
| 243 | + return self::exclude_query_ids($args, $excluded, 'include', 'exclude'); | |
| 244 | + } | |
| 245 | + | |
| 246 | + /** | |
| 247 | + * Excludes comments whose parent posts are inaccessible to the current user. | |
| 248 | + * | |
| 249 | + * @package s2Member\Security | |
| 250 | + * @since 261005 | |
| 251 | + * | |
| 252 | + * @attaches-to ``add_filter('rest_comment_query');`` | |
| 253 | + * | |
| 254 | + * @param array $args Prepared WP_Comment_Query arguments. | |
| 255 | + * @param WP_REST_Request $request Validated comment collection request. | |
| 256 | + * | |
| 257 | + * @return array Query arguments with comments on inaccessible posts excluded. | |
| 258 | + */ | |
| 259 | + public static function filter_comment_query($args, $request) | |
| 260 | + { | |
| 261 | + if(!self::needs_query_check(TRUE)) | |
| 262 | + return $args; | |
| 263 | + $scan = $args; | |
| 264 | + $scan['number'] = 200; | |
| 265 | + $scan['fields'] = 'all'; | |
| 266 | + $scan['count'] = FALSE; | |
| 267 | + $scan['orderby'] = 'comment_ID'; | |
| 268 | + $scan['order'] = 'ASC'; | |
| 269 | + $scan['hierarchical'] = FALSE; | |
| 270 | + $excluded = $parents = array(); | |
| 271 | + for($offset = 0; ; $offset += 200) | |
| 272 | + { | |
| 273 | + $scan['offset'] = $offset; | |
| 274 | + $comments = get_comments($scan); | |
| 275 | + _prime_post_caches(array_unique(wp_list_pluck($comments, 'comment_post_ID')), TRUE, TRUE); | |
| 276 | + foreach($comments as $comment) | |
| 277 | + { | |
| 278 | + $parent = (int)$comment->comment_post_ID; | |
| 279 | + if(!isset($parents[$parent])) | |
| 280 | + $parents[$parent] = ($post = get_post($parent)) && is_wp_error(self::check_post_access($post)); | |
| 281 | + if($parents[$parent]) | |
| 282 | + $excluded[] = (int)$comment->comment_ID; | |
| 283 | + } | |
| 284 | + if(count($comments) < 200) | |
| 285 | + break; | |
| 286 | + } | |
| 287 | + return self::exclude_query_ids($args, $excluded, 'comment__in', 'comment__not_in'); | |
| 288 | + } | |
| 289 | + | |
| 290 | + /** | |
| 291 | + * Applies exclusions without letting an exhausted include list expand the query. | |
| 292 | + * | |
| 293 | + * @package s2Member\Security | |
| 294 | + * @since 261005 | |
| 295 | + * | |
| 296 | + * @param array $args Prepared collection query arguments. | |
| 297 | + * @param int[] $excluded Resource IDs denied by membership restrictions. | |
| 298 | + * @param string $include_key Query argument containing explicit included IDs. | |
| 299 | + * @param string $exclude_key Query argument containing excluded IDs. | |
| 300 | + * | |
| 301 | + * @return array Query arguments with exclusions and a restrictive include list. | |
| 302 | + */ | |
| 303 | + private static function exclude_query_ids($args, $excluded, $include_key, $exclude_key) | |
| 304 | + { | |
| 305 | + if(!$excluded) | |
| 306 | + return $args; | |
| 307 | + $args[$exclude_key] = array_values(array_unique(array_merge(isset($args[$exclude_key]) ? wp_parse_id_list($args[$exclude_key]) : array(), $excluded))); | |
| 308 | + if(!empty($args[$include_key])) | |
| 309 | + { | |
| 310 | + $included = array_values(array_diff(wp_parse_id_list($args[$include_key]), $args[$exclude_key])); | |
| 311 | + //261005.2324 Keep an exhausted include list restrictive instead of expanding it to the whole collection. | |
| 312 | + $args[$include_key] = $included ? $included : array(0); | |
| 313 | + } | |
| 314 | + return $args; | |
| 315 | + } | |
| 316 | + | |
| 317 | + /** | |
| 318 | + * Checks the REST restriction policy against a canonical content resource. | |
| 319 | + * | |
| 320 | + * @package s2Member\Security | |
| 321 | + * @since 261005 | |
| 322 | + * | |
| 323 | + * @param string $type Resource kind: posts, pages, categories, tags, or terms. | |
| 324 | + * @param int $id Existing post or term ID. | |
| 325 | + * @param mixed $response Original response to preserve when access is allowed. | |
| 326 | + * @param string $post_type Actual registered post type for post/page restrictions. | |
| 327 | + * | |
| 328 | + * @return mixed Original response, or a membership WP_Error with HTTP 403. | |
| 329 | + */ | |
| 330 | + private static function check_resource_access($type, $id, $response = NULL, $post_type = '') | |
| 331 | + { | |
| 52 | 332 | $s2_options = $GLOBALS['WS_PLUGIN__']['s2member']['o']; |
| 53 | 333 | $s2_levels = $GLOBALS['WS_PLUGIN__']['s2member']['c']['levels']; |
| 54 | 334 | $ci = $s2_options['ruris_case_sensitive'] ? '' : 'i'; |
| 55 | 335 | |
| @@ -58,46 +338,50 @@ | ||
| 58 | 338 | |
| 59 | 339 | // Skip early? |
| 60 | 340 | if (empty($s2_options['membership_options_page']) |
| 61 | 341 | || empty($id) |
| 62 | - || $s2_options['membership_options_page'] == $id | |
| 63 | - || c_ws_plugin__s2member_systematics::is_wp_systematic_use_page()) { | |
| 342 | + || ($type === 'pages' && (int)$s2_options['membership_options_page'] === $id)) { | |
| 64 | 343 | return $response; |
| 65 | 344 | } |
| 66 | 345 | |
| 67 | 346 | |
| 68 | - // Post types array | |
| 69 | - // Use: in_array($type, $post_types, true) | |
| 70 | - $post_types = get_post_types(['show_in_rest' => true], 'names'); | |
| 71 | - | |
| 72 | 347 | // Get the regular path for this content, to check URI restriction |
| 73 | - $path = $_SERVER['REQUEST_URI']; // default | |
| 74 | - if (in_array($type, ['posts', 'pages'], true) | |
| 75 | - || in_array($type, $post_types, true)) { | |
| 348 | + $path = ''; // Only canonical resource URIs participate in content restrictions. | |
| 349 | + if (in_array($type, ['posts', 'pages'], true)) { | |
| 76 | 350 | $permalink = get_permalink($id); |
| 77 | - if (!is_wp_error($permalink)) { | |
| 78 | - $path = str_replace(home_url(), '', $permalink); | |
| 351 | + if ($permalink && !is_wp_error($permalink)) { | |
| 352 | + $path = c_ws_plugin__s2member_utils_urls::parse_uri($permalink); | |
| 79 | 353 | } |
| 80 | 354 | } elseif ($type === 'categories') { |
| 81 | 355 | $term_link = get_term_link($id, 'category'); |
| 82 | 356 | if (!is_wp_error($term_link)) { |
| 83 | - $path = str_replace(home_url(), '', $term_link); | |
| 357 | + $path = c_ws_plugin__s2member_utils_urls::parse_uri($term_link); | |
| 84 | 358 | } |
| 85 | 359 | } elseif ($type === 'tags') { |
| 86 | 360 | $term_link = get_term_link($id, 'post_tag'); |
| 87 | 361 | if (!is_wp_error($term_link)) { |
| 88 | - $path = str_replace(home_url(), '', $term_link); | |
| 362 | + $path = c_ws_plugin__s2member_utils_urls::parse_uri($term_link); | |
| 89 | 363 | } |
| 90 | 364 | } |
| 91 | 365 | |
| 92 | 366 | |
| 367 | + else if($type === 'terms' && ($term = get_term($id)) && !is_wp_error($term)) { | |
| 368 | + $term_link = get_term_link($term); | |
| 369 | + if (!is_wp_error($term_link)) { | |
| 370 | + $path = c_ws_plugin__s2member_utils_urls::parse_uri($term_link); | |
| 371 | + } | |
| 372 | + } | |
| 373 | + | |
| 374 | + //261005.2320 Page exemptions cannot be inherited by terms with the same numeric ID. | |
| 375 | + $page_id = $type === 'pages' ? $id : 0; | |
| 376 | + | |
| 93 | 377 | // Login welcome page |
| 94 | 378 | if($s2_options['login_welcome_page'] |
| 95 | - && $id === (int)$s2_options['login_welcome_page'] | |
| 379 | + && $page_id && $page_id === (int)$s2_options['login_welcome_page'] | |
| 96 | 380 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 97 | 381 | && (!$user || !$user->has_cap('access_s2member_level0')) |
| 98 | - && $id !== (int)$s2_options['membership_options_page']) | |
| 99 | - return new WP_Error('rest_forbidden', __('You do not have access to this page. [sys level 0]', 'text-domain'), array('status' => 403)); | |
| 382 | + && $page_id !== (int)$s2_options['membership_options_page']) | |
| 383 | + return new WP_Error('rest_forbidden', __('You do not have access to this page. [sys level 0]', 's2member'), array('status' => 403)); | |
| 100 | 384 | |
| 101 | 385 | // Login redirection override |
| 102 | 386 | else if($s2_options['login_redirection_override'] |
| 103 | 387 | && ($login_redirection_uri = c_ws_plugin__s2member_login_redirects::login_redirection_uri($user, 'root-returns-false')) |
| @@ -103,21 +387,21 @@ | ||
| 103 | 387 | && ($login_redirection_uri = c_ws_plugin__s2member_login_redirects::login_redirection_uri($user, 'root-returns-false')) |
| 104 | 388 | && preg_match('/^'.preg_quote($login_redirection_uri, '/').'$/'.$ci, $path) |
| 105 | 389 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 106 | 390 | && (!$user || !$user->has_cap('access_s2member_level0')) |
| 107 | - && $id !== (int)$s2_options['membership_options_page']) | |
| 108 | - return new WP_Error('rest_forbidden', __('You do not have access to this page. [sys level 0]', 'text-domain'), array('status' => 403)); | |
| 391 | + && $page_id !== (int)$s2_options['membership_options_page']) | |
| 392 | + return new WP_Error('rest_forbidden', __('You do not have access to this page. [sys level 0]', 's2member'), array('status' => 403)); | |
| 109 | 393 | |
| 110 | 394 | // File download limit exceeded page |
| 111 | 395 | else if($s2_options['file_download_limit_exceeded_page'] |
| 112 | - && $id === (int)$s2_options['file_download_limit_exceeded_page'] | |
| 396 | + && $page_id && $page_id === (int)$s2_options['file_download_limit_exceeded_page'] | |
| 113 | 397 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 114 | 398 | && (!$user || !$user->has_cap('access_s2member_level0')) |
| 115 | - && $id !== (int)$s2_options['membership_options_page']) | |
| 116 | - return new WP_Error('rest_forbidden', __('You do not have access to this page. [sys level 0]', 'text-domain'), array('status' => 403)); | |
| 399 | + && $page_id !== (int)$s2_options['membership_options_page']) | |
| 400 | + return new WP_Error('rest_forbidden', __('You do not have access to this page. [sys level 0]', 's2member'), array('status' => 403)); | |
| 117 | 401 | |
| 118 | 402 | // Do NOT protect Systematics (exceptions above) |
| 119 | - if (c_ws_plugin__s2member_systematics::is_systematic_use_page()) { | |
| 403 | + if (c_ws_plugin__s2member_systematics_sp::is_systematic_use_specific_page($page_id, $path)) { | |
| 120 | 404 | return $response; |
| 121 | 405 | } |
| 122 | 406 | |
| 123 | 407 | |
| @@ -129,9 +413,9 @@ | ||
| 129 | 413 | // "all" |
| 130 | 414 | if($s2_options['level'.$n.'_catgs'] === 'all' |
| 131 | 415 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 132 | 416 | && (!$user || !$user->has_cap('access_s2member_level'.$n))) |
| 133 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this category. [catg level %d]', 'text-domain'), $n), array('status' => 403)); | |
| 417 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this category. [catg level %d]', 's2member'), $n), array('status' => 403)); | |
| 134 | 418 | |
| 135 | 419 | // This specific categ |
| 136 | 420 | else if($s2_options['level'.$n.'_catgs'] |
| 137 | 421 | && in_array($id, ($catgs = preg_split('/['."\r\n\t".'\s;,]+/', $s2_options['level'.$n.'_catgs']))) |
| @@ -136,9 +420,9 @@ | ||
| 136 | 420 | else if($s2_options['level'.$n.'_catgs'] |
| 137 | 421 | && in_array($id, ($catgs = preg_split('/['."\r\n\t".'\s;,]+/', $s2_options['level'.$n.'_catgs']))) |
| 138 | 422 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 139 | 423 | && (!$user || !$user->has_cap('access_s2member_level'.$n))) |
| 140 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this category. [catg level %d]', 'text-domain'), $n), array('status' => 403)); | |
| 424 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this category. [catg level %d]', 's2member'), $n), array('status' => 403)); | |
| 141 | 425 | |
| 142 | 426 | // Check parent categs |
| 143 | 427 | else if($s2_options['level'.$n.'_catgs'] /* Check Category ancestry. */) |
| 144 | 428 | foreach(preg_split('/['."\r\n\t".'\s;,]+/', $s2_options['level'.$n.'_catgs']) as $catg) |
| @@ -145,9 +429,9 @@ | ||
| 145 | 429 | if($catg |
| 146 | 430 | && cat_is_ancestor_of($catg, $id) |
| 147 | 431 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 148 | 432 | && (!$user || !$user->has_cap('access_s2member_level'.$n))) |
| 149 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this category. [catg level %d]', 'text-domain'), $n), array('status' => 403)); | |
| 433 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this category. [catg level %d]', 's2member'), $n), array('status' => 403)); | |
| 150 | 434 | } |
| 151 | 435 | } |
| 152 | 436 | |
| 153 | 437 | |
| @@ -158,9 +442,9 @@ | ||
| 158 | 442 | // "all" |
| 159 | 443 | if($s2_options['level'.$n.'_ptags'] === 'all' |
| 160 | 444 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 161 | 445 | && (!$user || !$user->has_cap('access_s2member_level'.$n))) |
| 162 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this tag. [ptag level %d]', 'text-domain'), $n), array('status' => 403)); | |
| 446 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this tag. [ptag level %d]', 's2member'), $n), array('status' => 403)); | |
| 163 | 447 | |
| 164 | 448 | // This tag |
| 165 | 449 | else if($s2_options['level'.$n.'_ptags'] |
| 166 | 450 | && ($level_ptags = preg_split('/['."\r\n\t".';,]+/', $s2_options['level'.$n.'_ptags'])) |
| @@ -165,19 +449,18 @@ | ||
| 165 | 449 | else if($s2_options['level'.$n.'_ptags'] |
| 166 | 450 | && ($level_ptags = preg_split('/['."\r\n\t".';,]+/', $s2_options['level'.$n.'_ptags'])) |
| 167 | 451 | && ($tag = get_tag($id)) |
| 168 | 452 | && !empty($tag->name) |
| 169 | - && in_array($tag->name, $level_ptags) | |
| 453 | + && (in_array($tag->name, $level_ptags) || in_array($tag->slug, $level_ptags) || in_array((string)$tag->term_id, $level_ptags, TRUE)) | |
| 170 | 454 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 171 | 455 | && (!$user || !$user->has_cap('access_s2member_level'.$n))) |
| 172 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this tag. [ptag level %d]', 'text-domain'), $n), array('status' => 403)); | |
| 456 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this tag. [ptag level %d]', 's2member'), $n), array('status' => 403)); | |
| 173 | 457 | } |
| 174 | 458 | } |
| 175 | 459 | |
| 176 | 460 | |
| 177 | 461 | // All Posts & other inclusives. |
| 178 | - else if($type == 'posts' | |
| 179 | - || in_array($type, $post_types, true)) { | |
| 462 | + else if($type == 'posts') { | |
| 180 | 463 | // Post Level restrictions. |
| 181 | 464 | for($n = $s2_levels; $n >= 0; $n--) |
| 182 | 465 | { |
| 183 | 466 | if($s2_options['level'.$n.'_posts'] === 'all' |
| @@ -182,16 +465,16 @@ | ||
| 182 | 465 | { |
| 183 | 466 | if($s2_options['level'.$n.'_posts'] === 'all' |
| 184 | 467 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 185 | 468 | && (!$user || !$user->has_cap('access_s2member_level'.$n))) |
| 186 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this post. [post level %d]', 'text-domain'), $n), array('status' => 403)); | |
| 469 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this post. [post level %d]', 's2member'), $n), array('status' => 403)); | |
| 187 | 470 | |
| 188 | 471 | // what about custom post types? |
| 189 | 472 | else if(strpos($s2_options['level'.$n.'_posts'], 'all-') !== FALSE |
| 190 | - && (in_array('all-'.$type, preg_split('/['."\r\n\t".'\s;,]+/', $s2_options['level'.$n.'_posts'])) || in_array('all-'.$type.'s', preg_split('/['."\r\n\t".'\s;,]+/', $s2_options['level'.$n.'_posts']))) | |
| 473 | + && (in_array('all-'.$post_type, preg_split('/['."\r\n\t".'\s;,]+/', $s2_options['level'.$n.'_posts'])) || in_array('all-'.$post_type.'s', preg_split('/['."\r\n\t".'\s;,]+/', $s2_options['level'.$n.'_posts']))) | |
| 191 | 474 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 192 | 475 | && (!$user || !$user->has_cap('access_s2member_level'.$n))) |
| 193 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this post. [post level %d]', 'text-domain'), $n), array('status' => 403)); | |
| 476 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this post. [post level %d]', 's2member'), $n), array('status' => 403)); | |
| 194 | 477 | |
| 195 | 478 | // a specific post by ID |
| 196 | 479 | else if($s2_options['level'.$n.'_posts'] |
| 197 | 480 | && in_array($id, preg_split('/['."\r\n\t".'\s;,]+/', $s2_options['level'.$n.'_posts'])) |
| @@ -196,9 +479,9 @@ | ||
| 196 | 479 | else if($s2_options['level'.$n.'_posts'] |
| 197 | 480 | && in_array($id, preg_split('/['."\r\n\t".'\s;,]+/', $s2_options['level'.$n.'_posts'])) |
| 198 | 481 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 199 | 482 | && (!$user || !$user->has_cap('access_s2member_level'.$n))) |
| 200 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this post. [post level %d]', 'text-domain'), $n), array('status' => 403)); | |
| 483 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this post. [post level %d]', 's2member'), $n), array('status' => 403)); | |
| 201 | 484 | } |
| 202 | 485 | |
| 203 | 486 | // Category Level restrictions. |
| 204 | 487 | for($n = $s2_levels; $n >= 0; $n--) |
| @@ -205,9 +488,9 @@ | ||
| 205 | 488 | { |
| 206 | 489 | if($s2_options['level'.$n.'_catgs'] === 'all' |
| 207 | 490 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 208 | 491 | && (!$user || !$user->has_cap('access_s2member_level'.$n))) |
| 209 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this post. [catg level %d]', 'text-domain'), $n), array('status' => 403)); | |
| 492 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this post. [catg level %d]', 's2member'), $n), array('status' => 403)); | |
| 210 | 493 | |
| 211 | 494 | else if($s2_options['level'.$n.'_catgs'] |
| 212 | 495 | && ($catgs = preg_split('/['."\r\n\t".'\s;,]+/', $s2_options['level'.$n.'_catgs'])) |
| 213 | 496 | && (in_category($catgs, $id) || c_ws_plugin__s2member_utils_conds::in_descendant_category($catgs, $id)) |
| @@ -212,9 +495,9 @@ | ||
| 212 | 495 | && ($catgs = preg_split('/['."\r\n\t".'\s;,]+/', $s2_options['level'.$n.'_catgs'])) |
| 213 | 496 | && (in_category($catgs, $id) || c_ws_plugin__s2member_utils_conds::in_descendant_category($catgs, $id)) |
| 214 | 497 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 215 | 498 | && (!$user || !$user->has_cap('access_s2member_level'.$n))) |
| 216 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this post. [catg level %d]', 'text-domain'), $n), array('status' => 403)); | |
| 499 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this post. [catg level %d]', 's2member'), $n), array('status' => 403)); | |
| 217 | 500 | } |
| 218 | 501 | |
| 219 | 502 | if(has_term('', 'post_tag', $id)) // Here we take a look to see if this Post has any Tags. If so, we need to run the full set of routines against Tags also. |
| 220 | 503 | { |
| @@ -222,15 +505,15 @@ | ||
| 222 | 505 | { |
| 223 | 506 | if($s2_options['level'.$n.'_ptags'] === 'all' |
| 224 | 507 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 225 | 508 | && (!$user || !$user->has_cap('access_s2member_level'.$n))) |
| 226 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this post. [ptag level %d]', 'text-domain'), $n), array('status' => 403)); | |
| 509 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this post. [ptag level %d]', 's2member'), $n), array('status' => 403)); | |
| 227 | 510 | |
| 228 | 511 | else if($s2_options['level'.$n.'_ptags'] |
| 229 | 512 | && has_tag(preg_split('/['."\r\n\t".';,]+/', $s2_options['level'.$n.'_ptags']), $id) |
| 230 | 513 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 231 | 514 | && (!$user || !$user->has_cap('access_s2member_level'.$n))) |
| 232 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this post. [ptag level %d]', 'text-domain'), $n), array('status' => 403)); | |
| 515 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this post. [ptag level %d]', 's2member'), $n), array('status' => 403)); | |
| 233 | 516 | } |
| 234 | 517 | } |
| 235 | 518 | |
| 236 | 519 | if(is_array($ccaps_req = get_post_meta($id, 's2member_ccaps_req', TRUE)) |
| @@ -239,16 +522,16 @@ | ||
| 239 | 522 | { |
| 240 | 523 | foreach($ccaps_req as $ccap) // The ``$user`` MUST satisfy ALL Custom Capability requirements. Stored as an array of Custom Capabilities. |
| 241 | 524 | if(strlen($ccap) |
| 242 | 525 | && (!$user || !$user->has_cap('access_s2member_ccap_'.$ccap)) /* Does this ``$user``, have this Custom Capability? */) |
| 243 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this post. [ccap %s]', 'text-domain'), $ccap), array('status' => 403)); | |
| 526 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this post. [ccap %s]', 's2member'), $ccap), array('status' => 403)); | |
| 244 | 527 | } |
| 245 | 528 | |
| 246 | 529 | if($s2_options['specific_ids'] |
| 247 | 530 | && in_array($id, preg_split('/['."\r\n\t".'\s;,]+/', $s2_options['specific_ids'])) |
| 248 | 531 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 249 | - && !c_ws_plugin__s2member_sp_access::sp_access($id)) | |
| 250 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this post. [sp %d]', 'text-domain'), $id), array('status' => 403)); | |
| 532 | + && !c_ws_plugin__s2member_sp_access::sp_access($id, 'read-only')) | |
| 533 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this post. [sp %d]', 's2member'), $id), array('status' => 403)); | |
| 251 | 534 | |
| 252 | 535 | } |
| 253 | 536 | |
| 254 | 537 | |
| @@ -259,16 +542,16 @@ | ||
| 259 | 542 | // "all" |
| 260 | 543 | if($s2_options['level'.$n.'_pages'] === 'all' |
| 261 | 544 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 262 | 545 | && (!$user || !$user->has_cap('access_s2member_level'.$n))) |
| 263 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this page. [page level %d]', 'text-domain'), $n), array('status' => 403)); | |
| 546 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this page. [page level %d]', 's2member'), $n), array('status' => 403)); | |
| 264 | 547 | |
| 265 | - // "all" of custom post type "page" | |
| 266 | - else if(strpos($s2_options['level'.$n.'_posts'], 'all-') | |
| 548 | + //261006.0145 The Posts restriction setting also accepts "all-page" and "all-pages" to protect every page. | |
| 549 | + else if(strpos($s2_options['level'.$n.'_posts'], 'all-') !== FALSE | |
| 267 | 550 | && (in_array('all-page', preg_split('/['."\r\n\t".'\s;,]+/', $s2_options['level'.$n.'_posts'])) || in_array('all-pages', preg_split('/['."\r\n\t".'\s;,]+/', $s2_options['level'.$n.'_posts']))) |
| 268 | 551 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 269 | 552 | && (!$user || !$user->has_cap('access_s2member_level'.$n))) |
| 270 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this page. [page level %d]', 'text-domain'), $n), array('status' => 403)); | |
| 553 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this page. [page level %d]', 's2member'), $n), array('status' => 403)); | |
| 271 | 554 | |
| 272 | 555 | // Current page ID |
| 273 | 556 | else if($s2_options['level'.$n.'_pages'] |
| 274 | 557 | && in_array($id, preg_split('/['."\r\n\t".'\s;,]+/', $s2_options['level'.$n.'_pages'])) |
| @@ -273,9 +556,9 @@ | ||
| 273 | 556 | else if($s2_options['level'.$n.'_pages'] |
| 274 | 557 | && in_array($id, preg_split('/['."\r\n\t".'\s;,]+/', $s2_options['level'.$n.'_pages'])) |
| 275 | 558 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 276 | 559 | && (!$user || !$user->has_cap('access_s2member_level'.$n))) |
| 277 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this page. [page level %d]', 'text-domain'), $n), array('status' => 403)); | |
| 560 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this page. [page level %d]', 's2member'), $n), array('status' => 403)); | |
| 278 | 561 | } |
| 279 | 562 | |
| 280 | 563 | // Tag restriction |
| 281 | 564 | if(has_term('', 'post_tag', $id)) // Here we take a look to see if this Page has any Tags. If so, we need to run the full set of routines against Tags also. |
| @@ -285,9 +568,9 @@ | ||
| 285 | 568 | // "all" tags |
| 286 | 569 | if($s2_options['level'.$n.'_ptags'] === 'all' |
| 287 | 570 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 288 | 571 | && (!$user || !$user->has_cap('access_s2member_level'.$n))) |
| 289 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this page. [ptag level %d]', 'text-domain'), $n), array('status' => 403)); | |
| 572 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this page. [ptag level %d]', 's2member'), $n), array('status' => 403)); | |
| 290 | 573 | |
| 291 | 574 | // Has a restricted tag |
| 292 | 575 | else if($s2_options['level'.$n.'_ptags'] |
| 293 | 576 | && has_tag(preg_split('/['."\r\n\t".';,]+/', $s2_options['level'.$n.'_ptags']), $id) |
| @@ -292,9 +575,9 @@ | ||
| 292 | 575 | else if($s2_options['level'.$n.'_ptags'] |
| 293 | 576 | && has_tag(preg_split('/['."\r\n\t".';,]+/', $s2_options['level'.$n.'_ptags']), $id) |
| 294 | 577 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 295 | 578 | && (!$user || !$user->has_cap('access_s2member_level'.$n))) |
| 296 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this page. [ptag level %d]', 'text-domain'), $n), array('status' => 403)); | |
| 579 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this page. [ptag level %d]', 's2member'), $n), array('status' => 403)); | |
| 297 | 580 | } |
| 298 | 581 | } |
| 299 | 582 | |
| 300 | 583 | // Custom Capabilities |
| @@ -304,9 +587,9 @@ | ||
| 304 | 587 | { |
| 305 | 588 | foreach($ccaps_req as $ccap) // The ``$user`` MUST satisfy ALL Custom Capability requirements. Stored as an array of Custom Capabilities. |
| 306 | 589 | if(strlen($ccap) |
| 307 | 590 | && (!$user || !$user->has_cap('access_s2member_ccap_'.$ccap)) /* Does this ``$user``, have this Custom Capability? */) |
| 308 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this page. [ccap %s]', 'text-domain'), $ccap), array('status' => 403)); | |
| 591 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this page. [ccap %s]', 's2member'), $ccap), array('status' => 403)); | |
| 309 | 592 | } |
| 310 | 593 | |
| 311 | 594 | // Specific Page restriction |
| 312 | 595 | if($s2_options['specific_ids'] |
| @@ -311,10 +594,10 @@ | ||
| 311 | 594 | // Specific Page restriction |
| 312 | 595 | if($s2_options['specific_ids'] |
| 313 | 596 | && in_array($id, preg_split('/['."\r\n\t".'\s;,]+/', $s2_options['specific_ids'])) |
| 314 | 597 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 315 | - && !c_ws_plugin__s2member_sp_access::sp_access($id)) | |
| 316 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this page. [sp %d]', 'text-domain'), $id), array('status' => 403)); | |
| 598 | + && !c_ws_plugin__s2member_sp_access::sp_access($id, 'read-only')) | |
| 599 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this page. [sp %d]', 's2member'), $id), array('status' => 403)); | |
| 317 | 600 | |
| 318 | 601 | } |
| 319 | 602 | |
| 320 | 603 | |
| @@ -327,9 +610,9 @@ | ||
| 327 | 610 | if($str |
| 328 | 611 | && preg_match('/'.preg_quote($str, '/').'/'.$ci, $path) |
| 329 | 612 | && c_ws_plugin__s2member_no_cache::no_cache_constants('restricted') |
| 330 | 613 | && (!$user || !$user->has_cap('access_s2member_level'.$n))) |
| 331 | - return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this content. [ruri level %d]', 'text-domain'), $n), array('status' => 403)); | |
| 614 | + return new WP_Error('rest_forbidden', sprintf(__('You do not have access to this content. [ruri level %d]', 's2member'), $n), array('status' => 403)); | |
| 332 | 615 | } |
| 333 | 616 | } |
| 334 | 617 | |
| 335 | 618 | return $response; |