PluginProbe
wpForo Forum / 3.1.2
wpForo Forum v3.1.2
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.2, at widgets/RecentTopics.php

345 lines 18.9 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 // Validate 'orderby' parameter against whitelist
128 if( isset( $topic_args['orderby'] ) ) {
129 if( ! key_exists( $topic_args['orderby'], $this->orderby_fields ) ) {
130 $topic_args['orderby'] = $this->default_instance['orderby'];
131 }
132 }
133
134 // Validate 'order' parameter against whitelist
135 if( isset( $topic_args['order'] ) ) {
136 if( ! key_exists( $topic_args['order'], $this->order_fields ) ) {
137 $topic_args['order'] = $this->default_instance['order'];
138 }
139 }
140
141 // SECURITY: coerce id-list fields to integer arrays so a serialized
142 // payload from an unauthenticated POST can never reach
143 // wpforo_parse_args() / unserialize() downstream. Defense in depth
144 // alongside the allowed_classes=>false hardening in wpforo_parse_args.
145 foreach( [ 'forumids', 'include', 'exclude' ] as $idfield ) {
146 if( isset( $topic_args[ $idfield ] ) ) {
147 $topic_args[ $idfield ] = is_array( $topic_args[ $idfield ] )
148 ? array_map( 'intval', $topic_args[ $idfield ] )
149 : [];
150 }
151 }
152 }
153
154 wp_send_json_success( [ 'html' => $this->get_widget( $instance, $topic_args ) ] );
155 }
156
157 public function widget( $args, $instance ) {
158 wp_enqueue_script( 'wpforo-widgets-js' );
159 $instance = wpforo_parse_args( $instance, $this->default_instance );
160 if( $instance['current_forumid_filter'] && $instance['boardid'] === WPF()->board->get_current( 'boardid' ) && $current_forumid = wpfval(
161 WPF()->current_object,
162 'forumid'
163 ) ) {
164 $instance['forumids'] = (array) $current_forumid;
165 }
166 $data = [
167 'boardid' => $instance['boardid'],
168 'action' => 'wpforo_load_ajax_widget_RecentTopics',
169 'instance' => $instance,
170 'topic_args' => [
171 'forumids' => ( $instance['forumids'] ?: $this->default_instance['forumids'] ),
172 'orderby' => ( key_exists( $instance['orderby'], $this->orderby_fields ) ? $instance['orderby'] : $this->default_instance['orderby'] ),
173 'order' => ( key_exists( $instance['order'], $this->order_fields ) ? $instance['order'] : $this->default_instance['order'] ),
174 'row_count' => ( ( $count = intval( $instance['count'] ) ) ? $count : $this->default_instance['count'] ),
175 ],
176 ];
177 if( WPF()->board->get_current( 'boardid' ) === $instance['boardid'] ) {
178 $html = $this->get_widget( $data['instance'], $data['topic_args'] );
179 $onload = false;
180 } else {
181 $html = '<div style="text-align: center; font-size: 20px;"><i class="fas fa-spinner fa-spin"></i></div>';
182 $onload = true;
183 $data['referer'] = home_url();
184 }
185 $json = wp_json_encode( $data );
186 echo $args['before_widget'] . '<div id="wpf-widget-recent-replies" class="wpforo-widget-wrap">';
187 if( ! empty( $instance['title'] ) ) echo $args['before_title'] . apply_filters( 'widget_title', $instance['title'] ) . $args['after_title'];
188 echo '<div class="wpforo-widget-content wpforo-ajax-widget ' . ( ! $onload ? 'wpforo-ajax-widget-onload-false' : '' ) . '" data-json="' . esc_attr(
189 $json
190 ) . '">' . $html . '</div></div>' . $args['after_widget'];
191 }
192
193 public function form( $instance ) {
194 $instance = wpforo_parse_args( $instance, $this->default_instance );
195 $title = (string) $instance['title'];
196 $boardid = (int) $instance['boardid'];
197 $selected = array_unique( array_filter( array_map( 'intval', (array) $instance['forumids'] ) ) );
198 $orderby = (string) $instance['orderby'];
199 $order = (string) $instance['order'];
200 $count = (int) $instance['count'];
201 $display_avatar = (bool) $instance['display_avatar'];
202 $forumids_filter = (bool) $instance['forumids_filter'];
203 $current_forumid_filter = (bool) $instance['current_forumid_filter'];
204 $goto_unread = (bool) $instance['goto_unread'];
205 $refresh_interval = (int) $instance['refresh_interval'];
206 WPF()->change_board( $boardid );
207 ?>
208 <style>
209 .wpf-wdg-wrapper .wpf_wdg_forumids_wrap {
210 display: none !important;
211 }
212
213 .wpf-wdg-wrapper input.wpf_wdg_forumids_filter_1:checked ~ .wpf_wdg_forumids_wrap {
214 display: block !important;
215 }
216 </style>
217 <div class="wpf-wdg-wrapper">
218 <p>
219 <label for="<?php echo $this->get_field_id( 'title' ) ?>"><?php _e( 'Title', 'wpforo' ); ?>:</label>
220 <input id="<?php echo $this->get_field_id( 'title' ) ?>" class="widefat"
221 name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text"
222 value="<?php echo esc_attr( $title ); ?>">
223 </p>
224 <p>
225 <label for="<?php echo $this->get_field_id( 'boardid' ) ?>"></label>
226 <select id="<?php echo $this->get_field_id( 'boardid' ) ?>" class="wpf_wdg_boardid" name="<?php echo esc_attr( $this->get_field_name( 'boardid' ) ); ?>">
227 <?php echo WPF()->board->dropdown( $boardid ) ?>
228 </select>
229 </p>
230 <div>
231 <span><?php _e( 'Filter by forums', 'wpforo' ); ?> :</span>
232
233 <label for="<?php echo $this->get_field_id( 'forumids_filter' ) ?>_1"><?php _e( 'Yes', 'wpforo' ); ?></label>
234 <input id="<?php echo $this->get_field_id( 'forumids_filter' ) ?>_1" value="1" class="wpf_wdg_forumids_filter_1"
235 name="<?php echo esc_attr( $this->get_field_name( 'forumids_filter' ) ); ?>" <?php checked( $forumids_filter ); ?> type="radio">
236
237 <label for="<?php echo $this->get_field_id( 'forumids_filter' ) ?>_0"><?php _e( 'No', 'wpforo' ); ?></label>
238 <input id="<?php echo $this->get_field_id( 'forumids_filter' ) ?>_0" value="0" class="wpf_wdg_forumids_filter_0"
239 name="<?php echo esc_attr( $this->get_field_name( 'forumids_filter' ) ); ?>" <?php checked( $forumids_filter, false ); ?> type="radio">
240
241 <div class="wpf_wdg_forumids_wrap">
242 <label for="<?php echo $this->get_field_id( 'forumids' ) ?>"></label>
243 <select id="<?php echo $this->get_field_id( 'forumids' ) ?>" class="wpf_wdg_forumids" name="<?php echo esc_attr( $this->get_field_name( 'forumids' ) ); ?>[]" multiple>
244 <?php WPF()->forum->tree( 'select_box', false, $selected ) ?>
245 </select>
246 </div>
247 </div>
248 <p>
249 <span><?php _e( 'Autofilter by current forum', 'wpforo' ); ?> : </span>
250
251 <label for="<?php echo $this->get_field_id( 'current_forumid_filter' ) ?>_1"><?php _e( 'Yes', 'wpforo' ); ?></label>
252 <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(
253 $current_forumid_filter
254 ); ?> type="radio">
255
256 <label for="<?php echo $this->get_field_id( 'current_forumid_filter' ) ?>_0"><?php _e( 'No', 'wpforo' ); ?></label>
257 <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(
258 $current_forumid_filter,
259 false
260 ); ?> type="radio">
261 </p>
262 <p>
263 <label for="<?php echo $this->get_field_id( 'orderby' ) ?>"><?php _e( 'Order by', 'wpforo' ); ?>:</label>
264 <select name="<?php echo esc_attr( $this->get_field_name( 'orderby' ) ); ?>"
265 id="<?php echo $this->get_field_id( 'orderby' ) ?>">
266 <?php foreach( $this->orderby_fields as $orderby_key => $orderby_field ) : ?>
267 <option value="<?php echo $orderby_key; ?>"<?php echo( $orderby_key == $orderby ? ' selected' : '' ); ?>><?php echo $orderby_field; ?></option>
268 <?php endforeach; ?>
269 </select>
270 <label>
271 <select name="<?php echo esc_attr( $this->get_field_name( 'order' ) ); ?>">
272 <?php foreach( $this->order_fields as $order_key => $order_field ) : ?>
273 <option value="<?php echo $order_key; ?>"<?php echo( $order_key == $order ? ' selected' : '' ); ?>><?php echo $order_field; ?></option>
274 <?php endforeach; ?>
275 </select>
276 </label>
277 </p>
278 <p>
279 <label for="<?php echo $this->get_field_id( 'count' ) ?>"><?php _e( 'Number of Items', 'wpforo' ); ?></label>&nbsp;
280 <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' ) ); ?>"
281 value="<?php echo esc_attr( $count ); ?>">
282 </p>
283 <p>
284 <span><?php _e( 'Display with avatars', 'wpforo' ); ?> : </span>
285
286 <label for="<?php echo $this->get_field_id( 'display_avatar' ) ?>_1"><?php _e( 'Yes', 'wpforo' ); ?></label>
287 <input id="<?php echo $this->get_field_id( 'display_avatar' ) ?>_1" value="1" <?php checked( $display_avatar ); ?> type="radio"
288 name="<?php echo esc_attr( $this->get_field_name( 'display_avatar' ) ); ?>">
289
290 <label for="<?php echo $this->get_field_id( 'display_avatar' ) ?>_0"><?php _e( 'No', 'wpforo' ); ?></label>
291 <input id="<?php echo $this->get_field_id( 'display_avatar' ) ?>_0" value="0" <?php checked( $display_avatar, false ); ?> type="radio"
292 name="<?php echo esc_attr( $this->get_field_name( 'display_avatar' ) ); ?>">
293 </p>
294 <p>
295 <span><?php _e( 'Refer topics to first unread post', 'wpforo' ); ?> : </span>
296
297 <label for="<?php echo $this->get_field_id( 'goto_unread' ) ?>_1"><?php _e( 'Yes', 'wpforo' ); ?></label>
298 <input id="<?php echo $this->get_field_id( 'goto_unread' ) ?>_1" <?php checked( $goto_unread ); ?> type="radio"
299 name="<?php echo esc_attr( $this->get_field_name( 'goto_unread' ) ); ?>">
300
301 <label for="<?php echo $this->get_field_id( 'goto_unread' ) ?>_0"><?php _e( 'No', 'wpforo' ); ?></label>
302 <input id="<?php echo $this->get_field_id( 'goto_unread' ) ?>_0" <?php checked( $goto_unread, false ); ?> type="radio"
303 name="<?php echo esc_attr( $this->get_field_name( 'goto_unread' ) ); ?>">
304 </p>
305 <p>
306 <label for="<?php echo $this->get_field_id( 'refresh_interval' ) ?>"><?php _e( 'Auto Refresh Interval Seconds', 'wpforo' ); ?></label>&nbsp;
307 <input id="<?php echo $this->get_field_id( 'refresh_interval' ) ?>" type="number" min="0" style="display: inline-block; width: 53px;"
308 name="<?php echo esc_attr( $this->get_field_name( 'refresh_interval' ) ); ?>" value="<?php echo esc_attr( $refresh_interval ); ?>">
309 <span style="color: #ccc"><?php _e( 'Set 0 to disable autorefresh', 'wpforo' ) ?></span>
310 </p>
311 </div>
312 <?php
313 }
314
315 public function update( $new_instance, $old_instance ) {
316 $new_instance = wpforo_parse_args( $new_instance, $this->default_instance );
317 $instance = [];
318 $instance['title'] = strip_tags( (string) $new_instance['title'] );
319 $instance['boardid'] = (int) $new_instance['boardid'];
320 $instance['forumids_filter'] = (bool) (int) $new_instance['forumids_filter'];
321 $instance['forumids'] = array_unique( array_filter( array_map( 'intval', (array) $new_instance['forumids'] ) ) );
322 $instance['orderby'] = ( ! empty( $new_instance['orderby'] ) && key_exists(
323 $new_instance['orderby'],
324 $this->orderby_fields
325 ) ) ? $new_instance['orderby'] : $this->default_instance['orderby'];
326 $instance['order'] = ( ! empty( $new_instance['order'] ) && key_exists(
327 $new_instance['order'],
328 $this->order_fields
329 ) ) ? $new_instance['order'] : $this->default_instance['order'];
330 $instance['count'] = (int) $new_instance['count'];
331 $instance['display_avatar'] = (bool) (int) $new_instance['display_avatar'];
332 $instance['current_forumid_filter'] = (bool) (int) $new_instance['current_forumid_filter'];
333 $instance['goto_unread'] = (bool) (int) $new_instance['goto_unread'];
334 $instance['refresh_interval'] = (int) $new_instance['refresh_interval'];
335
336 return $instance;
337 }
338
339 public function get_forum_tree() {
340 ob_start();
341 WPF()->forum->tree( 'select_box', false, [] );
342 wp_send_json_success( [ 'html' => ob_get_clean() ] );
343 }
344 }
345