PluginProbe
wpForo Forum / 3.1.4
wpForo Forum v3.1.4
3.1.5 3.1.4 3.1.2 3.1.1 3.1.0 3.0.9 3.0.8 3.0.7 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 1.4.11 1.4.12 1.4.13 1.4.2 All 137 releases
wpforo / widgets / RecentTopics.php

RecentTopics.php in wpForo Forum 3.1.4, at widgets/RecentTopics.php

358 lines 19.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace wpforo\widgets;
4
5 use WP_Widget;
6
7 class RecentTopics extends WP_Widget {
8 private $default_instance = [];
9 private $orderby_fields = [];
10 private $order_fields = [];
11
12 function __construct() {
13 parent::__construct( 'wpforo_recent_topics', 'wpForo Recent Topics', [ 'description' => 'Your forum\'s recent topics.' ] );
14 $this->init_local_vars();
15 add_action( 'wp_ajax_wpforo_load_ajax_widget_RecentTopics', [ $this, 'load_ajax_widget' ] );
16 add_action( 'wp_ajax_nopriv_wpforo_load_ajax_widget_RecentTopics', [ $this, 'load_ajax_widget' ] );
17 if( is_admin() ) {
18 add_action( 'wp_ajax_wpforo_get_forum_tree', [ $this, 'get_forum_tree' ] );
19 }
20 }
21
22 private function init_local_vars() {
23 $this->default_instance = [
24 'boardid' => 0,
25 'title' => 'Recent Topics',
26 'forumids' => [],
27 'orderby' => 'created',
28 'order' => 'DESC',
29 'count' => 9,
30 'display_avatar' => true,
31 'forumids_filter' => false,
32 'current_forumid_filter' => false,
33 'goto_unread' => false,
34 'refresh_interval' => 0,
35 ];
36 $this->orderby_fields = [
37 'created' => __( 'Created Date', 'wpforo' ),
38 'modified' => __( 'Modified Date', 'wpforo' ),
39 'posts' => __( 'Posts Count', 'wpforo' ),
40 'views' => __( 'Views Count', 'wpforo' ),
41 ];
42 $this->order_fields = [
43 'DESC' => __( 'DESC', 'wpforo' ),
44 'ASC' => __( 'ASC', 'wpforo' ),
45 'RAND' => __( 'Random', 'wpforo' ),
46 ];
47 }
48
49 private function add_topic_cache_filter( $topic_args ) {
50 if( $topic_args['order'] === 'RAND' ) {
51 add_filter( 'wpforo_cache_topic', '__return_false' );
52 }
53 }
54
55 private function remove_topic_cache_filter( $topic_args ) {
56 if( $topic_args['order'] === 'RAND' ) {
57 remove_filter( 'wpforo_cache_topic', '__return_false' );
58 }
59 }
60
61 public function get_widget( $instance, $topic_args ) {
62 $this->add_topic_cache_filter( $topic_args );
63 $is_user_logged_in = (bool) WPF()->current_userid;
64 $topic_args['private'] = ( ! $is_user_logged_in || ! WPF()->usergroup->can( 'aum' ) ) ? 0 : null;;
65 $topic_args['status'] = ( ! $is_user_logged_in || ! WPF()->usergroup->can( 'aum' ) ) ? 0 : null;
66
67 $row_count = (int) wpfval( $topic_args, 'row_count' );
68 $topics = [];
69 $topic_args['offset'] = 0;
70 while( $row_count && count( $topics ) < $row_count ) {
71 if( ! ( $_topics = WPF()->topic->get_topics( $topic_args ) ) ) break;
72
73 $topics = array_merge( $topics, $_topics );
74 $topic_args['offset'] += $row_count;
75 }
76 array_splice( $topics, $row_count );
77
78 $print_avatar = $instance['display_avatar'] && wpforo_setting( 'profiles', 'avatars' ) && WPF()->usergroup->can( 'va' );
79
80 $lis = '';
81 foreach( $topics as $topic ) {
82 $topic_url = wpforo_topic( $topic['topicid'], 'url' );
83 $member = wpforo_member( $topic );
84
85 $lis .= sprintf(
86 '<li>
87 <div class="wpforo-list-item">
88 %1$s
89 <div class="wpforo-list-item-right" %2$s>
90 <p class="posttitle">%3$s</p>
91 <p class="postuser">
92 %4$s %5$s <span style="white-space: nowrap;">%6$s</span>
93 </p>
94 </div>
95 <div class="wpf-clear"></div>
96 </div>
97 </li>',
98 ( $print_avatar ? sprintf( '<div class="wpforo-list-item-left">%1$s</div>', wpforo_user_avatar( $member ) ) : '' ),
99 ( ! $print_avatar ? 'style="width: 100%"' : '' ),
100 ( wpfval( $instance, 'goto_unread' ) ?
101 wpforo_topic_title( $topic, $topic_url, '{p}{au}{t}{/a}', false ) .
102 ( $topic['topicid'] != wpfval( WPF()->current_object, 'topicid' ) ? wpforo_unread_button( $topic['topicid'], $topic_url, false ) : '' )
103 :
104 wpforo_topic_title( $topic, $topic_url, '{p}{a}{t}{/a}', false )
105 ),
106 wpforo_phrase( 'by', false ),
107 wpforo_member_link( $member, '', 30, '', false ),
108 esc_html( wpforo_date( $topic['created'], 'ago', false ) )
109 );
110 }
111
112 $this->remove_topic_cache_filter( $topic_args );
113
114 return sprintf( '<ul>%1$s</ul>', $lis );
115 }
116
117 public function load_ajax_widget() {
118 $_POST = wp_unslash( $_POST );
119 $instance = json_decode( (string) wpfval( $_POST, 'instance' ), true );
120 $topic_args = json_decode( (string) wpfval( $_POST, 'topic_args' ), true );
121
122 // SECURITY FIX: Sanitize and validate all user-controlled parameters
123 if( is_array( $topic_args ) ) {
124 // Remove dangerous 'where' parameter
125 unset( $topic_args['where'] );
126
127 // SECURITY: Strip parameters that bypass access controls
128 // access_filter=false skips all permission checks (intended for admin backend only)
129 // permgroup allows impersonating another usergroup's permissions
130 // forumid (singular) bypasses access_filter() which only runs when forumid is null
131 unset( $topic_args['access_filter'] );
132 unset( $topic_args['permgroup'] );
133 unset( $topic_args['forumid'] );
134
135 // Cap row_count to prevent resource exhaustion
136 if( isset( $topic_args['row_count'] ) ) {
137 $topic_args['row_count'] = min( 50, max( 1, intval( $topic_args['row_count'] ) ) );
138 }
139
140 // Validate 'orderby' parameter against whitelist
141 if( isset( $topic_args['orderby'] ) ) {
142 if( ! key_exists( $topic_args['orderby'], $this->orderby_fields ) ) {
143 $topic_args['orderby'] = $this->default_instance['orderby'];
144 }
145 }
146
147 // Validate 'order' parameter against whitelist
148 if( isset( $topic_args['order'] ) ) {
149 if( ! key_exists( $topic_args['order'], $this->order_fields ) ) {
150 $topic_args['order'] = $this->default_instance['order'];
151 }
152 }
153
154 // SECURITY: coerce id-list fields to integer arrays so a serialized
155 // payload from an unauthenticated POST can never reach
156 // wpforo_parse_args() / unserialize() downstream. Defense in depth
157 // alongside the allowed_classes=>false hardening in wpforo_parse_args.
158 foreach( [ 'forumids', 'include', 'exclude' ] as $idfield ) {
159 if( isset( $topic_args[ $idfield ] ) ) {
160 $topic_args[ $idfield ] = is_array( $topic_args[ $idfield ] )
161 ? array_map( 'intval', $topic_args[ $idfield ] )
162 : [];
163 }
164 }
165 }
166
167 wp_send_json_success( [ 'html' => $this->get_widget( $instance, $topic_args ) ] );
168 }
169
170 public function widget( $args, $instance ) {
171 wp_enqueue_script( 'wpforo-widgets-js' );
172 $instance = wpforo_parse_args( $instance, $this->default_instance );
173 if( $instance['current_forumid_filter'] && $instance['boardid'] === WPF()->board->get_current( 'boardid' ) && $current_forumid = wpfval(
174 WPF()->current_object,
175 'forumid'
176 ) ) {
177 $instance['forumids'] = (array) $current_forumid;
178 }
179 $data = [
180 'boardid' => $instance['boardid'],
181 'action' => 'wpforo_load_ajax_widget_RecentTopics',
182 'instance' => $instance,
183 'topic_args' => [
184 'forumids' => ( $instance['forumids'] ?: $this->default_instance['forumids'] ),
185 'orderby' => ( key_exists( $instance['orderby'], $this->orderby_fields ) ? $instance['orderby'] : $this->default_instance['orderby'] ),
186 'order' => ( key_exists( $instance['order'], $this->order_fields ) ? $instance['order'] : $this->default_instance['order'] ),
187 'row_count' => ( ( $count = intval( $instance['count'] ) ) ? $count : $this->default_instance['count'] ),
188 ],
189 ];
190 if( WPF()->board->get_current( 'boardid' ) === $instance['boardid'] ) {
191 $html = $this->get_widget( $data['instance'], $data['topic_args'] );
192 $onload = false;
193 } else {
194 $html = '<div style="text-align: center; font-size: 20px;"><i class="fas fa-spinner fa-spin"></i></div>';
195 $onload = true;
196 $data['referer'] = home_url();
197 }
198 $json = wp_json_encode( $data );
199 echo $args['before_widget'] . '<div id="wpf-widget-recent-replies" class="wpforo-widget-wrap">';
200 if( ! empty( $instance['title'] ) ) echo $args['before_title'] . apply_filters( 'widget_title', $instance['title'] ) . $args['after_title'];
201 echo '<div class="wpforo-widget-content wpforo-ajax-widget ' . ( ! $onload ? 'wpforo-ajax-widget-onload-false' : '' ) . '" data-json="' . esc_attr(
202 $json
203 ) . '">' . $html . '</div></div>' . $args['after_widget'];
204 }
205
206 public function form( $instance ) {
207 $instance = wpforo_parse_args( $instance, $this->default_instance );
208 $title = (string) $instance['title'];
209 $boardid = (int) $instance['boardid'];
210 $selected = array_unique( array_filter( array_map( 'intval', (array) $instance['forumids'] ) ) );
211 $orderby = (string) $instance['orderby'];
212 $order = (string) $instance['order'];
213 $count = (int) $instance['count'];
214 $display_avatar = (bool) $instance['display_avatar'];
215 $forumids_filter = (bool) $instance['forumids_filter'];
216 $current_forumid_filter = (bool) $instance['current_forumid_filter'];
217 $goto_unread = (bool) $instance['goto_unread'];
218 $refresh_interval = (int) $instance['refresh_interval'];
219 WPF()->change_board( $boardid );
220 ?>
221 <style>
222 .wpf-wdg-wrapper .wpf_wdg_forumids_wrap {
223 display: none !important;
224 }
225
226 .wpf-wdg-wrapper input.wpf_wdg_forumids_filter_1:checked ~ .wpf_wdg_forumids_wrap {
227 display: block !important;
228 }
229 </style>
230 <div class="wpf-wdg-wrapper">
231 <p>
232 <label for="<?php echo $this->get_field_id( 'title' ) ?>"><?php _e( 'Title', 'wpforo' ); ?>:</label>
233 <input id="<?php echo $this->get_field_id( 'title' ) ?>" class="widefat"
234 name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text"
235 value="<?php echo esc_attr( $title ); ?>">
236 </p>
237 <p>
238 <label for="<?php echo $this->get_field_id( 'boardid' ) ?>"></label>
239 <select id="<?php echo $this->get_field_id( 'boardid' ) ?>" class="wpf_wdg_boardid" name="<?php echo esc_attr( $this->get_field_name( 'boardid' ) ); ?>">
240 <?php echo WPF()->board->dropdown( $boardid ) ?>
241 </select>
242 </p>
243 <div>
244 <span><?php _e( 'Filter by forums', 'wpforo' ); ?> :</span>
245
246 <label for="<?php echo $this->get_field_id( 'forumids_filter' ) ?>_1"><?php _e( 'Yes', 'wpforo' ); ?></label>
247 <input id="<?php echo $this->get_field_id( 'forumids_filter' ) ?>_1" value="1" class="wpf_wdg_forumids_filter_1"
248 name="<?php echo esc_attr( $this->get_field_name( 'forumids_filter' ) ); ?>" <?php checked( $forumids_filter ); ?> type="radio">
249
250 <label for="<?php echo $this->get_field_id( 'forumids_filter' ) ?>_0"><?php _e( 'No', 'wpforo' ); ?></label>
251 <input id="<?php echo $this->get_field_id( 'forumids_filter' ) ?>_0" value="0" class="wpf_wdg_forumids_filter_0"
252 name="<?php echo esc_attr( $this->get_field_name( 'forumids_filter' ) ); ?>" <?php checked( $forumids_filter, false ); ?> type="radio">
253
254 <div class="wpf_wdg_forumids_wrap">
255 <label for="<?php echo $this->get_field_id( 'forumids' ) ?>"></label>
256 <select id="<?php echo $this->get_field_id( 'forumids' ) ?>" class="wpf_wdg_forumids" name="<?php echo esc_attr( $this->get_field_name( 'forumids' ) ); ?>[]" multiple>
257 <?php WPF()->forum->tree( 'select_box', false, $selected ) ?>
258 </select>
259 </div>
260 </div>
261 <p>
262 <span><?php _e( 'Autofilter by current forum', 'wpforo' ); ?> : </span>
263
264 <label for="<?php echo $this->get_field_id( 'current_forumid_filter' ) ?>_1"><?php _e( 'Yes', 'wpforo' ); ?></label>
265 <input id="<?php echo $this->get_field_id( 'current_forumid_filter' ) ?>_1" name="<?php echo esc_attr( $this->get_field_name( 'current_forumid_filter' ) ); ?>" value="1" <?php checked(
266 $current_forumid_filter
267 ); ?> type="radio">
268
269 <label for="<?php echo $this->get_field_id( 'current_forumid_filter' ) ?>_0"><?php _e( 'No', 'wpforo' ); ?></label>
270 <input id="<?php echo $this->get_field_id( 'current_forumid_filter' ) ?>_0" name="<?php echo esc_attr( $this->get_field_name( 'current_forumid_filter' ) ); ?>" value="0" <?php checked(
271 $current_forumid_filter,
272 false
273 ); ?> type="radio">
274 </p>
275 <p>
276 <label for="<?php echo $this->get_field_id( 'orderby' ) ?>"><?php _e( 'Order by', 'wpforo' ); ?>:</label>
277 <select name="<?php echo esc_attr( $this->get_field_name( 'orderby' ) ); ?>"
278 id="<?php echo $this->get_field_id( 'orderby' ) ?>">
279 <?php foreach( $this->orderby_fields as $orderby_key => $orderby_field ) : ?>
280 <option value="<?php echo $orderby_key; ?>"<?php echo( $orderby_key == $orderby ? ' selected' : '' ); ?>><?php echo $orderby_field; ?></option>
281 <?php endforeach; ?>
282 </select>
283 <label>
284 <select name="<?php echo esc_attr( $this->get_field_name( 'order' ) ); ?>">
285 <?php foreach( $this->order_fields as $order_key => $order_field ) : ?>
286 <option value="<?php echo $order_key; ?>"<?php echo( $order_key == $order ? ' selected' : '' ); ?>><?php echo $order_field; ?></option>
287 <?php endforeach; ?>
288 </select>
289 </label>
290 </p>
291 <p>
292 <label for="<?php echo $this->get_field_id( 'count' ) ?>"><?php _e( 'Number of Items', 'wpforo' ); ?></label>&nbsp;
293 <input id="<?php echo $this->get_field_id( 'count' ) ?>" type="number" min="1" style="width: 53px;" name="<?php echo esc_attr( $this->get_field_name( 'count' ) ); ?>"
294 value="<?php echo esc_attr( $count ); ?>">
295 </p>
296 <p>
297 <span><?php _e( 'Display with avatars', 'wpforo' ); ?> : </span>
298
299 <label for="<?php echo $this->get_field_id( 'display_avatar' ) ?>_1"><?php _e( 'Yes', 'wpforo' ); ?></label>
300 <input id="<?php echo $this->get_field_id( 'display_avatar' ) ?>_1" value="1" <?php checked( $display_avatar ); ?> type="radio"
301 name="<?php echo esc_attr( $this->get_field_name( 'display_avatar' ) ); ?>">
302
303 <label for="<?php echo $this->get_field_id( 'display_avatar' ) ?>_0"><?php _e( 'No', 'wpforo' ); ?></label>
304 <input id="<?php echo $this->get_field_id( 'display_avatar' ) ?>_0" value="0" <?php checked( $display_avatar, false ); ?> type="radio"
305 name="<?php echo esc_attr( $this->get_field_name( 'display_avatar' ) ); ?>">
306 </p>
307 <p>
308 <span><?php _e( 'Refer topics to first unread post', 'wpforo' ); ?> : </span>
309
310 <label for="<?php echo $this->get_field_id( 'goto_unread' ) ?>_1"><?php _e( 'Yes', 'wpforo' ); ?></label>
311 <input id="<?php echo $this->get_field_id( 'goto_unread' ) ?>_1" <?php checked( $goto_unread ); ?> type="radio"
312 name="<?php echo esc_attr( $this->get_field_name( 'goto_unread' ) ); ?>">
313
314 <label for="<?php echo $this->get_field_id( 'goto_unread' ) ?>_0"><?php _e( 'No', 'wpforo' ); ?></label>
315 <input id="<?php echo $this->get_field_id( 'goto_unread' ) ?>_0" <?php checked( $goto_unread, false ); ?> type="radio"
316 name="<?php echo esc_attr( $this->get_field_name( 'goto_unread' ) ); ?>">
317 </p>
318 <p>
319 <label for="<?php echo $this->get_field_id( 'refresh_interval' ) ?>"><?php _e( 'Auto Refresh Interval Seconds', 'wpforo' ); ?></label>&nbsp;
320 <input id="<?php echo $this->get_field_id( 'refresh_interval' ) ?>" type="number" min="0" style="display: inline-block; width: 53px;"
321 name="<?php echo esc_attr( $this->get_field_name( 'refresh_interval' ) ); ?>" value="<?php echo esc_attr( $refresh_interval ); ?>">
322 <span style="color: #ccc"><?php _e( 'Set 0 to disable autorefresh', 'wpforo' ) ?></span>
323 </p>
324 </div>
325 <?php
326 }
327
328 public function update( $new_instance, $old_instance ) {
329 $new_instance = wpforo_parse_args( $new_instance, $this->default_instance );
330 $instance = [];
331 $instance['title'] = strip_tags( (string) $new_instance['title'] );
332 $instance['boardid'] = (int) $new_instance['boardid'];
333 $instance['forumids_filter'] = (bool) (int) $new_instance['forumids_filter'];
334 $instance['forumids'] = array_unique( array_filter( array_map( 'intval', (array) $new_instance['forumids'] ) ) );
335 $instance['orderby'] = ( ! empty( $new_instance['orderby'] ) && key_exists(
336 $new_instance['orderby'],
337 $this->orderby_fields
338 ) ) ? $new_instance['orderby'] : $this->default_instance['orderby'];
339 $instance['order'] = ( ! empty( $new_instance['order'] ) && key_exists(
340 $new_instance['order'],
341 $this->order_fields
342 ) ) ? $new_instance['order'] : $this->default_instance['order'];
343 $instance['count'] = (int) $new_instance['count'];
344 $instance['display_avatar'] = (bool) (int) $new_instance['display_avatar'];
345 $instance['current_forumid_filter'] = (bool) (int) $new_instance['current_forumid_filter'];
346 $instance['goto_unread'] = (bool) (int) $new_instance['goto_unread'];
347 $instance['refresh_interval'] = (int) $new_instance['refresh_interval'];
348
349 return $instance;
350 }
351
352 public function get_forum_tree() {
353 ob_start();
354 WPF()->forum->tree( 'select_box', false, [] );
355 wp_send_json_success( [ 'html' => ob_get_clean() ] );
356 }
357 }
358