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
bbp-core / includes / features / bbp_voting / helpers.php

helpers.php in Forumax – AI Powered Advanced Community Forum Plugin 2.5.1, at includes/features/bbp_voting/helpers.php

273 lines 9.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 }
273