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 +110 -21 260805260917 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,30 +241,55 @@
219 241 {
220 242 /** @var wpdb $wpdb WordPress DB object instance. */
221 243 global $wpdb; // Global DB object reference.
222 244
223 - if(is_array($results = $wpdb->get_results("SELECT `".$wpdb->postmeta."`.`post_id`, `".$wpdb->postmeta."`.`meta_value`, `".$wpdb->posts."`.`post_type`".
224 - " FROM `".$wpdb->posts."`, `".$wpdb->postmeta."` WHERE `".$wpdb->posts."`.`ID` = `".$wpdb->postmeta."`.`post_id`".
225 - " AND `".$wpdb->postmeta."`.`meta_key` = 's2member_ccaps_req' AND `".$wpdb->postmeta."`.`meta_value` != ''")))
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();
247 + $_cache_key = $wpdb->posts.'|'.$wpdb->postmeta;
248 + $_changes = did_action('added_post_meta') + did_action('updated_post_meta') + did_action('deleted_post_meta');
249 + if(!isset($_results[$_cache_key]) || !isset($_meta_changes[$_cache_key]) || $_meta_changes[$_cache_key] !== $_changes)
226 250 {
251 + $_results[$_cache_key] = $wpdb->get_results("SELECT `".$wpdb->postmeta."`.`post_id`, `".$wpdb->postmeta."`.`meta_value`, `".$wpdb->posts."`.`post_type`".
252 + " FROM `".$wpdb->posts."`, `".$wpdb->postmeta."` WHERE `".$wpdb->posts."`.`ID` = `".$wpdb->postmeta."`.`post_id`".
253 + " AND `".$wpdb->postmeta."`.`meta_key` = 's2member_ccaps_req' AND `".$wpdb->postmeta."`.`meta_value` != ''");
254 + $_result_ccaps[$_cache_key] = array();
255 + $_meta_changes[$_cache_key] = $_changes;
256 + }
257 + $results = $_results[$_cache_key];
258 + $result_ccaps = &$_result_ccaps[$_cache_key];
259 + unset($_cache_key, $_changes); //260901 Request-local SQL cache.
260 +
261 + if(is_array($results))
262 + {
227 263 $bbpress_restrictions_enable = apply_filters('ws_plugin__s2member_bbpress_restrictions_enable', TRUE);
228 264 $bbpress_installed = c_ws_plugin__s2member_utils_conds::bbp_is_installed(); // bbPress is installed?
229 265 $bbpress_forum_post_type = $bbpress_installed ? bbp_get_forum_post_type() : ''; // Acquire the current post type for forums.
230 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();
231 269
232 - 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.
233 271 {
234 272 if(!is_object($user) || empty($user->ID)) // No ``$user`` object? Maybe not logged-in?.
235 273 $singular_ids[] = (int)$r->post_id; // It's NOT available. There is no ``$user``.
236 274
237 - else if(is_array($ccaps = c_ws_plugin__s2member_utils_arrays::maybe_unserialize($r->meta_value))) // Make sure we unserialize.
275 + else
238 276 {
239 - foreach($ccaps as $ccap) // Test for Custom Capability Restrictions now.
240 - 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.
241 283 {
242 - $singular_ids[] = (int)$r->post_id; // It's NOT available.
243 - 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 + }
244 290 }
291 + }
245 292 }
246 293 if($bbpress_restrictions_enable && $bbpress_installed && $r->post_type === $bbpress_forum_post_type)
247 294 if(!empty($singular_ids) && in_array((int)$r->post_id, $singular_ids, TRUE))
248 295 {
@@ -355,12 +402,39 @@
355 402 * @return array Unique array of all Singular IDs *(as integers)* NOT available to current Visitor, due to Specific Post/Page Restrictions.
356 403 */
357 404 public static function get_unavailable_singular_ids_with_sp($exclude_conflicts = FALSE)
358 405 {
359 - 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'])))
360 - 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()``.
361 - 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'))
362 435 $singular_ids[] = (int)$_singular_id;
436 + }
363 437
364 438 if(!empty($singular_ids) && is_array($singular_ids) && $exclude_conflicts)
365 439 {
366 440 $all_singular_ids_not_conflicting = c_ws_plugin__s2member_utils_gets::get_all_singular_ids_with_sp('exclude-conflicts');
@@ -416,11 +490,26 @@
416 490 {
417 491 /** @var wpdb $wpdb WordPress DB object instance. */
418 492 global $wpdb; // Global DB object reference.
419 493
420 - 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)."'))")))
421 - $singular_ids = c_ws_plugin__s2member_utils_arrays::force_integers($singular_ids);
494 + if(empty($terms) || !is_array($terms)) return array();
422 495
423 - 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];
424 513 }
425 514 }
426 515 }