PluginProbe
Forumax – AI Powered Advanced Community Forum Plugin / 2.5.1
Forumax – AI Powered Advanced Community Forum Plugin v2.5.1
2.5.1 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 All 31 releases
← All changes | includes/features/bbp_voting/helpers.php +272 -156 1.2.5 → 2.5.1 View file →
@@ -1,156 +1,272 @@
1 -<?php
2 -if ( ! defined( 'ABSPATH' ) ) {
3 - exit; // Exit if accessed directly.
4 -}
5 -
6 -if ( ! function_exists( 'bbp_voting_get_plugin_install_link' ) ) {
7 - function bbp_voting_get_plugin_install_link( $plugin, $action = 'install-plugin' ) {
8 - return wp_nonce_url(
9 - add_query_arg(
10 - [
11 - 'action' => $action,
12 - 'plugin' => $plugin,
13 - ],
14 - admin_url( 'update.php' )
15 - ),
16 - $action . '_' . $plugin
17 - );
18 - }
19 -}
20 -
21 -
22 -if ( ! function_exists( 'bbp_voting_get_plugin_activate_link' ) ) {
23 - function bbp_voting_get_plugin_activate_link( $plugin, $action = 'activate' ) {
24 - if ( strpos( $plugin, '/' ) ) {
25 - $plugin = str_replace( '\/', '%2F', $plugin );
26 - }
27 - $url = sprintf( admin_url( 'plugins.php?action=' . $action . '&plugin=%s&plugin_status=all&paged=1&s' ), $plugin );
28 - $_REQUEST['plugin'] = $plugin;
29 - $url = wp_nonce_url( $url, $action . '-plugin_' . $plugin );
30 - return $url;
31 - }
32 -}
33 -
34 -
35 -if ( ! function_exists( 'bbp_voting_field' ) ) {
36 - function bbp_voting_field( $key, $name, $label = '', $description = '', $type = 'bool', $pro = false ) {
37 - $name_and_id = 'name="' . $key . '" id="' . $key . '"';
38 - $teaser = defined( 'BBPVOTINGPRO' ) ? false : $pro;
39 - $attributes = $teaser ? 'disabled' : $name_and_id;
40 - ?>
41 - <tr valign="top">
42 - <th scope="row">
43 - <?php echo $name; ?>:
44 - <?php if ( $pro ) { ?>
45 - <a href="https://wpforthewin.com/product/bbpress-voting-pro/" target="_blank"><span class="bbp-voting-pro-badge bbp-voting-pro-green">Pro</span></a>
46 - <?php } ?>
47 - </th>
48 - <td>
49 - <?php if ( $type == 'bool' ) { ?>
50 - <input type="checkbox" <?php echo $attributes; ?> value="true"
51 - <?php
52 - if ( get_option( $key ) == 'true' ) {
53 - echo 'checked';}
54 - ?>
55 - />
56 - <?php } elseif ( is_array( $type ) ) { ?>
57 - <select <?php echo $attributes; ?>>
58 - <?php foreach ( $type as $option ) { ?>
59 - <option value="<?php echo $option; ?>"
60 - <?php
61 - if ( get_option( $key ) == $option ) {
62 - echo ' selected';}
63 - ?>
64 - ><?php echo ucwords( str_replace( '-', ' ', $option ) ); ?></option>
65 - <?php } ?>
66 - </select>
67 - <?php } else { ?>
68 - <input type="<?php echo $type; ?>" <?php echo $attributes; ?> value="<?php echo get_option( $key ); ?>" data-lpignore="true" />
69 - <?php } ?>
70 - <?php if ( ! empty( $label ) ) { ?>
71 - <label for="<?php echo $key; ?>"><?php echo $label; ?></label>
72 - <?php } ?>
73 - <?php if ( ! empty( $description ) ) { ?>
74 - <p class="description"><?php echo $description; ?></p>
75 - <?php } ?>
76 - </td>
77 - </tr>
78 - <?php
79 - }
80 -}
81 -
82 -if ( ! function_exists( 'bbp_voting_get_current_post_type' ) ) {
83 - function bbp_voting_get_current_post_type() {
84 - $this_post_id = bbp_get_reply_id() ?: bbp_get_topic_id();
85 - return get_post_type( $this_post_id );
86 - }
87 -}
88 -
89 -if ( ! function_exists( 'bbp_voting_get_post_type_by_id' ) ) {
90 - function bbp_voting_get_post_type_by_id( $post_id ) {
91 - $this_post_id = bbp_get_reply_id( $post_id ) ?: bbp_get_topic_id( $post_id );
92 - return get_post_type( $this_post_id );
93 - }
94 -}
95 -
96 -if ( ! class_exists( 'WilsonConfidenceIntervalCalculator' ) ) {
97 - class WilsonConfidenceIntervalCalculator {
98 - /**
99 - * Computed value for confidence (z)
100 - *
101 - * These values were computed using Ruby's Statistics2.pnormaldist function
102 - * 1.645 = 90% confidence
103 - * 1.959964 = 95.0% confidence
104 - * 2.241403 = 97.5% confidence
105 - */
106 - public function getScore( int $positiveVotes, int $totalVotes, float $confidence = 1.645 ) : float {
107 - return (float) $totalVotes ? $this->lowerBound( $positiveVotes, $totalVotes, $confidence ) : 0;
108 - }
109 - private function lowerBound( int $positiveVotes, int $totalVotes, float $confidence ) : float {
110 - $phat = 1.0 * $positiveVotes / $totalVotes;
111 - $numerator = $this->calculationNumerator( $totalVotes, $confidence, $phat );
112 - $denominator = $this->calculationDenominator( $totalVotes, $confidence );
113 - return $numerator / $denominator;
114 - }
115 - private function calculationDenominator( int $total, float $z ) : float {
116 - return 1 + $z * $z / $total;
117 - }
118 - private function calculationNumerator( int $total, float $z, float $phat ) : float {
119 - return $phat + $z * $z / ( 2 * $total ) - $z * sqrt( ( $phat * ( 1 - $phat ) + $z * $z / ( 4 * $total ) ) / $total );
120 - }
121 - }
122 -}
123 -
124 -
125 -if ( ! function_exists( 'bbp_voting_parse_args' ) ) {
126 - function bbp_voting_parse_args( $args, $defaults = [], $filter_key = '' ) {
127 -
128 - // Setup a temporary array from $args
129 - if ( is_object( $args ) ) {
130 - $r = get_object_vars( $args );
131 - } elseif ( is_array( $args ) ) {
132 - $r =& $args;
133 - } else {
134 - $r = [];
135 - wp_parse_str( $args, $r );
136 - }
137 -
138 - // Passively filter the args before the parse
139 - if ( ! empty( $filter_key ) ) {
140 - $r = apply_filters( "bbp_voting_before_{$filter_key}_parse_args", $r, $args, $defaults );
141 - }
142 -
143 - // Parse
144 - if ( is_array( $defaults ) && ! empty( $defaults ) ) {
145 - $r = array_merge( $defaults, $r );
146 - }
147 -
148 - // Aggressively filter the args after the parse
149 - if ( ! empty( $filter_key ) ) {
150 - $r = apply_filters( "bbp_voting_after_{$filter_key}_parse_args", $r, $args, $defaults );
151 - }
152 -
153 - // Return the parsed results
154 - return $r;
155 - }
156 -}
1 +<?php
2 +if ( ! defined( 'ABSPATH' ) ) {
3 + exit; // Exit if accessed directly.
4 +}
5 +
6 +if ( ! function_exists( 'bbp_voting_get_plugin_install_link' ) ) {
7 + function bbp_voting_get_plugin_install_link( $plugin, $action = 'install-plugin' ) {
8 + return esc_url(
9 + wp_nonce_url(
10 + add_query_arg(
11 + [
12 + 'action' => $action,
13 + 'plugin' => $plugin,
14 + ],
15 + admin_url( 'update.php' )
16 + ),
17 + $action . '_' . $plugin
18 + )
19 + );
20 + }
21 +}
22 +
23 +
24 +if ( ! function_exists( 'bbp_voting_get_plugin_activate_link' ) ) {
25 + function bbp_voting_get_plugin_activate_link( $plugin, $action = 'activate' ) {
26 + if ( strpos( $plugin, '/' ) ) {
27 + $plugin = str_replace( '\/', '%2F', $plugin );
28 + }
29 + $url = sprintf( admin_url( 'plugins.php?action=' . $action . '&plugin=%s&plugin_status=all&paged=1&s' ), $plugin );
30 + $_REQUEST['plugin'] = $plugin;
31 + $url = wp_nonce_url( $url, $action . '-plugin_' . $plugin );
32 + return $url;
33 + }
34 +}
35 +
36 +
37 +if ( ! function_exists( 'bbp_voting_field' ) ) {
38 + function bbp_voting_field( $key, $name, $label = '', $description = '', $type = 'bool', $pro = false ) {
39 + $name_and_id = 'name="' . esc_attr( $key ) . '" id="' . esc_attr( $key ) . '"';
40 + $teaser = defined( 'BBPVOTINGPRO' ) ? false : $pro;
41 + $attributes = $teaser ? 'disabled' : $name_and_id;
42 + ?>
43 + <tr valign="top">
44 + <th scope="row">
45 + <?php echo esc_html( $name ); ?>:
46 + <?php if ( $pro ) { ?>
47 + <a href="<?php echo esc_url( 'https://wpforthewin.com/product/bbpress-voting-pro/' ); ?>" target="_blank" rel="noopener noreferrer">
48 + <span class="bbp-voting-pro-badge bbp-voting-pro-green">Pro</span>
49 + </a>
50 + <?php } ?>
51 + </th>
52 + <td>
53 + <?php if ( $type === 'bool' ) { ?>
54 + <input type="checkbox" <?php echo wp_kses_post( $attributes ); ?> value="true"
55 + <?php checked( get_option( $key ), 'true' ); ?> />
56 + <?php } elseif ( is_array( $type ) ) { ?>
57 + <select <?php echo wp_kses_post( $attributes ); ?>>
58 + <?php foreach ( $type as $option ) { ?>
59 + <option value="<?php echo esc_attr( $option ); ?>" <?php selected( get_option( $key ), $option ); ?>>
60 + <?php echo esc_html( ucwords( str_replace( '-', ' ', $option ) ) ); ?>
61 + </option>
62 + <?php } ?>
63 + </select>
64 + <?php } else { ?>
65 + <input type="<?php echo esc_attr( $type ); ?>" <?php echo wp_kses_post( $attributes ); ?>
66 + value="<?php echo esc_attr( get_option( $key ) ); ?>" data-lpignore="true" />
67 + <?php } ?>
68 +
69 + <?php if ( ! empty( $label ) ) { ?>
70 + <label for="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $label ); ?></label>
71 + <?php } ?>
72 +
73 + <?php if ( ! empty( $description ) ) { ?>
74 + <p class="description"><?php echo wp_kses_post( $description ); ?></p>
75 + <?php } ?>
76 + </td>
77 + </tr>
78 + <?php
79 + }
80 +}
81 +
82 +if ( ! function_exists( 'bbp_voting_get_current_post_type' ) ) {
83 + function bbp_voting_get_current_post_type() {
84 + $this_post_id = bbp_get_reply_id() ?: bbp_get_topic_id();
85 + return get_post_type( $this_post_id );
86 + }
87 +}
88 +
89 +if ( ! function_exists( 'bbp_voting_get_post_type_by_id' ) ) {
90 + function bbp_voting_get_post_type_by_id( $post_id ) {
91 + $this_post_id = bbp_get_reply_id( $post_id ) ?: bbp_get_topic_id( $post_id );
92 + return get_post_type( $this_post_id );
93 + }
94 +}
95 +
96 +if ( ! class_exists( 'WilsonConfidenceIntervalCalculator' ) ) {
97 + class WilsonConfidenceIntervalCalculator {
98 + /**
99 + * Computed value for confidence (z)
100 + *
101 + * These values were computed using Ruby's Statistics2.pnormaldist function
102 + * 1.645 = 90% confidence
103 + * 1.959964 = 95.0% confidence
104 + * 2.241403 = 97.5% confidence
105 + */
106 + public function getScore( int $positiveVotes, int $totalVotes, float $confidence = 1.645 ) : float {
107 + return (float) $totalVotes ? $this->lowerBound( $positiveVotes, $totalVotes, $confidence ) : 0;
108 + }
109 + private function lowerBound( int $positiveVotes, int $totalVotes, float $confidence ) : float {
110 + $phat = 1.0 * $positiveVotes / $totalVotes;
111 + $numerator = $this->calculationNumerator( $totalVotes, $confidence, $phat );
112 + $denominator = $this->calculationDenominator( $totalVotes, $confidence );
113 + return $numerator / $denominator;
114 + }
115 + private function calculationDenominator( int $total, float $z ) : float {
116 + return 1 + $z * $z / $total;
117 + }
118 + private function calculationNumerator( int $total, float $z, float $phat ) : float {
119 + return $phat + $z * $z / ( 2 * $total ) - $z * sqrt( ( $phat * ( 1 - $phat ) + $z * $z / ( 4 * $total ) ) / $total );
120 + }
121 + }
122 +}
123 +
124 +
125 +if ( ! function_exists( 'bbp_voting_parse_args' ) ) {
126 + function bbp_voting_parse_args( $args, $defaults = [], $filter_key = '' ) {
127 +
128 + // Setup a temporary array from $args
129 + if ( is_object( $args ) ) {
130 + $r = get_object_vars( $args );
131 + } elseif ( is_array( $args ) ) {
132 + $r =& $args;
133 + } else {
134 + $r = [];
135 + wp_parse_str( $args, $r );
136 + }
137 +
138 + // Passively filter the args before the parse
139 + if ( ! empty( $filter_key ) ) {
140 + $r = apply_filters( "bbp_voting_before_{$filter_key}_parse_args", $r, $args, $defaults );
141 + }
142 +
143 + // Parse
144 + if ( is_array( $defaults ) && ! empty( $defaults ) ) {
145 + $r = array_merge( $defaults, $r );
146 + }
147 +
148 + // Aggressively filter the args after the parse
149 + if ( ! empty( $filter_key ) ) {
150 + $r = apply_filters( "bbp_voting_after_{$filter_key}_parse_args", $r, $args, $defaults );
151 + }
152 +
153 + // Return the parsed results
154 + return $r;
155 + }
156 +}
157 +
158 +
159 +if ( ! function_exists( 'forumax_init_voting_scores' ) ) {
160 + /**
161 + * Initialize voting scores for new topics and replies.
162 + *
163 + * @param int $post_id Post ID.
164 + */
165 + function forumax_init_voting_scores( $post_id ) {
166 + update_post_meta( $post_id, 'bbp_voting_score', '0' );
167 + update_post_meta( $post_id, 'bbp_voting_ups', '0' );
168 + update_post_meta( $post_id, 'bbp_voting_downs', '0' );
169 + update_post_meta( $post_id, 'bbp_voting_log', [] );
170 + }
171 + add_action( 'bbp_new_topic', 'forumax_init_voting_scores' );
172 + add_action( 'bbp_new_reply', 'forumax_init_voting_scores' );
173 +}
174 +
175 +if ( ! function_exists( 'forumax_ensure_voting_score_meta' ) ) {
176 + /**
177 + * Give every saved topic/reply a vote score, including ones created in
178 + * wp-admin or by importers, so "sort by votes" never drops them.
179 + *
180 + * @param int $post_id Post ID.
181 + */
182 + function forumax_ensure_voting_score_meta( $post_id ) {
183 + if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
184 + return;
185 + }
186 +
187 + add_post_meta( $post_id, 'bbp_voting_score', '0', true );
188 + }
189 + add_action( 'save_post_topic', 'forumax_ensure_voting_score_meta' );
190 + add_action( 'save_post_reply', 'forumax_ensure_voting_score_meta' );
191 +}
192 +
193 +if ( ! function_exists( 'forumax_backfill_voting_score_meta_once' ) ) {
194 + /**
195 + * One-time bulk backfill of bbp_voting_score for existing topics/replies.
196 + *
197 + * Replaces the old per-page-view backfill, which ran an extra query and
198 + * stored a new transient for every distinct topic list.
199 + */
200 + function forumax_backfill_voting_score_meta_once() {
201 + if ( get_option( 'forumax_vote_score_backfilled' ) ) {
202 + return;
203 + }
204 +
205 + global $wpdb;
206 +
207 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
208 + $wpdb->query(
209 + "INSERT INTO {$wpdb->postmeta} ( post_id, meta_key, meta_value )
210 + SELECT p.ID, 'bbp_voting_score', '0'
211 + FROM {$wpdb->posts} p
212 + LEFT JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = 'bbp_voting_score'
213 + WHERE p.post_type IN ( 'topic', 'reply' ) AND pm.meta_id IS NULL"
214 + );
215 +
216 + update_option( 'forumax_vote_score_backfilled', 1 );
217 +
218 + // Drop the transients the old per-page backfill left behind.
219 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
220 + $wpdb->query(
221 + $wpdb->prepare(
222 + "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
223 + $wpdb->esc_like( '_transient_frmx_vote_backfill_' ) . '%',
224 + $wpdb->esc_like( '_transient_timeout_frmx_vote_backfill_' ) . '%'
225 + )
226 + );
227 + }
228 + add_action( 'admin_init', 'forumax_backfill_voting_score_meta_once' );
229 +}
230 +
231 +if ( ! function_exists( 'forumax_backfill_voting_sort_meta' ) ) {
232 + /**
233 + * Backfill a custom (filtered) sort meta key for the posts of one query.
234 + *
235 + * Only used when bbp_voting_sort_meta_key points at a non-default key.
236 + * Results are remembered in the object cache for the request/day.
237 + *
238 + * @param string $sort_meta_key Meta key used for sorting.
239 + * @param array $args bbPress query args.
240 + */
241 + function forumax_backfill_voting_sort_meta( $sort_meta_key, $args ) {
242 + $cache_key = 'vote_backfill_' . md5( $sort_meta_key . wp_json_encode( $args ) );
243 + if ( false !== wp_cache_get( $cache_key, 'forumax' ) ) {
244 + return;
245 + }
246 +
247 + $query = new WP_Query(
248 + array_merge(
249 + $args,
250 + [
251 + 'meta_query' => [
252 + [
253 + 'key' => $sort_meta_key,
254 + 'compare' => 'NOT EXISTS',
255 + ],
256 + ],
257 + 'fields' => 'ids',
258 + 'no_found_rows' => true,
259 + 'update_post_meta_cache' => false,
260 + 'update_post_term_cache' => false,
261 + ]
262 + )
263 + );
264 +
265 + foreach ( $query->posts as $post_id ) {
266 + $default_value = apply_filters( 'bbp_voting_sort_meta_key_default_value', '0', $post_id );
267 + update_post_meta( $post_id, $sort_meta_key, $default_value );
268 + }
269 +
270 + wp_cache_set( $cache_key, 1, 'forumax', DAY_IN_SECONDS );
271 + }
272 +}