PluginProbe
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions / 260917
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions v260917
260917 260913 260909 260829 260814 260805 110710 110731 110812 110815 110912 110913 110915 110926 110927 111002 111003 111011 111017 111029 111105 111206 111216 111220 120213 All 189 releases
← All changes | src/includes/classes/utils-gets.inc.php +99 -20 260913260917 View file →
@@ -130,12 +130,23 @@
130 130 {
131 131 /** @var wpdb $wpdb WordPress DB object instance. */
132 132 global $wpdb; // Global DB object reference.
133 133
134 - if(is_array($post_ids = $wpdb->get_col("SELECT `ID` FROM `".$wpdb->posts."` WHERE `post_status` = 'publish' AND ".(($post_type) ? "`post_type` = '".esc_sql((string)$post_type)."'" : "`post_type` NOT IN('page','attachment','nav_menu_item','revision')"))))
135 - $post_ids = c_ws_plugin__s2member_utils_arrays::force_integers($post_ids);
134 + //260914.1643 Query-level access checks can request the same published Post IDs many times per page load, so cache each Posts-table/Post-Type combination for this request; including the table keeps switched Multisite blogs isolated.
135 + static $_post_ids = array(), $_post_changes = array();
136 + $_cache_key = $wpdb->posts.'|'.(string)$post_type;
136 137
137 - return (!empty($post_ids) && is_array($post_ids)) ? array_unique($post_ids) : array();
138 + //260914.1643 Refresh after normal WordPress post mutations so a write followed by another access check in the same request cannot reuse stale published Post IDs.
139 + $_changes = did_action('save_post') + did_action('deleted_post');
140 + if(!isset($_post_ids[$_cache_key]) || !isset($_post_changes[$_cache_key]) || $_post_changes[$_cache_key] !== $_changes)
141 + {
142 + $post_ids = $wpdb->get_col("SELECT `ID` FROM `".$wpdb->posts."` WHERE `post_status` = 'publish' AND ".(($post_type) ? "`post_type` = '".esc_sql((string)$post_type)."'" : "`post_type` NOT IN('page','attachment','nav_menu_item','revision')"));
143 + if(is_array($post_ids)) $post_ids = c_ws_plugin__s2member_utils_arrays::force_integers($post_ids);
144 +
145 + $_post_ids[$_cache_key] = (!empty($post_ids) && is_array($post_ids)) ? array_unique($post_ids) : array();
146 + $_post_changes[$_cache_key] = $_changes;
147 + }
148 + return $_post_ids[$_cache_key];
138 149 }
139 150
140 151 /**
141 152 * Retrieves a unique array of all published Child Post IDs in the database.
@@ -176,12 +187,23 @@
176 187 {
177 188 /** @var wpdb $wpdb WordPress DB object instance. */
178 189 global $wpdb; // Global DB object reference.
179 190
180 - if(is_array($page_ids = $wpdb->get_col("SELECT `ID` FROM `".$wpdb->posts."` WHERE `post_status` = 'publish' AND `post_type` = 'page'")))
181 - $page_ids = c_ws_plugin__s2member_utils_arrays::force_integers($page_ids);
191 + //260914.1643 Query-level access checks can request the complete published Page-ID list repeatedly in one page load, so cache it per Posts table for this request; the table key also isolates switched Multisite blogs.
192 + static $_page_ids = array(), $_post_changes = array();
193 + $_cache_key = $wpdb->posts;
182 194
183 - return (!empty($page_ids) && is_array($page_ids)) ? array_unique($page_ids) : array();
195 + //260914.1643 Refresh after normal WordPress post mutations so Pages created, deleted, or changed earlier in this request are reflected by later access checks.
196 + $_changes = did_action('save_post') + did_action('deleted_post');
197 + if(!isset($_page_ids[$_cache_key]) || !isset($_post_changes[$_cache_key]) || $_post_changes[$_cache_key] !== $_changes)
198 + {
199 + $page_ids = $wpdb->get_col("SELECT `ID` FROM `".$wpdb->posts."` WHERE `post_status` = 'publish' AND `post_type` = 'page'");
200 + if(is_array($page_ids)) $page_ids = c_ws_plugin__s2member_utils_arrays::force_integers($page_ids);
201 +
202 + $_page_ids[$_cache_key] = (!empty($page_ids) && is_array($page_ids)) ? array_unique($page_ids) : array();
203 + $_post_changes[$_cache_key] = $_changes;
204 + }
205 + return $_page_ids[$_cache_key];
184 206 }
185 207
186 208 /**
187 209 * Retrieves a unique array of all Singular IDs in the database that require Custom Capabilities.
@@ -219,9 +241,10 @@
219 241 {
220 242 /** @var wpdb $wpdb WordPress DB object instance. */
221 243 global $wpdb; // Global DB object reference.
222 244
223 - static $_results = array(), $_meta_changes = array();
245 + //260914.2132 Keep both the SQL rows and their lazily unserialized CCAP requirements for this request. The parsed cache is reset whenever post-meta changes invalidate the SQL rows, and anonymous visitors never pay the unserialization cost.
246 + static $_results = array(), $_result_ccaps = array(), $_meta_changes = array();
224 247 $_cache_key = $wpdb->posts.'|'.$wpdb->postmeta;
225 248 $_changes = did_action('added_post_meta') + did_action('updated_post_meta') + did_action('deleted_post_meta');
226 249 if(!isset($_results[$_cache_key]) || !isset($_meta_changes[$_cache_key]) || $_meta_changes[$_cache_key] !== $_changes)
227 250 {
@@ -227,11 +250,14 @@
227 250 {
228 251 $_results[$_cache_key] = $wpdb->get_results("SELECT `".$wpdb->postmeta."`.`post_id`, `".$wpdb->postmeta."`.`meta_value`, `".$wpdb->posts."`.`post_type`".
229 252 " FROM `".$wpdb->posts."`, `".$wpdb->postmeta."` WHERE `".$wpdb->posts."`.`ID` = `".$wpdb->postmeta."`.`post_id`".
230 253 " AND `".$wpdb->postmeta."`.`meta_key` = 's2member_ccaps_req' AND `".$wpdb->postmeta."`.`meta_value` != ''");
254 + $_result_ccaps[$_cache_key] = array();
231 255 $_meta_changes[$_cache_key] = $_changes;
232 256 }
233 - $results = $_results[$_cache_key]; unset($_cache_key, $_changes); //260901 Request-local SQL cache.
257 + $results = $_results[$_cache_key];
258 + $result_ccaps = &$_result_ccaps[$_cache_key];
259 + unset($_cache_key, $_changes); //260901 Request-local SQL cache.
234 260
235 261 if(is_array($results))
236 262 {
237 263 $bbpress_restrictions_enable = apply_filters('ws_plugin__s2member_bbpress_restrictions_enable', TRUE);
@@ -237,22 +263,33 @@
237 263 $bbpress_restrictions_enable = apply_filters('ws_plugin__s2member_bbpress_restrictions_enable', TRUE);
238 264 $bbpress_installed = c_ws_plugin__s2member_utils_conds::bbp_is_installed(); // bbPress is installed?
239 265 $bbpress_forum_post_type = $bbpress_installed ? bbp_get_forum_post_type() : ''; // Acquire the current post type for forums.
240 266 $bbpress_topic_post_type = $bbpress_installed ? bbp_get_topic_post_type() : ''; // Acquire the current post type for topics.
267 + //260914.2132 Cache each distinct CCAP decision only for this helper invocation; many protected Posts share the same CCAP, but a later query pass can still receive a different filtered capability result.
268 + $ccap_access = array();
241 269
242 - foreach($results as $r) // Now we need to check Custom Capabilities against ``$user``. If ``$user`` is a valid `WP_User` object, else all are unavailable.
270 + foreach($results as $_result_index => $r) // Now we need to check Custom Capabilities against ``$user``. If ``$user`` is a valid `WP_User` object, else all are unavailable.
243 271 {
244 272 if(!is_object($user) || empty($user->ID)) // No ``$user`` object? Maybe not logged-in?.
245 273 $singular_ids[] = (int)$r->post_id; // It's NOT available. There is no ``$user``.
246 274
247 - else if(is_array($ccaps = c_ws_plugin__s2member_utils_arrays::maybe_unserialize($r->meta_value))) // Make sure we unserialize.
275 + else
248 276 {
249 - foreach($ccaps as $ccap) // Test for Custom Capability Restrictions now.
250 - if(strlen($ccap) && !$user->has_cap('access_s2member_ccap_'.$ccap))
277 + if(!array_key_exists($_result_index, $result_ccaps))
278 + $result_ccaps[$_result_index] = c_ws_plugin__s2member_utils_arrays::maybe_unserialize($r->meta_value);
279 +
280 + if(is_array($ccaps = $result_ccaps[$_result_index]))
281 + {
282 + foreach($ccaps as $ccap) // Test for Custom Capability Restrictions now.
251 283 {
252 - $singular_ids[] = (int)$r->post_id; // It's NOT available.
253 - break; // Break now, no need to continue in this loop.
284 + $ccap = (string)$ccap;
285 + if(strlen($ccap) && !(isset($ccap_access[$ccap]) ? $ccap_access[$ccap] : ($ccap_access[$ccap] = (bool)$user->has_cap('access_s2member_ccap_'.$ccap))))
286 + {
287 + $singular_ids[] = (int)$r->post_id; // It's NOT available.
288 + break; // Break now, no need to continue in this loop.
289 + }
254 290 }
291 + }
255 292 }
256 293 if($bbpress_restrictions_enable && $bbpress_installed && $r->post_type === $bbpress_forum_post_type)
257 294 if(!empty($singular_ids) && in_array((int)$r->post_id, $singular_ids, TRUE))
258 295 {
@@ -365,12 +402,39 @@
365 402 * @return array Unique array of all Singular IDs *(as integers)* NOT available to current Visitor, due to Specific Post/Page Restrictions.
366 403 */
367 404 public static function get_unavailable_singular_ids_with_sp($exclude_conflicts = FALSE)
368 405 {
369 - if($GLOBALS['WS_PLUGIN__']['s2member']['o']['specific_ids'] && is_array($_singular_ids = preg_split('/['."\r\n\t".'\s;,]+/', $GLOBALS['WS_PLUGIN__']['s2member']['o']['specific_ids'])))
370 - foreach($_singular_ids as $_singular_id) // Now check access to this Singular, against the current Visitor, via read-only ``c_ws_plugin__s2member_sp_access::sp_access()``.
371 - if(is_numeric($_singular_id) && !c_ws_plugin__s2member_sp_access::sp_access($_singular_id, 'read-only'))
406 + if($GLOBALS['WS_PLUGIN__']['s2member']['o']['specific_ids'])
407 + {
408 + //260915.0105 Parse/normalize configured Specific Post/Page IDs only once per distinct option value in this request; Alternative View filtering can call this helper many times on one page load.
409 + static $_specific_ids_cache = array();
410 + $_specific_ids = (string)$GLOBALS['WS_PLUGIN__']['s2member']['o']['specific_ids'];
411 + if(!isset($_specific_ids_cache[$_specific_ids]))
412 + {
413 + $_specific_ids_cache[$_specific_ids] = array();
414 + foreach((array)preg_split('/['."\r\n\t".'\s;,]+/', $_specific_ids) as $_specific_id)
415 + if(is_numeric($_specific_id)) $_specific_ids_cache[$_specific_ids][] = (int)$_specific_id;
416 + $_specific_ids_cache[$_specific_ids] = array_values(array_unique($_specific_ids_cache[$_specific_ids]));
417 + }
418 + $_singular_ids = $_specific_ids_cache[$_specific_ids];
419 +
420 + //260915.0122 Most Alternative View requests have no Specific Access credential. Use WordPress's listener APIs instead of testing `$wp_filter` keys directly, because an empty retained hook object must not disable this fast path.
421 + $_sp_access_customized = has_action('ws_plugin__s2member_before_sp_access')
422 + || has_filter('ws_plugin__s2member_sp_access_excluded')
423 + || has_filter('ws_plugin__s2member_sp_access_excluded_cap')
424 + || has_filter('ws_plugin__s2member_sp_access')
425 + || has_action('ws_plugin__s2member_before_sp_access_session')
426 + || has_filter('ws_plugin__s2member_sp_access_session');
427 + $_sp_access_credential = !empty($_GET['s2member_sp_access']) || !empty($_COOKIE['s2member_sp_access']);
428 +
429 + if(!$_sp_access_customized && !$_sp_access_credential)
430 + {
431 + if(!current_user_can('edit_posts')) $singular_ids = $_singular_ids;
432 + }
433 + else foreach($_singular_ids as $_singular_id) // A link/session or SP customization requires the established per-ID access routine.
434 + if(!c_ws_plugin__s2member_sp_access::sp_access($_singular_id, 'read-only'))
372 435 $singular_ids[] = (int)$_singular_id;
436 + }
373 437
374 438 if(!empty($singular_ids) && is_array($singular_ids) && $exclude_conflicts)
375 439 {
376 440 $all_singular_ids_not_conflicting = c_ws_plugin__s2member_utils_gets::get_all_singular_ids_with_sp('exclude-conflicts');
@@ -426,11 +490,26 @@
426 490 {
427 491 /** @var wpdb $wpdb WordPress DB object instance. */
428 492 global $wpdb; // Global DB object reference.
429 493
430 - if(!empty($terms) && is_array($terms) && is_array($singular_ids = $wpdb->get_col("SELECT `object_id` FROM `".$wpdb->term_relationships."` WHERE `term_taxonomy_id` IN (SELECT `term_taxonomy_id` FROM `".$wpdb->term_taxonomy."` WHERE `term_id` IN('".implode("','", $terms)."'))")))
431 - $singular_ids = c_ws_plugin__s2member_utils_arrays::force_integers($singular_ids);
494 + if(empty($terms) || !is_array($terms)) return array();
432 495
433 - return (!empty($singular_ids) && is_array($singular_ids)) ? array_unique($singular_ids) : array();
496 + //260914.1643 Term restrictions can request the same term-to-Singular lookup repeatedly in one page load. Normalize only the cache key so equivalent term sets share one result regardless of order or duplicates, while leaving the legacy SQL input unchanged.
497 + static $_singular_ids = array(), $_term_changes = array();
498 + $_cache_terms = array_values(array_unique(c_ws_plugin__s2member_utils_arrays::force_integers($terms)));
499 + sort($_cache_terms, SORT_NUMERIC);
500 + $_cache_key = $wpdb->term_relationships.'|'.$wpdb->term_taxonomy.'|'.implode(',', $_cache_terms);
501 +
502 + //260914.1643 Refresh after normal WordPress object-term mutations so a relationship changed earlier in this request is visible to later access checks; table names in the key isolate switched Multisite blogs.
503 + $_changes = did_action('set_object_terms') + did_action('deleted_term_relationships');
504 + if(!isset($_singular_ids[$_cache_key]) || !isset($_term_changes[$_cache_key]) || $_term_changes[$_cache_key] !== $_changes)
505 + {
506 + $singular_ids = $wpdb->get_col("SELECT `object_id` FROM `".$wpdb->term_relationships."` WHERE `term_taxonomy_id` IN (SELECT `term_taxonomy_id` FROM `".$wpdb->term_taxonomy."` WHERE `term_id` IN('".implode("','", $terms)."'))");
507 + if(is_array($singular_ids)) $singular_ids = c_ws_plugin__s2member_utils_arrays::force_integers($singular_ids);
508 +
509 + $_singular_ids[$_cache_key] = (!empty($singular_ids) && is_array($singular_ids)) ? array_unique($singular_ids) : array();
510 + $_term_changes[$_cache_key] = $_changes;
511 + }
512 + return $_singular_ids[$_cache_key];
434 513 }
435 514 }
436 515 }