PluginProbe
Forumax – AI Powered Advanced Community Forum Plugin / 2.5.0
Forumax – AI Powered Advanced Community Forum Plugin v2.5.0
2.5.0 2.4.4 2.4.3 2.4.2 2.4.1 2.4.0 trunk 1.0.8 1.1.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.4.1 2.0.0 All 30 releases
bbp-core / includes / topic-render-guard.php

topic-render-guard.php in Forumax – AI Powered Advanced Community Forum Plugin 2.5.0, at includes/topic-render-guard.php

220 lines 6.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Single-topic render guards.
4 *
5 * Theme-compat runs display_topic() during template_include. If
6 * bbp_is_single_topic() is false at that moment, bbp_has_replies() defaults
7 * to post_parent=any and can load every topic and reply. Capability mapping
8 * then fans out until PHP hits the memory limit.
9 *
10 * @package Forumax
11 */
12
13 defined( 'ABSPATH' ) || exit;
14
15 /**
16 * Resolve the topic being rendered, without calling bbp_is_single_topic().
17 *
18 * @return int Topic ID or 0.
19 */
20 function forumax_resolve_current_topic_id() {
21 if ( function_exists( 'bbpress' ) ) {
22 $current = (int) bbpress()->current_topic_id;
23 if ( $current > 0 ) {
24 return $current;
25 }
26 }
27
28 $object = get_queried_object();
29 if ( $object instanceof WP_Post && function_exists( 'bbp_get_topic_post_type' ) && bbp_get_topic_post_type() === $object->post_type ) {
30 return (int) $object->ID;
31 }
32
33 return 0;
34 }
35
36 /**
37 * Seed current_topic_id as soon as the main query is a topic.
38 *
39 * @return void
40 */
41 function forumax_prime_current_topic_id() {
42 if ( ! function_exists( 'bbpress' ) || ! function_exists( 'bbp_get_topic_post_type' ) ) {
43 return;
44 }
45
46 if ( ! empty( bbpress()->current_topic_id ) ) {
47 return;
48 }
49
50 if ( is_singular( bbp_get_topic_post_type() ) ) {
51 $topic_id = (int) get_queried_object_id();
52 if ( $topic_id > 0 ) {
53 bbpress()->current_topic_id = $topic_id;
54 }
55 }
56 }
57 add_action( 'wp', 'forumax_prime_current_topic_id', 1 );
58
59 /**
60 * Treat a primed topic ID as a single-topic view.
61 *
62 * @param bool $is_single_topic Current detection result.
63 * @return bool
64 */
65 function forumax_filter_is_single_topic( $is_single_topic ) {
66 if ( $is_single_topic ) {
67 return true;
68 }
69
70 if ( function_exists( 'bbp_is_topic_edit' ) && bbp_is_topic_edit() ) {
71 return false;
72 }
73
74 return forumax_resolve_current_topic_id() > 0;
75 }
76 add_filter( 'bbp_is_single_topic', 'forumax_filter_is_single_topic' );
77
78 /**
79 * Fall back to the primed / queried topic when bbPress cannot resolve one.
80 *
81 * @param int $topic_id Resolved topic ID.
82 * @param int $requested ID passed into bbp_get_topic_id().
83 * @return int
84 */
85 function forumax_filter_get_topic_id( $topic_id, $requested ) {
86 if ( ! empty( $requested ) && is_numeric( $requested ) ) {
87 return (int) $topic_id;
88 }
89
90 if ( (int) $topic_id > 0 ) {
91 return (int) $topic_id;
92 }
93
94 return forumax_resolve_current_topic_id();
95 }
96 add_filter( 'bbp_get_topic_id', 'forumax_filter_get_topic_id', 10, 2 );
97
98 /**
99 * Never run an unscoped replies query while a topic is being rendered.
100 *
101 * User-profile and author queries keep post_parent=any. Hierarchical
102 * site-wide queries are also refused so posts_per_page cannot become -1.
103 *
104 * @param array $args Reply query args after parse.
105 * @return array
106 */
107 function forumax_scope_has_replies_query( $args ) {
108 if ( ! is_array( $args ) ) {
109 return $args;
110 }
111
112 $explicit_parent = isset( $args['post_parent'] ) && is_numeric( $args['post_parent'] ) && (int) $args['post_parent'] > 0;
113 $is_author_query = ! empty( $args['author'] ) || ! empty( $args['author__in'] );
114 $is_id_query = ! empty( $args['post__in'] ) || ! empty( $args['post__not_in'] );
115
116 if ( ! $explicit_parent && ! $is_author_query && ! $is_id_query ) {
117 $topic_id = forumax_resolve_current_topic_id();
118 if ( $topic_id > 0 ) {
119 $args['post_parent'] = $topic_id;
120 }
121 }
122
123 $parent = isset( $args['post_parent'] ) ? $args['post_parent'] : '';
124 if ( ! empty( $args['hierarchical'] ) && ( ! is_numeric( $parent ) || (int) $parent < 1 ) ) {
125 $args['hierarchical'] = false;
126 if ( isset( $args['posts_per_page'] ) && (int) $args['posts_per_page'] < 1 ) {
127 $args['posts_per_page'] = function_exists( 'bbp_get_replies_per_page' ) ? (int) bbp_get_replies_per_page() : 20;
128 }
129 }
130
131 return $args;
132 }
133 add_filter( 'bbp_has_replies_query', 'forumax_scope_has_replies_query', 1 );
134
135 /**
136 * Build a cache key fragment for a map_meta_cap argument.
137 *
138 * Gutenberg passes objects such as WP_Block_Editor_Context. Those cannot be
139 * cast with strval() and must not fatal the post editor.
140 *
141 * @param mixed $arg Raw map_meta_cap argument.
142 * @return string
143 */
144 function forumax_map_meta_cap_arg_fragment( $arg ) {
145 if ( is_null( $arg ) || is_bool( $arg ) || is_int( $arg ) || is_float( $arg ) || is_string( $arg ) ) {
146 return (string) $arg;
147 }
148
149 if ( is_object( $arg ) ) {
150 return get_class( $arg ) . '#' . spl_object_id( $arg );
151 }
152
153 if ( is_array( $arg ) ) {
154 $parts = array();
155 foreach ( $arg as $key => $value ) {
156 $parts[] = forumax_map_meta_cap_arg_fragment( $key ) . '=' . forumax_map_meta_cap_arg_fragment( $value );
157 }
158
159 return '[' . implode( ',', $parts ) . ']';
160 }
161
162 return gettype( $arg );
163 }
164
165 /**
166 * Cache bbPress meta-cap maps for the current request and break re-entry.
167 *
168 * The bundled mappers call user_can() from inside map_meta_cap. On a topic
169 * page that repeats the same checks per reply/admin link until memory dies.
170 *
171 * @param string[] $caps Primitive caps.
172 * @param string $cap Meta capability.
173 * @param int $user_id User ID.
174 * @param array $args Extra arguments (usually a post ID).
175 * @return string[]
176 */
177 function forumax_cache_bbp_map_meta_caps( $caps, $cap, $user_id, $args ) {
178 static $cache = array();
179 static $stack = array();
180
181 $arg_key = '';
182 if ( ! empty( $args ) && is_array( $args ) ) {
183 $arg_key = implode( ',', array_map( 'forumax_map_meta_cap_arg_fragment', $args ) );
184 }
185
186 $key = (int) $user_id . '|' . (string) $cap . '|' . $arg_key;
187
188 if ( isset( $cache[ $key ] ) ) {
189 return $cache[ $key ];
190 }
191
192 // Same cap is already being mapped further up the stack.
193 if ( isset( $stack[ $key ] ) ) {
194 return $caps;
195 }
196
197 $stack[ $key ] = true;
198 $mapped = bbp_map_meta_caps( $caps, $cap, $user_id, $args );
199 unset( $stack[ $key ] );
200
201 $cache[ $key ] = $mapped;
202
203 return $mapped;
204 }
205
206 /**
207 * Replace the stock bbPress map_meta_cap aggregator with a cached wrapper.
208 *
209 * @return void
210 */
211 function forumax_install_map_meta_cap_cache() {
212 if ( ! function_exists( 'bbp_map_meta_caps' ) ) {
213 return;
214 }
215
216 remove_filter( 'map_meta_cap', 'bbp_map_meta_caps', 10 );
217 add_filter( 'map_meta_cap', 'forumax_cache_bbp_map_meta_caps', 10, 4 );
218 }
219 forumax_install_map_meta_cap_cache();
220