PluginProbe
wpForo Forum / 3.1.6
wpForo Forum v3.1.6
3.1.6 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 All 138 releases
wpforo / classes / Posts.php

Posts.php in wpForo Forum 3.1.6, at classes/Posts.php

2,366 lines 84.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace wpforo\classes;
4
5 // Exit if accessed directly
6 if( ! defined( 'ABSPATH' ) ) exit;
7
8 class Posts {
9 private $fields = [];
10
11 private $touched_postids = [];
12
13 public static $cache = [ 'posts' => [], 'post' => [], 'item' => [], 'topic_slug' => [], 'forum_slug' => [], 'post_url' => [] ];
14
15 function __construct() {
16 $this->init_hooks();
17 }
18
19 public function get_cache( $var ) {
20 if( isset( self::$cache[ $var ] ) ) return self::$cache[ $var ];
21 }
22
23 public function reset() {
24 self::$cache = [ 'posts' => [], 'post' => [], 'item' => [], 'topic_slug' => [], 'forum_slug' => [], 'post_url' => [] ];
25 }
26
27 /**
28 * Get appropriate error message for flood protection
29 *
30 * @param string $reason The flood reason code
31 * @return string Error message
32 */
33 private function get_flood_error_message( $reason ) {
34 switch( $reason ) {
35 case 'temp_ban':
36 $remaining = WPF()->perm->get_flood_ban_remaining();
37 $minutes = ceil( $remaining / 60 );
38 return sprintf(
39 __( 'You have been temporarily blocked for posting too quickly. Please wait %d minute(s) before trying again.', 'wpforo' ),
40 $minutes
41 );
42 case 'per_minute':
43 return __( 'You are posting too quickly. Please wait a moment before posting again.', 'wpforo' );
44 case 'per_hour':
45 return __( 'You have reached the hourly posting limit. Please wait before posting more.', 'wpforo' );
46 case 'ip_per_hour':
47 return __( 'Too many posts from your location. Please wait before trying again.', 'wpforo' );
48 case 'interval':
49 default:
50 return __( 'You are posting too quickly. Slow down.', 'wpforo' );
51 }
52 }
53
54 private function init_hooks() {
55 add_filter( 'wpforo_content_after', [ $this, 'print_custom_fields' ], 99, 2 );
56 add_action( 'wpforo_after_delete_user', [ $this, 'after_delete_user' ], 10, 2 );
57 add_action( 'wpforo_before_delete_reaction', [ $this, 'before_delete_reaction' ], 10, 2 );
58 add_action( 'wpforo_after_delete_reaction', [ $this, 'after_delete_reaction' ] );
59 add_action( 'wpforo_after_add_reaction', [ $this, 'after_add_reaction' ] );
60 }
61
62 /**
63 * @param int $layout
64 *
65 * @return int items_per_page
66 */
67 public function get_option_items_per_page( $layout = null ) {
68 switch( $layout ) {
69 case 4:
70 $items_per_page = wpforo_setting( 'topics', 'layout_threaded_posts_per_page' );
71 break;
72 case 3:
73 $items_per_page = wpforo_setting( 'topics', 'layout_qa_posts_per_page' );
74 break;
75 default:
76 $items_per_page = wpforo_setting( 'topics', 'posts_per_page' );
77 break;
78 }
79
80 return (int) apply_filters( 'wpforo_post_get_option_items_per_page', $items_per_page, $layout );
81 }
82
83 /**
84 * @param int $layout
85 *
86 * @return bool
87 */
88 public function get_option_union_first_post( $layout ) {
89 $layout = intval( $layout );
90 $union_first_post = (bool) wpforo_setting( 'topics', 'union_first_post', $layout );
91
92 return (bool) apply_filters( 'wpforo_post_options_get_union_first_post', $union_first_post, $layout );
93 }
94
95 public function add( $args = [] ) {
96
97 $guestposting = false;
98 $root_exists = wpforo_root_exist();
99
100 if( empty( $args ) && empty( $_REQUEST['post'] ) ) {
101 WPF()->notice->add( 'Reply request error', 'error' );
102
103 return false;
104 }
105 if( empty( $args ) && ! empty( $_REQUEST['post'] ) ) $args = $_REQUEST['post'];
106 if( ! isset( $args['body'] ) || ! $args['body'] ) {
107 WPF()->notice->add( 'Post is empty', 'error' );
108
109 return false;
110 }
111 if( ! wpfval( $args, 'title' ) && wpfval( $args, 'topicid' ) ) {
112 $args['title'] = wpforo_phrase( 'RE', false ) . ': ' . wpforo_topic( $args['topicid'], 'title' );
113 }
114 $args['name'] = ( isset( $args['name'] ) ? strip_tags( (string) $args['name'] ) : '' );
115 $args['email'] = ( isset( $args['email'] ) ? sanitize_email( $args['email'] ) : '' );
116 if( isset( $args['userid'] ) && $args['userid'] == 0 && $args['name'] && $args['email'] ) $guestposting = true;
117
118 // Security: extract only expected keys to prevent variable injection (e.g., $guestposting override)
119 $topicid = isset( $args['topicid'] ) ? $args['topicid'] : null;
120 $parentid = isset( $args['parentid'] ) ? $args['parentid'] : null;
121 $title = isset( $args['title'] ) ? $args['title'] : null;
122 $body = isset( $args['body'] ) ? $args['body'] : null;
123 $created = isset( $args['created'] ) ? $args['created'] : null;
124 $userid = isset( $args['userid'] ) ? $args['userid'] : null;
125 $root = isset( $args['root'] ) ? $args['root'] : null;
126 $name = isset( $args['name'] ) ? $args['name'] : '';
127 $email = isset( $args['email'] ) ? $args['email'] : '';
128 $status = isset( $args['status'] ) ? $args['status'] : null;
129
130 if( ! isset( $topicid ) || ! $topicid ) {
131 WPF()->notice->add( 'Error: No topic selected', 'error' );
132
133 return false;
134 }
135 if( ! $topic = WPF()->topic->get_topic( intval( $topicid ) ) ) {
136 WPF()->notice->add( 'Error: Topic is not found', 'error' );
137
138 return false;
139 }
140 if( ! $forum = WPF()->forum->get_forum( intval( $topic['forumid'] ) ) ) {
141 WPF()->notice->add( 'Error: Forum is not found', 'error' );
142
143 return false;
144 }
145
146 if( $topic['closed'] ) {
147 WPF()->notice->add( 'Can\'t write a post: This topic is closed', 'error' );
148
149 return false;
150 }
151
152 if( ! $guestposting && ! ( WPF()->perm->forum_can( 'cr', $topic['forumid'] ) || ( wpforo_is_owner( $topic['userid'], $topic['email'] ) && WPF()->perm->forum_can(
153 'ocr',
154 $topic['forumid']
155 ) ) ) ) {
156 WPF()->notice->add( 'You don\'t have permission to create post in this forum', 'error' );
157
158 return false;
159 }
160
161 // Skip flood check for AI-generated content (created by AI Tasks)
162 if ( empty( $args['is_ai_generated'] ) ) {
163 $flood_reason = '';
164 $flood_result = WPF()->perm->can_post_now( $flood_reason );
165 if( $flood_result === false ) {
166 $message = $this->get_flood_error_message( $flood_reason );
167 WPF()->notice->add( $message, 'error' );
168 return false;
169 }
170 // If flood action is 'unapprove', force the post to be unapproved
171 if( $flood_result === 'unapprove' ) {
172 $args['status'] = 1;
173 $args['_flood_reason'] = $flood_reason; // Pass flood reason for moderation logging
174 }
175 }
176
177 if( ! WPF()->current_userid && $args['email'] ) WPF()->member->set_guest_cookies( $args );
178
179 do_action( 'wpforo_start_add_post', $args );
180
181 $post = $args;
182 $post['forumid'] = $forumid = ( isset( $topic['forumid'] ) ? intval( $topic['forumid'] ) : 0 );
183 $post['parentid'] = $parentid = ( isset( $parentid ) ? intval( $parentid ) : 0 );
184 $post['title'] = $title = ( isset( $title ) ? wpforo_text( trim( (string) $title ), 250, false ) : '' );
185 $post['body'] = $body = ( isset( $body ) ? preg_replace( '#</pre>[\r\n\t\s\0]*<pre>#isu', "\r\n", (string) $body ) : '' );
186 $post['created'] = $created = ( isset( $created ) ? $created : current_time( 'mysql', 1 ) );
187 $post['userid'] = $userid = ( isset( $userid ) && wpforo_current_user_is( 'moderator' ) ? wpforo_bigintval( $userid ) : WPF()->current_userid );
188 if( $root_exists ) {
189 $post['root'] = ( $parentid ) ? ( isset( $root ) ? intval( $root ) : $this->get_root( $parentid ) ) : - 1;
190 } else {
191 $root = null;
192 }
193
194 $post = apply_filters( 'wpforo_add_post_data_filter', $post );
195
196 if( empty( $post ) ) return false;
197
198 // Security: explicitly reassign from filtered $post instead of extract()
199 $forumid = isset( $post['forumid'] ) ? $post['forumid'] : $forumid;
200 $topicid = isset( $post['topicid'] ) ? $post['topicid'] : $topicid;
201 $parentid = isset( $post['parentid'] ) ? $post['parentid'] : $parentid;
202 $title = isset( $post['title'] ) ? $post['title'] : $title;
203 $body = isset( $post['body'] ) ? $post['body'] : $body;
204 $created = isset( $post['created'] ) ? $post['created'] : $created;
205 $userid = isset( $post['userid'] ) ? $post['userid'] : $userid;
206 $root = isset( $post['root'] ) ? $post['root'] : $root;
207 $name = isset( $post['name'] ) ? $post['name'] : $name;
208 $email = isset( $post['email'] ) ? $post['email'] : $email;
209 $status = isset( $post['status'] ) ? $post['status'] : $status;
210
211 if( isset( $forumid ) ) $forumid = intval( $forumid );
212 if( isset( $topicid ) ) $topicid = intval( $topicid );
213 if( isset( $parentid ) ) $parentid = intval( $parentid );
214 if( isset( $title ) ) $title = sanitize_text_field( trim( $title ) );
215 if( isset( $created ) ) $created = sanitize_text_field( $created );
216 if( isset( $userid ) ) $userid = wpforo_bigintval( $userid );
217 if( isset( $body ) ) $body = wpforo_kses( trim( (string) $body ), 'post' );
218 $status = ( isset( $status ) && $status ? 1 : 0 );
219 $private = ( isset( $topic['private'] ) && $topic['private'] ? 1 : 0 );
220 if( isset( $name ) ) $name = strip_tags( trim( (string) $name ) );
221 if( isset( $email ) ) $email = strip_tags( trim( (string) $email ) );
222
223 do_action( 'wpforo_before_add_post', $post );
224
225 $fields = [
226 'forumid' => $forumid,
227 'topicid' => $topicid,
228 'parentid' => $parentid,
229 'userid' => $userid,
230 'title' => stripslashes( $title ),
231 'body' => stripslashes( (string) $body ),
232 'created' => $created,
233 'modified' => $created,
234 'status' => $status,
235 'private' => $private,
236 'name' => $name,
237 'email' => $email,
238 'root' => $root,
239 ];
240
241 $values = [ '%d', '%d', '%d', '%d', '%s', '%s', '%s', '%s', '%d', '%d', '%s', '%s', '%d' ];
242
243 if( ! $root_exists ) {
244 unset( $fields['root'] );
245 unset( $fields[12] );
246 }
247
248 if( WPF()->db->insert(
249 WPF()->tables->posts,
250 $fields,
251 $values
252 ) ) {
253 $postid = WPF()->db->insert_id;
254
255 $post['postid'] = $postid;
256 $post['status'] = $status;
257 $post['private'] = $private;
258 $post['posturl'] = $this->get_url( $postid );
259
260 if( $root_exists ) WPF()->topic->rebuild_threads( $topic, $root );
261
262 $post = apply_filters( 'wpforo_after_add_post_filter', $post, $topic, $forum );
263 do_action( 'wpforo_after_add_post', $post, $topic, $forum );
264
265 // Set guest ownership cookie for secure edit verification.
266 // Uses the local $userid/$email actually written to the row, not the
267 // twice-filtered $post array, which addons may alter.
268 if( ! $userid && $email ) {
269 wpforo_add_guest_ownership( $postid );
270 }
271
272 wpforo_clean_cache( 'post', $postid, $post );
273 WPF()->notice->add( 'You successfully replied', 'success' );
274
275 return $postid;
276 }
277
278 WPF()->notice->add( 'Reply request error', 'error' );
279
280 return false;
281 }
282
283 public function edit( $args = [] ) {
284
285 //This variable will be based on according CAN of guest usergroup once Guest Posing is ready
286 $guestposting = false;
287
288 if( empty( $args ) && ( ! isset( $_REQUEST['post'] ) || empty( $_REQUEST['post'] ) ) ) return false;
289 if( empty( $args ) && ! empty( $_REQUEST['post'] ) ) $args = $_REQUEST['post'];
290 if( isset( $args['name'] ) ) {
291 $args['name'] = strip_tags( (string) $args['name'] );
292 }
293 if( isset( $args['email'] ) ) {
294 $args['email'] = sanitize_email( $args['email'] );
295 }
296
297 do_action( 'wpforo_start_edit_post', $args );
298
299 if( ! isset( $args['postid'] ) || ! $args['postid'] || ! wpforo_is_id( $args['postid'] ) ) {
300 WPF()->notice->add( 'Cannot update post data', 'error' );
301
302 return false;
303 }
304 $args['postid'] = intval( $args['postid'] );
305 if( ! $post = $this->get_post( $args['postid'] ) ) {
306 WPF()->notice->add( 'No Posts found for update', 'error' );
307
308 return false;
309 }
310 if( ! $topic = WPF()->topic->get_topic( $post['topicid'] ) ) {
311 WPF()->notice->add( 'No Topic found for update', 'error' );
312
313 return false;
314 }
315 if( ! $forum = WPF()->forum->get_forum( $topic['forumid'] ) ) {
316 WPF()->notice->add( 'No Forum found for update', 'error' );
317
318 return false;
319 }
320
321 if( ! is_user_logged_in() ) {
322 if( ! isset( $post['email'] ) || ! $post['email'] ) {
323 WPF()->notice->add( 'Permission denied', 'error' );
324
325 return false;
326 } elseif( ! wpforo_guest_owns_post( $post['postid'] ) ) {
327 WPF()->notice->add( 'You are not allowed to edit this post', 'error' );
328
329 return false;
330 }
331 if( ! $args['name'] || ! $args['email'] ) {
332 WPF()->notice->add( 'Please insert required fields!', 'error' );
333
334 return false;
335 } else {
336 WPF()->member->set_guest_cookies( $args );
337 }
338 }
339
340 $args['userid'] = $post['userid'];
341 $args['status'] = $post['status'];
342
343 if( isset( $args['userid'] ) && $args['userid'] == 0 && isset( $args['name'] ) && isset( $args['email'] ) ) $guestposting = true;
344
345 $args = apply_filters( 'wpforo_edit_post_data_filter', $args );
346 if( empty( $args ) ) return false;
347
348 // Security: extract only expected keys to prevent variable injection (e.g., $guestposting override)
349 $postid = isset( $args['postid'] ) ? $args['postid'] : null;
350 $topicid = isset( $args['topicid'] ) ? $args['topicid'] : null;
351 $title = isset( $args['title'] ) ? $args['title'] : null;
352 $body = isset( $args['body'] ) ? $args['body'] : null;
353 $status = isset( $args['status'] ) ? $args['status'] : null;
354 $private = isset( $args['private'] ) ? $args['private'] : null;
355 $name = isset( $args['name'] ) ? $args['name'] : null;
356 $email = isset( $args['email'] ) ? $args['email'] : null;
357
358 if( ! $guestposting ) {
359 $diff = time() - strtotime( $post['created'] . ' GMT' );
360 if( ! ( WPF()->perm->forum_can( 'er', $post['forumid'] ) || ( WPF()->current_userid == $post['userid'] && WPF()->perm->forum_can( 'eor', $post['forumid'] ) ) ) ) {
361 WPF()->notice->add( 'You don\'t have permission to edit post from this forum', 'error' );
362
363 return false;
364 }
365
366 if( ! WPF()->perm->forum_can( 'er', $post['forumid'] ) && wpforo_setting( 'posting', 'edit_own_post_durr' ) !== 0 && $diff > wpforo_setting( 'posting', 'edit_own_post_durr' ) ) {
367 WPF()->notice->add( 'The time to edit this post is expired.', 'error' );
368
369 return false;
370 }
371 }
372
373 $title = ( isset( $title ) ? wpforo_text( trim( (string) $title ), 250, false ) : '' );
374 $body = ( isset( $body ) ? preg_replace( '#</pre>[\r\n\t\s\0]*<pre>#isu', "\r\n", (string) $body ) : '' );
375 $body = wpforo_kses( trim( (string) $body ), 'post' );
376
377 $topicid = wpforo_bigintval( ( isset( $topicid ) ? $topicid : $post['topicid'] ) );
378 $title = trim( $title ) ? stripslashes( sanitize_text_field( trim( $title ) ) ) : stripslashes( (string) $post['title'] );
379 $body = $body ? stripslashes( (string) $body ) : stripslashes( (string) $post['body'] );
380 $status = isset( $status ) ? intval( $status ) : intval( $post['status'] );
381 $private = isset( $private ) ? intval( $private ) : intval( $post['private'] );
382 $name = isset( $name ) ? stripslashes( strip_tags( trim( (string) $name ) ) ) : stripslashes( (string) $post['name'] );
383 $email = isset( $email ) ? stripslashes( strip_tags( trim( (string) $email ) ) ) : stripslashes( (string) $post['email'] );
384
385 if( false !== WPF()->db->update(
386 WPF()->tables->posts,
387 [
388 'title' => $title,
389 'body' => $body,
390 'modified' => current_time(
391 'mysql',
392 1
393 ),
394 'status' => $status,
395 'name' => $name,
396 'email' => $email,
397 ],
398 [ 'postid' => $postid ],
399 [ '%s', '%s', '%s', '%d', '%s', '%s' ], [ '%d' ]
400 ) ) {
401 $post['topicid'] = $topicid;
402 $post['title'] = $title;
403 $post['body'] = $body;
404 $post['status'] = $status;
405 $post['private'] = $private;
406 $post['name'] = $name;
407 $post['email'] = $email;
408 do_action( 'wpforo_after_edit_post', $post, $topic, $forum, $args );
409
410 wpforo_clean_cache( 'post', $postid, $post );
411 WPF()->notice->add( 'This post successfully edited', 'success' );
412
413 return $postid;
414 }
415
416 WPF()->notice->add( 'Reply request error', 'error' );
417
418 return false;
419 }
420
421 #################################################################################
422
423 /**
424 * Delete post from DB
425 * Returns true if successfully deleted or false.
426 *
427 * @param int $postid
428 * @param bool $delete_cache
429 * @param bool $rebuild_data
430 * @param array &$exclude
431 * @param bool $check_permissions
432 *
433 * @return bool
434 * @since 1.0.0
435 *
436 */
437 function delete( $postid, $delete_cache = true, $rebuild_data = true, &$exclude = [], $check_permissions = true ) {
438 $postid = intval( $postid );
439 $exclude = (array) $exclude;
440
441 if( ! $post = $this->get_post( $postid ) ) return true;
442
443 do_action( 'wpforo_before_delete_post', $post );
444
445 $diff = time() - strtotime( $post['created'] . ' GMT' );
446 if( $check_permissions && ( ! ( WPF()->perm->forum_can( 'dr', $post['forumid'] ) || ( WPF()->current_userid == $post['userid'] && WPF()->perm->forum_can( 'dor', $post['forumid'] ) ) ) ) ) {
447 WPF()->notice->add( 'You don\'t have permission to delete post from this forum', 'error' );
448
449 return false;
450 }
451
452 if( $check_permissions && ( ! WPF()->perm->forum_can( 'dr', $post['forumid'] ) && wpforo_setting( 'posting', 'delete_own_post_durr' ) !== 0 && $diff > wpforo_setting(
453 'posting',
454 'delete_own_post_durr'
455 ) ) ) {
456 WPF()->notice->add( 'The time to delete this post is expired.', 'error' );
457
458 return false;
459 }
460 //Find and delete default attachments before deleting post
461 $this->delete_attachments( $postid );
462
463 //Delete post
464 if( WPF()->db->delete( WPF()->tables->posts, [ 'postid' => $postid ], [ '%d' ] ) ) {
465 $layout = WPF()->forum->get_layout( $post['forumid'] );
466
467 if( isset( $post['parentid'] ) ) {
468 if( ! $post['is_first_post'] && $layout === 4 ) {
469 if( $post['parentid'] == 0 ) {
470 $replies = WPF()->db->get_results( "SELECT `postid` FROM `" . WPF()->tables->posts . "` WHERE `root` = " . wpforo_bigintval( $postid ), ARRAY_A );
471 } else {
472 $children = [];
473 $replies = $this->get_children( $postid, $children, true );
474 }
475 if( ! empty( $replies ) ) {
476 foreach( $replies as $reply ) {
477 if( ! in_array( $reply['postid'], $exclude ) ) {
478 $exclude[] = $reply['postid'];
479 $this->delete( $reply['postid'], false, false, $exclude, false );
480 }
481 }
482 }
483 } elseif( $post['parentid'] != 0 ) {
484 WPF()->db->query( "UPDATE `" . WPF()->tables->posts . "` SET `parentid` = " . wpforo_bigintval( $post['parentid'] ) . " WHERE `parentid` = " . wpforo_bigintval( $postid ) );
485 }
486 }
487
488 if( $rebuild_data && ! $post['is_first_post'] && $layout === 4 ) WPF()->topic->rebuild_threads( $post['topicid'] );
489
490 do_action( 'wpforo_after_delete_post', $post );
491
492 WPF()->notice->add( 'This post successfully deleted', 'success' );
493
494 if( $post['is_first_post'] ) return WPF()->topic->delete( $post['topicid'], true, $check_permissions );
495 if( $delete_cache ) wpforo_clean_cache( 'post', $postid, $post );
496
497 return true;
498 }
499
500 WPF()->notice->add( 'Post delete error', 'error' );
501
502 return false;
503 }
504
505 /**
506 * @param int $postid
507 * @param bool $protect
508 *
509 * @return array
510 * @since 1.0.0
511 *
512 */
513 public function _get_post( $postid, $protect = true ) {
514 $sql = "SELECT * FROM `" . WPF()->tables->posts . "` WHERE `postid` = %d";
515 $post = (array) WPF()->db->get_row( WPF()->db->prepare( $sql, $postid ), ARRAY_A );
516
517 if( ! empty( $post ) ) $post['userid'] = wpforo_bigintval( $post['userid'] );
518
519 if( $protect ) {
520 if( isset( $post['forumid'] ) && $post['forumid'] && ! WPF()->perm->forum_can( 'vf', $post['forumid'] ) ) {
521 return [];
522 }
523 if( isset( $post['status'] ) && $post['status'] && ! wpforo_is_owner( $post['userid'], $post['email'] ) ) {
524 if( isset( $post['forumid'] ) && $post['forumid'] && ! WPF()->perm->forum_can( 'au', $post['forumid'] ) ) {
525 return [];
526 }
527 }
528 }
529
530 if( $post ) {
531 $post['url'] = $this->get_url( $post );
532 $post['full_url'] = $this->get_full_url( $post );
533 $post['short_url'] = $this->get_short_url( $post );
534 }
535
536 return apply_filters( 'wpforo_get_post', $post );
537 }
538
539 public function get_post( $postid, $protect = true ) {
540 return wpforo_ram_get( [ $this, '_get_post' ], $postid, $protect );
541 }
542
543
544 /**
545 * get all posts based on provided arguments
546 *
547 * @param array $args
548 * @param int $items_count
549 * @param bool $count
550 *
551 * @return array
552 * @since 1.0.0
553 *
554 */
555 function get_posts( $args = [], &$items_count = 0, $count = true ) {
556
557 $cache = WPF()->cache->on( 'post' );
558
559 $default = [
560 'include' => [], // array( 2, 10, 25 )
561 'exclude' => [], // array( 2, 10, 25 )
562 'forumids' => [],
563 'topicid' => null, // topic id in DB
564 'forumid' => null, // forum id in DB
565 'parentid' => null, // parent post id
566 'root' => null, // root postid
567 'userid' => null, // user id in DB
568 'orderby' => '`is_first_post` DESC, `created` ASC, `postid` ASC', // forumid, order, parentid
569 'order' => '', // ASC DESC
570 'offset' => null, // this use when you give row_count
571 'row_count' => null, // 4 or 1 ...
572 'status' => null, // 0 or 1 ...
573 'private' => null, // 0 or 1 ...
574 'email' => null, // example@example.com ...
575 'check_private' => true,
576 'where' => null,
577 'owner' => null,
578 'cache_type' => 'sql', // sql or args
579 'limit_per_topic' => null,
580 'union_first_post' => false,
581 'is_first_post' => null,
582 'is_answer' => null,
583 'threaded' => false,
584 ];
585
586 $request = $args;
587 if( empty( $args['orderby'] ) ) $args['order'] = '';
588
589 $args = wpforo_parse_args( $args, $default );
590
591 if( $args['row_count'] === 0 ) return [];
592 if( $args['forumid'] && $args['check_private'] && ! WPF()->perm->forum_can( 'vf', $args['forumid'] ) ) return [];
593 if( strtoupper( (string) $args['order'] ) !== 'DESC' && strtoupper( (string) $args['order'] ) !== 'ASC' && strtoupper( (string) $args['order'] ) !== 'RAND' ) $args['order'] = '';
594 if( ! wpforo_root_exist() && ! is_null( $args['root'] ) ) {
595 $args['parentid'] = $args['root'];
596 $args['root'] = null;
597 }
598
599 $wheres = $this->get_posts_conditions( $args );
600 if( $args['order'] === 'RAND' ) {
601 $ordering = " ORDER BY RAND()";
602 } else {
603 $ordering = ( $args['orderby'] ? " ORDER BY " . esc_sql( $args['orderby'] . ' ' . $args['order'] ) : '' );
604 }
605 $limiting = ( $args['row_count'] ? " LIMIT " . intval( $args['offset'] ) . "," . intval( $args['row_count'] ) : '' );
606
607 if( $limit_per_topic = intval( $args['limit_per_topic'] ) ) {
608 $sql = "SELECT SUBSTRING_INDEX( GROUP_CONCAT(`postid` ORDER BY `created` DESC), ',', " . $limit_per_topic . " ) postids
609 FROM `" . WPF()->tables->posts . "` " . ( $wheres ? " WHERE " . implode( " AND ", $wheres ) : '' ) . " GROUP BY `topicid` ORDER BY MAX(`postid`) DESC " . $limiting;
610
611 if( $cache ) {
612 if( $args['cache_type'] === 'sql' ) {
613 $object_key = md5( $sql . WPF()->current_user_groupid );
614 $object_cache = WPF()->cache->get( $object_key );
615 if( ! empty( $object_cache ) ) {
616 return apply_filters( 'wpforo_get_posts', $object_cache['items'] );
617 }
618 }
619 }
620
621 // Returns an array of post IDs ////////////////////////////////
622 $posts = WPF()->db->get_col( $sql );
623 ////////////////////////////////////////////////////////////////
624
625 } else {
626
627 $sql = "SELECT * FROM `" . WPF()->tables->posts . "`";
628 if( ! empty( $wheres ) ) {
629 $sql .= " WHERE " . implode( " AND ", $wheres );
630 }
631 if( $count ) {
632 $item_count_sql = preg_replace( '#SELECT.+?FROM#isu', 'SELECT count(*) FROM', $sql, 1 );
633 if( $item_count_sql ) $items_count = WPF()->db->get_var( $item_count_sql );
634 }
635
636 $sql .= $ordering . $limiting;
637
638 if( $args['union_first_post'] && $args['topicid'] && ! $args['parentid'] && $items_count > intval( $args['offset'] ) ) {
639 $sql = "( SELECT * FROM `" . WPF()->tables->posts . "`
640 WHERE `topicid` = " . wpforo_bigintval( $args['topicid'] ) . "
641 AND `is_first_post` = 1 )
642 UNION
643 ( " . $sql . " )";
644 }
645
646 if( $cache ) {
647 if( $args['cache_type'] === 'sql' ) {
648 $object_key = md5( $sql . WPF()->current_user_groupid );
649 $object_cache = WPF()->cache->get( $object_key );
650 if( ! empty( $object_cache ) ) {
651 return apply_filters( 'wpforo_get_posts', $object_cache['items'] );
652 }
653 }
654 if( $args['cache_type'] === 'args' ) {
655 $hach = serialize( $request );
656 $cache_args_key = md5( $hach . WPF()->current_user_groupid );
657 $object_cache = WPF()->cache->get( $cache_args_key, 'loop', 'post' );
658 if( ! empty( $object_cache ) ) {
659 return apply_filters( 'wpforo_get_posts', $object_cache['items'] );
660 }
661 }
662 }
663
664 // Returns an array of posts /////////////////////////////////
665 $posts = WPF()->db->get_results( $sql, ARRAY_A );
666 //////////////////////////////////////////////////////////////
667
668 if( $args['check_private'] && ! $args['forumid'] ) {
669 $posts = $this->access_filter( $posts );
670 }
671 }
672
673 if( $cache && isset( $object_key ) && ! empty( $posts ) ) {
674 self::$cache['posts'][ $object_key ]['items'] = $posts;
675 self::$cache['posts'][ $object_key ]['items_count'] = $items_count;
676 if( isset( $cache_args_key ) && $args['cache_type'] == 'args' ) {
677 WPF()->cache->create_custom( $request, $posts, 'post', $items_count );
678 }
679 }
680
681 return apply_filters( 'wpforo_get_posts', $posts );
682 }
683
684 function get_posts_conditions( $args = [] ) {
685
686 $wheres = [];
687 $table_as_prefix = '`' . WPF()->tables->posts . '`.';
688
689 $args['include'] = wpforo_parse_args( $args['include'] );
690 $args['exclude'] = wpforo_parse_args( $args['exclude'] );
691 $args['forumids'] = wpforo_parse_args( $args['forumids'] );
692
693 if( ! empty( $args['include'] ) ) $wheres[] = $table_as_prefix . "`postid` IN(" . implode( ',', array_map( 'wpforo_bigintval', $args['include'] ) ) . ")";
694 if( ! empty( $args['exclude'] ) ) $wheres[] = $table_as_prefix . "`postid` NOT IN(" . implode( ',', array_map( 'wpforo_bigintval', $args['exclude'] ) ) . ")";
695 if( ! empty( $args['forumids'] ) ) $wheres[] = $table_as_prefix . "`forumid` IN(" . implode( ',', array_map( 'intval', $args['forumids'] ) ) . ")";
696
697 if( ! is_null( $args['topicid'] ) ) $wheres[] = $table_as_prefix . "`topicid` = " . wpforo_bigintval( $args['topicid'] );
698 if( ! is_null( $args['parentid'] ) ) $wheres[] = $table_as_prefix . "`parentid` = " . wpforo_bigintval( $args['parentid'] );
699 if( ! is_null( $args['root'] ) ) $wheres[] = $table_as_prefix . "`root` = " . wpforo_bigintval( $args['root'] );
700 if( ! is_null( $args['userid'] ) ) $wheres[] = $table_as_prefix . "`userid` = " . wpforo_bigintval( $args['userid'] );
701 if( ! is_null( $args['status'] ) ) $wheres[] = $table_as_prefix . "`status` = " . intval( (bool) $args['status'] );
702 if( ! is_null( $args['private'] ) ) $wheres[] = $table_as_prefix . "`private` = " . intval( (bool) $args['private'] );
703 if( ! is_null( $args['is_first_post'] ) ) $wheres[] = $table_as_prefix . "`is_first_post` = " . intval( (bool) $args['is_first_post'] );
704 if( ! is_null( $args['is_answer'] ) ) $wheres[] = $table_as_prefix . "`is_answer` = " . intval( (bool) $args['is_answer'] );
705 if( ! is_null( $args['email'] ) ) $wheres[] = $table_as_prefix . "`email` = '" . esc_sql( $args['email'] ) . "' ";
706 if( ! is_null( $args['where'] ) ) $wheres[] = $table_as_prefix . $args['where'];
707
708 if( wpfval( $args, 'forumid' ) && $args['check_private'] ) {
709
710 /////Check "View Reply" Access//////////////////////////////
711 if( ! WPF()->perm->forum_can( 'vr', $args['forumid'] ) ) {
712 $wheres[] = $table_as_prefix . " `is_first_post` = 1";
713 }
714
715 /////Check Unapproved Post Access////////////////////////////
716 if( WPF()->perm->forum_can( 'au', $args['forumid'] ) ) {
717 //Check "Can Approve/Unapprove Posts" Access (View Unapproved Posts)
718 if( ! is_null( $args['status'] ) ) $wheres[] = $table_as_prefix . " `status` = " . intval( $args['status'] );
719 } elseif( WPF()->current_userid ) {
720 //Allow Users see own unapproved posts
721 $wheres[] = " ( " . $table_as_prefix . "`status` = 0 OR (" . $table_as_prefix . "`status` = 1 AND " . $table_as_prefix . "`userid` = " . intval( WPF()->current_userid ) . ") )";
722 } elseif( WPF()->current_user_email ) {
723 //Allow Guests see own unapproved posts
724 $wheres[] = " ( " . $table_as_prefix . "`status` = 0 OR (" . $table_as_prefix . "`status` = 1 AND " . $table_as_prefix . "`email` = '" . sanitize_email(
725 WPF()->current_user_email
726 ) . "') )";
727 } else {
728 //If doesn't have "Can Approve/Unapprove Posts" access and not Owner, only return approved posts
729 $wheres[] = " " . $table_as_prefix . "`status` = 0";
730 }
731 }
732
733 return $wheres;
734 }
735
736 function access_filter( $posts, $user = null ) {
737 if( ! empty( $posts ) ) {
738 foreach( $posts as $key => $post ) {
739 if( ! $this->view_access( $post, $user ) ) unset( $posts[ $key ] );
740 }
741 }
742
743 return $posts;
744 }
745
746 private function _view_access( $post, $user = null ): bool {
747 $groupids = wpfval( $user, 'groupids' );
748 if( ! WPF()->perm->forum_can( 'vf', $post['forumid'], $groupids ) || ! WPF()->perm->forum_can( 'vt', $post['forumid'], $groupids ) || ( ! (int) wpfval( $post, 'is_first_post' ) && ! WPF(
749 )->perm->forum_can( 'vr', $post['forumid'], $groupids ) ) ) {
750 return apply_filters( 'wpforo_post_access', false, $post, $user, 'vr' );
751 }
752
753 if( (int) wpfval( $post, 'status' ) && ! WPF()->perm->forum_can( 'au', $post['forumid'], $groupids ) ) {
754 $post_owner = wpforo_member( $post );
755 if( ! wpforo_is_users_same( $post_owner, $user ) ) {
756 return apply_filters( 'wpforo_post_access', false, $post, $user, 'au' );
757 }
758 }
759
760 $topic = wpforo_topic( $post['topicid'] );
761 if( (int) wpfval( $topic, 'private' ) && ! WPF()->perm->forum_can( 'vp', $post['forumid'], $groupids ) ) {
762 $topic_owner = wpforo_member( $topic );
763 if( ! wpforo_is_users_same( $topic_owner, $user ) ) {
764 return apply_filters( 'wpforo_post_access', false, $post, $user, 'vp' );
765 }
766 }
767
768 return true;
769 }
770
771 function view_access( $post, $user = null ): bool {
772 return apply_filters( 'wpforo_post_view_access', $this->_view_access( $post, $user ) );
773 }
774
775 function replies( array $posts, $topic = [], $forum = [], $level = 0 ) {
776 $level ++;
777 if( function_exists( 'wpforo_thread_reply' ) ) {
778 if( wpfval( $posts, 'posts' ) ) {
779 $key = key( $posts['posts'] );
780 $parentid = ( wpfval( $posts, 'posts', $key, 'parentid' ) ) ? $posts['posts'][ $key ]['parentid'] : 0;
781 $max_level = wpforo_setting( 'topics', 'layout_threaded_nesting_level' );
782 if( ! $max_level ) {
783 $class = ( $level > 1 ) ? '' : ' level-1';
784 } elseif( $level > ( $max_level + 1 ) ) {
785 $class = '';
786 } else {
787 $class = ' level-' . $level;
788 }
789 echo '<div id="wpf-post-replies-' . intval( $parentid ) . '" class="wpf-post-replies ' . $class . '">';
790 foreach( $posts['posts'] as $post ) {
791 $parents = ( wpfval( $posts, 'parents' ) ) ? $posts['parents'] : [];
792 wpforo_thread_reply( $post, $topic, $forum, $level, $parents );
793 if( ! empty( $post['children'] ) ) {
794 $posts['posts'] = $post['children'];
795 $this->replies( $posts, $topic, $forum, $level );
796 }
797 }
798 echo '</div>';
799 }
800 } else {
801 wpforo_phrase( 'Function wpforo_thread_reply() not found.' );
802 }
803 }
804
805 function get_thread_tree( $post, $parents = true ) {
806 if( ! wpfval( $post, 'postid' ) || ( wpfkey( $post, 'root' ) && $post['root'] == - 1 ) ) {
807 return [ 'posts' => [], 'parents' => [], 'count' => 0, 'children' => '' ];
808 }
809
810 $items = [];
811 $thread = [];
812 $parentid = (int) $post['postid'];
813 $type = apply_filters( 'wpforo_thread_builder_type', 'topic-query' ); //'topic-query', 'inside-mysql', 'multi-query'
814 if( $type === 'topic-query' ) {
815 if( wpfval( $post, 'topicid' ) ) {
816 $args = [ 'root' => $post['postid'], 'orderby' => '`created` ASC' ];
817 $posts = $this->get_posts( $args, $items_count, false );
818 if( empty( $posts ) ) {
819 $args = [ 'topicid' => $post['topicid'], 'orderby' => '`created` ASC' ];
820 $posts = $this->get_posts( $args, $items_count, false );
821 }
822
823 if( ! empty( $posts ) ) {
824 foreach( $posts as $post ) {
825 $items[ $post['postid'] ] = $post;
826 }
827 $thread = $this->build_thread_data( $parentid, $items );
828 }
829 }
830 } elseif( $type === 'inside-mysql' ) {
831 $mod = wpforo_current_user_is( 'admin' ) || wpforo_current_user_is( 'moderator' );
832 if( wpforo_is_db_mysql8() ) {
833 $sql = "WITH RECURSIVE `post_path` AS (
834 SELECT `postid`, `parentid`, `userid`, `status`, `email`
835 FROM `" . WPF()->tables->posts . "`
836 WHERE `parentid` = %d
837 UNION
838 SELECT p.`postid`, p.`parentid`, p.`userid`, p.`status`, p.`email`
839 FROM `" . WPF()->tables->posts . "` p
840 INNER JOIN `post_path` pp ON pp.`parentid` <> pp.`postid` AND pp.`postid` = p.`parentid`
841 ) SELECT CONCAT( `postid`, '-', `parentid`, '-', `userid`, '-', `status`, '-', `email` ) AS tree FROM `post_path`";
842 $posts = WPF()->db->get_col( WPF()->db->prepare( $sql, $parentid ) );
843 } else {
844 $sql = "SELECT GROUP_CONCAT( @id := (
845 SELECT GROUP_CONCAT(postid,'-', parentid, '-', userid, '-', status, '-', email)
846 FROM `" . WPF()->tables->posts . "`
847 WHERE parentid = @id
848 )
849 ) AS tree
850 FROM ( SELECT @id := %d ) vars
851 STRAIGHT_JOIN `" . WPF()->tables->posts . "`
852 WHERE @id IS NOT NULL";
853 $posts = explode( ',', (string) WPF()->db->get_var( WPF()->db->prepare( $sql, $parentid ) ) );
854 }
855 if( ! empty( $posts ) ) {
856 foreach( $posts as $post ) {
857 $post = explode( '-', $post );
858 if( ! $mod && isset( $post[3] ) && $post[3] ) {
859 if( isset( $post[2] ) && isset( $post[4] ) && ( isset( WPF()->current_user['userid'] ) || isset( WPF()->current_user['user_email'] ) ) ) {
860 if( WPF()->current_user['userid'] != $post[2] && WPF()->current_user['user_email'] != $post[4] ) continue;
861 }
862 }
863 if( isset( $post[0] ) && isset( $post[1] ) ) {
864 $items[ $post[0] ] = [ 'postid' => $post[0], 'parentid' => $post[1] ];
865 }
866 }
867 $thread = $this->build_thread_data( $parentid, $items );
868 }
869 } elseif( $type === 'multi-query' ) {
870 $mod = wpforo_current_user_is( 'admin' ) || wpforo_current_user_is( 'moderator' );
871 $items = $this->get_children( $parentid, $children, $mod );
872 $thread = $this->build_thread_data( $parentid, $items );
873 }
874
875 return $thread;
876 }
877
878 function build_thread_data( $parentid, $items = [], $count = 0 ) {
879 $parents = [];
880 $thread = [ 'posts' => [], 'parents' => [], 'count' => 0, 'children' => '' ];
881
882 if( ! empty( $items ) ) {
883 foreach( $items as $item ) {
884 $parents[ $item['postid'] ] = $this->parents( $item['postid'], $items );
885 }
886 if( ! empty( $parents ) ) $thread['parents'] = $parents;
887 $thread['posts'] = $this->build_thread_tree( $items, $parentid );
888 $children = $this->children( $parentid, $thread['posts'] );
889 $thread['count'] = count( $children );
890 $thread['children'] = array_keys( $children );
891 }
892
893 return $thread;
894 }
895
896 function build_thread_tree( array $posts, $parentid = 0 ) {
897 $tree = [];
898 foreach( $posts as $post ) {
899 if( $post['parentid'] == $parentid ) {
900 $children = $this->build_thread_tree( $posts, $post['postid'] );
901 if( $children ) {
902 $post['children'] = $children;
903 }
904 $tree[] = $post;
905 }
906 }
907
908 return $tree;
909 }
910
911 function root( $postid, $parentid = null ) {
912 if( ! $postid ) return 0;
913 $parents = $this->get_parents( $postid, $parentid );
914 $root = array_pop( $parents );
915
916 return intval( $root );
917 }
918
919 function get_root( $postid ) {
920 if( ! $postid || ! wpforo_root_exist() ) return $postid;
921 $root = WPF()->db->get_var( "SELECT `root` FROM `" . WPF()->tables->posts . "` WHERE `postid` = " . intval( $postid ) );
922 if( ! is_null( $root ) && ( $root <= 0 || $root == $postid ) ) {
923 $root = $postid;
924 } else {
925 $root = $this->root( $postid );
926 }
927
928 return $root;
929 }
930
931 function parents( $postid, $posts, $parents = [] ) {
932 if( ! empty( $posts ) ) {
933 if( isset( $posts[ $postid ] ) ) {
934 $parentid = wpfval( $posts[ $postid ], 'parentid' ) ? $posts[ $postid ]['parentid'] : 0;
935 if( $parentid > 0 ) {
936 array_unshift( $parents, $parentid );
937
938 return $this->parents( $parentid, $posts, $parents );
939 }
940 }
941 }
942
943 return $parents;
944 }
945
946 function get_parents( $postid, $parentid = null, &$parents = [], $mod = false ) {
947 if( $postid ) {
948 $status = ( ! $mod ) ? ' AND `status` = 0 ' : '';
949 if( is_null( $parentid ) ) {
950 $where = "`postid` = " . intval( $postid );
951 } else {
952 $where = "`postid` = " . intval( $parentid );
953 }
954 if( $parentid === 0 ) {
955 return $parents;
956 } else {
957 $post = WPF()->db->get_row( "SELECT `postid`, `parentid` FROM `" . WPF()->tables->posts . "` WHERE " . $where . $status, ARRAY_A );
958 if( wpfval( $post, 'parentid' ) ) {
959 $parents[ $post['postid'] ] = $post['parentid'];
960 $this->get_parents( $post['postid'], $post['parentid'], $parents, $mod );
961 }
962 }
963 }
964
965 return $parents;
966 }
967
968 function children( $parentid, $posts, &$children = [] ) {
969 if( $parentid ) {
970 if( ! empty( $posts ) ) {
971 foreach( $posts as $post ) {
972 $children[ $post['postid'] ] = [ 'postid' => $post['postid'], 'parentid' => $post['parentid'] ];
973 if( isset( $post['children'] ) ) $this->children( $post['postid'], $post['children'], $children );
974 }
975 }
976 }
977
978 return $children;
979 }
980
981 function get_children( $parentid, &$children = [], $mod = false ) {
982 if( $parentid ) {
983 $status = ( ! $mod ) ? ' AND `status` = 0 ' : '';
984 $posts = WPF()->db->get_results(
985 "SELECT `postid`, `parentid` FROM `" . WPF()->tables->posts . "` FORCE INDEX (PRIMARY) WHERE `parentid` = " . intval( $parentid ) . " " . $status,
986 ARRAY_A
987 );
988 if( ! empty( $posts ) ) {
989 foreach( $posts as $post ) {
990 $children[ $post['postid'] ] = [ 'postid' => $post['postid'], 'parentid' => $post['parentid'] ];
991 $this->get_children( $post['postid'], $children, $mod );
992 }
993 }
994 }
995
996 return $children;
997 }
998
999 function get_root_replies_count( $postid ) {
1000 $postid = intval( $postid );
1001 if( $postid && wpforo_root_exist() ) {
1002 return (int) WPF()->db->get_var( "SELECT COUNT(*) FROM `" . WPF()->tables->posts . "` WHERE `root` = " . $postid );
1003 } else {
1004 return (int) WPF()->db->get_var( "SELECT COUNT(*) FROM `" . WPF()->tables->posts . "` WHERE `parentid` = " . $postid );
1005 }
1006 }
1007
1008 function get_posts_filtered( $args = [] ) {
1009 $posts = $this->get_posts( $args, $items_count, false );
1010 if( ! empty( $posts ) ) {
1011 foreach( $posts as $key => $post ) {
1012 if( isset( $post['forumid'] ) && ! WPF()->perm->forum_can( 'vf', $post['forumid'] ) ) {
1013 unset( $posts[ $key ] );
1014 }
1015 if( isset( $posts[ $key ] ) && isset( $post['forumid'] ) && isset( $post['private'] ) && $post['private'] && ! wpforo_is_owner( $post['userid'], $post['email'] ) ) {
1016 if( ! WPF()->perm->forum_can( 'vp', $post['forumid'] ) ) {
1017 unset( $posts[ $key ] );
1018 }
1019 }
1020 if( isset( $posts[ $key ] ) && isset( $post['forumid'] ) && isset( $post['status'] ) && $post['status'] && ! wpforo_is_owner( $post['userid'], $post['email'] ) ) {
1021 if( ! WPF()->perm->forum_can( 'au', $post['forumid'] ) ) {
1022 unset( $posts[ $key ] );
1023 }
1024 }
1025 }
1026 }
1027
1028 return $posts;
1029 }
1030
1031 function search( $args = [], &$items_count = 0 ) {
1032 if( ! is_array( $args ) ) $args = [ 'needle' => $args ];
1033 if( ! wpfval( $args, 'needle' ) && ! wpfval( $args, 'postids' ) ) return [];
1034
1035 $args = array_filter( $args );
1036
1037 $default = [
1038 'needle' => '', // search needle
1039 'forumids' => [], // array( 2, 10, 25 )
1040 'postids' => [], // array( 2, 10, 25 )
1041 'date_period' => 0, // topic id in DB
1042 'type' => 'entire-posts', // search type ( entire-posts | titles-only | user-posts | user-topics | tag )
1043 'orderby' => 'relevancy', // Sort Search Results by ( relevancy | date | user | forum )
1044 'order' => 'DESC', // Sort Search Results ( ASC | DESC )
1045 'offset' => null, // this use when you give row_count
1046 'row_count' => null, // 4 or 1 ...
1047 ];
1048
1049 $args = wpforo_parse_args( $args, $default );
1050 $args['postids'] = wpforo_parse_args( $args['postids'] );
1051 $args['postids'] = array_filter( array_map( 'wpforo_bigintval', $args['postids'] ) );
1052
1053 $args['order'] = strtoupper( (string) $args['order'] );
1054 if( ! in_array( $args['order'], [ 'ASC', 'DESC' ] ) ) $args['order'] = 'DESC';
1055
1056 $date_period = intval( $args['date_period'] );
1057
1058 $fa = "p";
1059 $from = "`" . WPF()->tables->posts . "` " . $fa;
1060 $selects = [ $fa . '.*' ];
1061 $innerjoins = [];
1062 $wheres = [];
1063 $orders = [];
1064
1065 if( $args['forumids'] ) $wheres[] = $fa . ".`forumid` IN(" . implode( ', ', array_map( 'intval', $args['forumids'] ) ) . ")";
1066 if( $date_period != 0 ) {
1067 $date = gmdate( 'Y-m-d H:i:s', time() - ( $date_period * 24 * 60 * 60 ) );
1068 if( $date ) $wheres[] = $fa . ".`created` > '" . esc_sql( $date ) . "'";
1069 }
1070
1071 if( $args['needle'] ) {
1072 if( in_array( $args['type'], [ 'entire-posts', 'titles-only' ] ) ) {
1073 if( apply_filters( 'wpforo_enable_legacy_search', false ) ) {
1074 $words = preg_split( '#[^\p{L}\p{N}\'`]+#u', $args['needle'] );
1075 $words = array_slice( array_filter( $words ), 0, 10 );
1076 $words = array_map( function( $w ) {
1077 return '+' . esc_sql( str_replace( [ '(', ')', '*', '+', '-', '~', '<', '>', '@', '"' ], '', $w ) ) . '*';
1078 }, $words );
1079 $needle = implode( ' ', $words );
1080 } else {
1081 $needle = esc_sql( $args['needle'] );
1082 }
1083 } else {
1084 $needle = esc_sql( $args['needle'] );
1085 }
1086
1087 // The @ symbol is a special character in MySQL, typically used to define variables. To use an @ symbol as part of a search string in a full-text search, you should treat it as a regular string.
1088 // Enclose your search string with the @ symbol in double quotes. This tells MySQL to treat the enclosed string as a literal.
1089 $needle_AGAINST = strpos( $needle, '@' ) === false ? $needle : '"' . trim( $needle, '"' ) . '"';
1090
1091 if( $args['type'] === 'entire-posts' ) {
1092 $selects[] = "MATCH(" . $fa . ".`title`) AGAINST('$needle_AGAINST' IN BOOLEAN MODE) + MATCH(" . $fa . ".`body`) AGAINST('$needle_AGAINST' IN BOOLEAN MODE) AS matches";
1093 $wheres[] = "( MATCH(" . $fa . ".`title`, " . $fa . ".`body`) AGAINST('$needle_AGAINST' IN BOOLEAN MODE) OR " . $fa . ".`title` LIKE '%" . esc_sql(
1094 $args['needle']
1095 ) . "%' OR " . $fa . ".`body` LIKE '%" . esc_sql( $args['needle'] ) . "%' )";
1096 $orders[] = "matches";
1097 $orders[] = "`created`";
1098 } elseif( $args['type'] === 'titles-only' ) {
1099 $selects[] = "MATCH(" . $fa . ".`title`) AGAINST('$needle_AGAINST' IN BOOLEAN MODE) AS matches";
1100 $wheres[] = "( MATCH(" . $fa . ".`title`) AGAINST('$needle_AGAINST' IN BOOLEAN MODE) OR " . $fa . ".`title` LIKE '%" . esc_sql( $args['needle'] ) . "%' )";
1101 $orders[] = "matches";
1102 $orders[] = "`created`";
1103 } elseif( $args['type'] === 'user-posts' || $args['type'] === 'user-topics' ) {
1104 $innerjoins[] = "INNER JOIN `" . WPF()->db->users . "` u ON u.`ID` = " . $fa . ".`userid`";
1105 $wheres[] = "( u.`user_nicename` LIKE '%{$needle}%' OR u.`display_name` LIKE '%{$needle}%' )";
1106 if( $args['type'] === 'user-topics' ) $wheres[] = "" . $fa . ".`is_first_post` = 1";
1107 } elseif( $args['type'] === 'tag' ) {
1108 $fa = "t";
1109 $from = "`" . WPF()->tables->topics . "` " . $fa;
1110 $selects = [ $fa . '.*', $fa . '.`first_postid` AS postid', '1 AS `is_first_post`' ];
1111 $innerjoins = [];
1112 $wheres = [ "( " . $fa . ".`tags` LIKE '%{$needle}%' )" ];
1113 // $wheres = array( "( FIND_IN_SET('{$needle}', ".$fa.".`tags`) )" ); //exact version
1114 }
1115 }
1116
1117 if( $args['postids'] ) $wheres[] = "(" . $fa . ".`postid` IN(" . implode( ',', $args['postids'] ) . "))";
1118
1119 if( $args['orderby'] === 'date' ) {
1120 $orders = [ $fa . '.`created`' ];
1121 } elseif( $args['orderby'] === 'user' ) {
1122 $orders = [ $fa . '.`userid`' ];
1123 } elseif( $args['orderby'] === 'forum' ) {
1124 $orders = [ $fa . '.`forumid`' ];
1125 }
1126
1127 $sql = "SELECT COUNT(*) FROM " . $from . " " . implode( ' ', $innerjoins );
1128 if( $wheres ) $sql .= " WHERE " . implode( " AND ", $wheres );
1129 $items_count = (int) WPF()->db->get_var( $sql );
1130 if( wpforo_setting( 'topics', 'search_max_results' ) && $items_count > wpforo_setting( 'topics', 'search_max_results' ) ) $items_count = wpforo_setting( 'topics', 'search_max_results' );
1131
1132 $sql = "SELECT " . implode( ', ', $selects ) . " FROM " . $from . " " . implode( ' ', $innerjoins );
1133 if( $wheres ) $sql .= " WHERE " . implode( " AND ", $wheres );
1134 if( $orders ) $sql .= " ORDER BY " . implode( ' ' . $args['order'] . ', ', $orders ) . " " . $args['order'];
1135
1136 if( wpforo_setting( 'topics', 'search_max_results' ) ) $sql = "SELECT * FROM (" . $sql . " LIMIT " . wpforo_setting( 'topics', 'search_max_results' ) . ") AS p";
1137
1138 if( $args['row_count'] ) $sql .= " LIMIT " . intval( $args['offset'] ) . "," . intval( $args['row_count'] );
1139
1140 $sql = apply_filters( 'wpforo_search_sql', $sql, $args );
1141
1142 $posts = WPF()->db->get_results( $sql, ARRAY_A );
1143
1144 do_action( 'wpforo_search_result_after', $args, $items_count, $posts, $sql );
1145
1146 foreach( $posts as $key => $post ) {
1147 if( ! WPF()->perm->forum_can( 'vf', $post['forumid'] ) ) unset( $posts[ $key ] );
1148 if( ! WPF()->perm->forum_can( 'vt', $post['forumid'] ) ) unset( $posts[ $key ] );
1149 if( ! $post['is_first_post'] && ! WPF()->perm->forum_can( 'vr', $post['forumid'] ) ) unset( $posts[ $key ] );
1150 if( $post['private'] && ! WPF()->perm->forum_can( 'vp', $post['forumid'] ) ) unset( $posts[ $key ] );
1151 if( $post['status'] && ! WPF()->perm->forum_can( 'au', $post['forumid'] ) ) unset( $posts[ $key ] );
1152 }
1153
1154 return apply_filters( 'wpforo_post_search', $posts, $args, $items_count );
1155 }
1156
1157 /**
1158 * @param int $postid
1159 *
1160 * @return string
1161 * @since 1.0.0
1162 *
1163 */
1164 public function _get_forumslug_byid( $postid ) {
1165 return (string) WPF()->db->get_var(
1166 WPF()->db->prepare(
1167 "SELECT `slug` FROM " . WPF()->tables->forums . " WHERE `forumid` = (
1168 SELECT forumid FROM `" . WPF()->tables->topics . "` WHERE `topicid` = (
1169 SELECT `topicid` FROM `" . WPF()->tables->posts . "` WHERE postid = %d
1170 )
1171 )",
1172 $postid
1173 )
1174 );
1175 }
1176
1177 public function get_forumslug_byid( $postid ) {
1178 return wpforo_ram_get( [ $this, '_get_forumslug_byid' ], $postid );
1179 }
1180
1181 /**
1182 * @param int
1183 *
1184 * @return string
1185 * @since 1.0.0
1186 *
1187 */
1188 public function _get_topicslug_byid( $postid ) {
1189 return (string) WPF()->db->get_var(
1190 WPF()->db->prepare(
1191 "SELECT `slug` FROM " . WPF()->tables->topics . " WHERE `topicid` = (
1192 SELECT `topicid` FROM `" . WPF()->tables->posts . "` WHERE postid = %d
1193 )",
1194 $postid
1195 )
1196 );
1197 }
1198
1199 public function get_topicslug_byid( $postid ) {
1200 return wpforo_ram_get( [ $this, '_get_topicslug_byid' ], $postid );
1201 }
1202
1203 /**
1204 * @param array $post the array of wpforo post
1205 *
1206 * @return int
1207 */
1208 private function get_position_in_topic( $post ) {
1209 $layout = WPF()->forum->get_layout( $post['forumid'] );
1210 $pid = $post['postid'];
1211 if( $post['parentid'] ) {
1212 switch( $layout ) {
1213 case 3:
1214 $pid = $post['parentid'];
1215 break;
1216 case 4:
1217 $pid = $post['root'];
1218 break;
1219 }
1220 }
1221
1222 $where = "";
1223 $orderby = "`is_first_post` DESC, `created` ASC, `postid` ASC";
1224 if( $layout === 3 ) {
1225 $where .= " AND NOT p.`parentid` ";
1226 $orderby = "`is_first_post` DESC, `is_answer` DESC, `votes` DESC, `created` ASC, `postid` ASC";
1227 } elseif( $layout === 4 ) {
1228 $where .= " AND NOT p.`parentid` ";
1229 }
1230
1231 if( ! wpforo_current_user_is( 'admin' ) && ! wpforo_current_user_is( 'moderator' ) && ! WPF()->perm->forum_can( 'au', $post['forumid'] ) ) {
1232 if( WPF()->current_userid ) {
1233 $where .= " AND ( p.`status` = 0 OR (p.`status` = 1 AND p.`userid` = %d) ) ";
1234 $where = WPF()->db->prepare( $where, WPF()->current_userid );
1235 } elseif( WPF()->current_user_email ) {
1236 $where .= " AND ( p.`status` = 0 OR (p.`status` = 1 AND p.`email` = %s) ) ";
1237 $where = WPF()->db->prepare( $where, sanitize_email( WPF()->current_user_email ) );
1238 } else {
1239 $where .= " AND NOT p.`status` ";
1240 }
1241 }
1242
1243 if( wpforo_is_db_mysql8() ) {
1244 $sql = "SELECT tmp_view.`rownum` FROM
1245 (SELECT ROW_NUMBER() OVER() AS rownum, p.`postid`
1246 FROM `" . WPF()->tables->posts . "` p
1247 WHERE p.`topicid` = %d
1248 " . $where . "
1249 ORDER BY " . $orderby . ") AS tmp_view
1250 WHERE tmp_view.`postid` = %d";
1251 } else {
1252 $sql = "SELECT tmp_view.`rownum` FROM
1253 (SELECT @rownum := @rownum + 1 AS rownum, p.`postid`
1254 FROM `" . WPF()->tables->posts . "` p
1255 CROSS JOIN ( SELECT @rownum := 0 ) AS init_var
1256 WHERE p.`topicid` = %d
1257 " . $where . "
1258 ORDER BY " . $orderby . ") AS tmp_view
1259 WHERE tmp_view.`postid` = %d";
1260 }
1261 $sql = WPF()->db->prepare( $sql, $post['topicid'], $pid );
1262
1263 return (int) WPF()->db->get_var( $sql );
1264 }
1265
1266 /**
1267 * @param $arg
1268 *
1269 * @return string
1270 * @deprecated since 2.0.10 instead this method use get_url()
1271 *
1272 * @since 1.0.0
1273 *
1274 */
1275 function get_post_url( $arg ) {
1276 return $this->get_url( $arg );
1277 }
1278
1279 /**
1280 * return post full url by id
1281 *
1282 * @param int|array $arg
1283 *
1284 * @return string $url
1285 * @since 2.0.10
1286 *
1287 */
1288 function get_url( $arg ) {
1289 if( wpforo_setting( 'board', 'url_structure' ) === 'short' ) return $this->get_short_url( $arg );
1290
1291 return $this->get_full_url( $arg );
1292 }
1293
1294 public function get_full_url( $arg ) {
1295 $cache = WPF()->cache->on( 'url' );
1296
1297 if( isset( $arg ) && ! is_array( $arg ) ) {
1298 $postid = wpforo_bigintval( $arg );
1299 $post = $this->get_post( $postid, false );
1300 } elseif( ! empty( $arg ) && isset( $arg['postid'] ) ) {
1301 $post = $arg;
1302 $postid = $post['postid'];
1303 }
1304
1305 if( ! empty( $post ) && is_array( $post ) && ! empty( $postid ) ) {
1306
1307 if( $cache ) {
1308 $post_url = WPF()->cache->get_item( $postid, 'url', 'post' );
1309 if( $post_url ) return $post_url;
1310 }
1311
1312 $forum_slug = ( wpfval( $post, 'forumid' ) ) ? wpforo_forum( $post['forumid'], 'slug' ) : $this->get_forumslug_byid( $postid );
1313 $topic_slug = ( wpfval( $post, 'topicid' ) ) ? wpforo_topic( $post['topicid'], 'slug' ) : $this->get_topicslug_byid( $postid );
1314
1315 $url = $forum_slug . '/' . $topic_slug;
1316
1317 $position = $this->get_position_in_topic( $post );
1318 $items_per_page = $this->get_option_items_per_page( WPF()->forum->get_layout( $post['forumid'] ) );
1319
1320 if( $position <= $items_per_page ) {
1321 $post_url = wpforo_home_url( $url ) . "#post-" . wpforo_bigintval( $postid );
1322 if( $cache ) WPF()->cache->create( 'item', [ 'post_' . intval( $postid ) => $post_url ], 'url' );
1323
1324 return $post_url;
1325 }
1326 if( $position && $items_per_page ) {
1327 $paged = (int) ceil( $position / $items_per_page );
1328 } else {
1329 $paged = 1;
1330 }
1331
1332 $post_url = wpforo_home_url( $url . "/" . wpforo_settings_get_slug( 'paged' ) . "/" . $paged ) . "#post-" . wpforo_bigintval( $postid );
1333 if( $cache ) WPF()->cache->create( 'item', [ 'post_' . intval( $postid ) => $post_url ], 'url' );
1334
1335 return $post_url;
1336 }
1337
1338 return wpforo_home_url();
1339 }
1340
1341 public function get_short_url( $arg ) {
1342 if( wpforo_is_id( $arg ) ) {
1343 $postid = $arg;
1344 } elseif( wpfkey( $arg, 'postid' ) ) {
1345 $postid = $arg['postid'];
1346 } elseif( wpfkey( $arg, 'first_postid' ) ) {
1347 $postid = $arg['first_postid'];
1348 } else {
1349 $postid = 0;
1350 }
1351
1352 if( $postid = wpforo_bigintval( $postid ) ) return wpforo_home_url( '/' . wpforo_settings_get_slug( 'postid' ) . '/' . $postid . '/' );
1353
1354 return wpforo_home_url();
1355 }
1356
1357 /**
1358 *
1359 * @param int $postid
1360 *
1361 * @return int
1362 * @since 1.0.0
1363 *
1364 */
1365 function is_answered( $postid ) {
1366 $is_answered = WPF()->db->get_var(
1367 WPF()->db->prepare(
1368 "SELECT is_answer FROM `" . WPF()->tables->posts . "` WHERE postid = %d",
1369 $postid
1370 )
1371 );
1372
1373 return $is_answered;
1374 }
1375
1376 function get_best_answer( $topicid, $extended = true ) {
1377 $best_answer = [];
1378 $args = [
1379 'topicid' => $topicid,
1380 'is_answer' => 1,
1381 'status' => 0,
1382 ];
1383 $best_answers = $this->get_posts( $args );
1384 if( ! empty( $best_answers ) && isset( $best_answers[0] ) ) {
1385 $best_answer = $best_answers[0];
1386 } else if( $extended ) {
1387 $best_answer = WPF()->db->get_row(
1388 WPF()->db->prepare(
1389 "SELECT * FROM `" . WPF()->tables->posts . "` WHERE `topicid` = %d AND `is_first_post` = 0 AND `parentid` = 0 AND `votes` > 0 ORDER BY `votes` DESC LIMIT 1",
1390 $topicid
1391 ),
1392 ARRAY_A
1393 );
1394 }
1395
1396 return $best_answer;
1397 }
1398
1399 function get_suggested_answers( $topicid, $count = 3 ) {
1400 $args = [
1401 'topicid' => $topicid,
1402 'is_first_post' => 0,
1403 'parentid' => 0,
1404 'is_answer' => 0,
1405 'orderby' => '`votes` DESC, `created` ASC',
1406 'row_count' => $count,
1407 'status' => 0,
1408 ];
1409
1410 return $this->get_posts( $args );
1411 }
1412
1413 function is_approved( $postid ) {
1414 $post = WPF()->db->get_var( "SELECT `status` FROM " . WPF()->tables->posts . " WHERE `postid` = " . intval( $postid ) );
1415 if( $post ) return false;
1416
1417 return true;
1418 }
1419
1420 function get_count( $args = [] ) {
1421 $sql = "SELECT SQL_NO_CACHE COUNT(*) FROM `" . WPF()->tables->posts . "`";
1422 if( $args && is_array( $args ) ) {
1423 $wheres = [];
1424 foreach( $args as $key => $value ) $wheres[] = "`$key` = '" . esc_sql( $value ) . "'";
1425 if( $wheres ) $sql .= " WHERE " . implode( ' AND ', $wheres );
1426 }
1427
1428 return WPF()->db->get_var( $sql );
1429 }
1430
1431 function unapproved_count() {
1432 return (int) WPF()->db->get_var( "SELECT COUNT(*) FROM `" . WPF()->tables->posts . "` WHERE `status` = 1" );
1433 }
1434
1435 function get_attachment_id( $filename ) {
1436 $attach_id = WPF()->db->get_var( "SELECT `post_id` FROM `" . WPF()->db->postmeta . "` WHERE `meta_key` = '_wp_attached_file' AND `meta_value` LIKE '%" . esc_sql( $filename ) . "' LIMIT 1" );
1437
1438 return $attach_id;
1439 }
1440
1441 function delete_attachments( $postid ) {
1442 $post = $this->get_post( $postid );
1443 if( isset( $post['body'] ) && $post['body'] ) {
1444 if( preg_match_all( '|\/wpforo\/default_attachments\/([^\s\"\]]+)|i', (string) $post['body'], $attachments, PREG_SET_ORDER ) ) {
1445 foreach( $attachments as $attachment ) {
1446 $filename = trim( $attachment[1] );
1447 $filename = str_replace( [ '../', './', '\\' ], '', $filename );
1448 $file = WPF()->folders['default_attachments']['dir'] . DIRECTORY_SEPARATOR . $filename;
1449 $real_file = realpath( $file );
1450 $real_dir = realpath( WPF()->folders['default_attachments']['dir'] );
1451 if( ! $real_file || ! $real_dir || strpos( $real_file, $real_dir . DIRECTORY_SEPARATOR ) !== 0 ) {
1452 continue;
1453 }
1454 if( file_exists( $file ) ) {
1455 $posts = WPF()->db->get_var( "SELECT COUNT(*) as posts FROM `" . WPF()->tables->posts . "` WHERE `body` LIKE '%" . esc_sql( $attachment[0] ) . "%'" );
1456 if( is_numeric( $posts ) && $posts == 1 ) {
1457 $attachmentid = $this->get_attachment_id( '/' . $filename );
1458 if( ! wp_delete_attachment( $attachmentid ) ) {
1459 @unlink( $file );
1460 }
1461 }
1462 }
1463 }
1464 }
1465 }
1466 }
1467
1468 public function set_status( $postid, $status ) {
1469 $postid = wpforo_bigintval( $postid );
1470 $status = intval( $status );
1471 if( ! $post = $this->get_post( $postid, false ) ) return false;
1472
1473 if( intval( $post['is_first_post'] ) ) WPF()->topic->set_status( $post['topicid'], $status );
1474
1475 if( $r = WPF()->db->update( WPF()->tables->posts, [ 'status' => $status ], [ 'postid' => $postid ], [ '%d' ], [ '%d' ] ) ) {
1476 if( $status ) {
1477 do_action( 'wpforo_post_unapprove', $post );
1478 } else {
1479 do_action( 'wpforo_post_approve', $post );
1480 }
1481
1482 do_action( 'wpforo_post_status_update', $post, $status );
1483 wpforo_clean_cache( 'post', $postid, $post );
1484 }
1485
1486 if( $r !== false ) {
1487 WPF()->notice->add( 'Done!', 'success' );
1488
1489 return true;
1490 }
1491
1492 WPF()->notice->add( 'error: Change Status action', 'error' );
1493
1494 return false;
1495 }
1496
1497 public function next_post( $postid, $topicid = 0 ) {
1498 $next_postid = 0;
1499 if( ! $topicid ) $topicid = wpforo_post( $postid, 'topicid' );
1500 if( $topicid ) {
1501 $next_postid = WPF()->db->get_var(
1502 "SELECT `postid` FROM `" . WPF()->tables->posts . "` WHERE `topicid` = " . intval( $topicid ) . " AND `postid` > " . intval(
1503 $postid
1504 ) . " AND `status` = 0 ORDER BY `created` ASC LIMIT 1"
1505 );
1506 }
1507
1508 return intval( $next_postid );
1509 }
1510
1511 public function get_liked_posts( $args, &$items_count ) {
1512 $default = [
1513 'userid' => null,
1514 'order' => 'DESC',
1515 'offset' => null,
1516 'row_count' => null,
1517 'where' => null,
1518 'var' => null,
1519 ];
1520
1521 $posts = [];
1522 if( ! wpfval( $args, 'userid' ) ) return [];
1523 $args = wpforo_parse_args( $args, $default );
1524 if( is_array( $args ) && ! empty( $args ) ) {
1525 $userid = $args['userid'];
1526 $order = $args['order'];
1527 $offset = $args['offset'];
1528 $row_count = $args['row_count'];
1529 $var = $args['var'];
1530 if( $row_count === 0 ) return [];
1531 $items_count = WPF()->reaction->get_count( [ 'userid' => $userid, 'type' => 'up' ] );
1532 $liked_postids = WPF()->reaction->get_reactions_col( 'postid', [ 'userid' => $userid, 'type' => 'up', 'orderby' => "`reactionid` $order", 'offset' => $offset, 'row_count' => $row_count ]
1533 );
1534 if( ! empty( $liked_postids ) ) {
1535 if( $var === 'postid' ) {
1536 return $liked_postids;
1537 } else {
1538 $liked_postids = implode( ',', $liked_postids );
1539 $post_args = [ 'include' => $liked_postids, 'status' => 0, 'private' => 0 ];
1540 $posts = $this->get_posts( $post_args );
1541 }
1542 }
1543 }
1544
1545 return $posts;
1546 }
1547
1548 public function get_unread_posts( $args, $limit = 10 ) {
1549
1550 $unread_posts = [];
1551
1552 //If the unread post logging is disabled return an empty array.
1553 if( ! wpforo_setting( 'logging', 'view_logging' ) ) return $unread_posts;
1554
1555 //If there is no information about last read post.
1556 //Max number recent posts to search unread posts in.
1557 $args['row_count'] = apply_filters( 'wpforo_max_number_of_unread_posts', 100 );
1558
1559 //Find the last unread postid, if so, add 'where' condition.
1560 $last_read_postid = WPF()->log->get_all_read( 'post' );
1561 if( $last_read_postid ) {
1562 $args['where'] = '`postid` > ' . intval( $last_read_postid );
1563 }
1564
1565 //Find unread posts based on last read postid's in topics
1566 $posts = $this->get_posts( $args );
1567 $read_topics = WPF()->log->get_read_topics();
1568 if( ! empty( $posts ) ) {
1569 if( ! empty( $read_topics ) ) {
1570 foreach( $posts as $key => $post ) {
1571 if( $key == $limit ) break;
1572 if( ! wpfkey( $post, 'topicid' ) && $post ) {
1573 $post_ids = explode( ',', $post );
1574 if( ! empty( $post_ids ) ) {
1575 foreach( $post_ids as $post_id ) {
1576 $topicid = wpforo_post( $post_id, 'topicid' );
1577 if( $topicid == wpfval( WPF()->current_object, 'topicid' ) ) continue;
1578 if( wpfkey( $read_topics, $topicid ) ) {
1579 $last_read_postid = $read_topics[ $topicid ];
1580 if( (int) $post_id > (int) $last_read_postid ) {
1581 $unread_posts[] = $post_id;
1582 }
1583 } else {
1584 $unread_posts[] = $post_id;
1585 }
1586 }
1587 }
1588 } elseif( wpfkey( $post, 'topicid' ) && wpfkey( $read_topics, $post['topicid'] ) ) {
1589 $last_read_postid = $read_topics[ $post['topicid'] ];
1590 if( (int) $post['postid'] > (int) $last_read_postid ) {
1591 $unread_posts[] = $post;
1592 }
1593 } else {
1594 $unread_posts[] = $post;
1595 }
1596 }
1597 } else {
1598 $unread_posts = $posts;
1599 }
1600 }
1601
1602 return $unread_posts;
1603 }
1604
1605 public function reset_fields( $type = null ) {
1606 if( is_null( $type ) ) {
1607 $this->fields = [];
1608 } else {
1609 unset( $this->fields, $type );
1610 }
1611 }
1612
1613 private function init_fields( $type, $forum = [] ) {
1614 if( $fields = (array) wpfval( $this->fields, $type ) ) return;
1615 $all_groupids = WPF()->usergroup->get_usergroups( 'groupid' );
1616 $all_groupids = array_map( 'intval', $all_groupids );
1617
1618 $fields = apply_filters( 'wpforo_post_before_init_fields', $fields );
1619
1620 $fields['title'] = [
1621 'fieldKey' => 'title',
1622 'type' => 'text',
1623 'isDefault' => 1,
1624 'isRemovable' => 0,
1625 'isRequired' => 1,
1626 'isEditable' => 1,
1627 'label' => wpforo_phrase( 'Topic Title', false ),
1628 'title' => wpforo_phrase( 'Title', false ),
1629 'placeholder' => wpforo_phrase( 'Enter title here', false ),
1630 'minLength' => 0,
1631 'maxLength' => 0,
1632 'faIcon' => 'fas fa-pen-alt',
1633 'name' => 'title',
1634 'cantBeInactive' => [ 'topic' ],
1635 'canEdit' => $all_groupids,
1636 'canView' => $all_groupids,
1637 'can' => '',
1638 'isSearchable' => 1,
1639 ];
1640
1641 /*$fields['slug'] = array(
1642 'fieldKey' => 'slug',
1643 'type' => 'text',
1644 'isDefault' => 1,
1645 'isRemovable' => 0,
1646 'isRequired' => 0,
1647 'isEditable' => 1,
1648 'label' => wpforo_phrase( 'Slug', false ),
1649 'title' => wpforo_phrase( 'Slug', false ),
1650 'placeholder' => wpforo_phrase( 'Slug', false ),
1651 'minLength' => 0,
1652 'maxLength' => 0,
1653 'faIcon' => 'fas fa-link',
1654 'name' => 'slug',
1655 'cantBeInactive' => array(),
1656 'canEdit' => $all_groupids,
1657 'canView' => $all_groupids,
1658 'can' => '',
1659 'isSearchable' => 1
1660 );*/
1661
1662 $fields['body'] = [
1663 'fieldKey' => 'body',
1664 'type' => 'tinymce',
1665 'isDefault' => 1,
1666 'isRemovable' => 0,
1667 'isRequired' => 1,
1668 'isEditable' => 1,
1669 'title' => wpforo_phrase( 'Body', false ),
1670 'placeholder' => wpforo_phrase( 'Body', false ),
1671 'minLength' => 0,
1672 'maxLength' => 0,
1673 'faIcon' => '',
1674 'name' => 'body',
1675 'cantBeInactive' => [ 'topic', 'post', 'comment', 'reply' ],
1676 'canEdit' => $all_groupids,
1677 'canView' => $all_groupids,
1678 'can' => '',
1679 'isSearchable' => 1,
1680 ];
1681
1682 $fields['name'] = [
1683 'fieldKey' => 'name',
1684 'type' => 'text',
1685 'isDefault' => 1,
1686 'isRemovable' => 0,
1687 'isRequired' => 0,
1688 'isEditable' => 1,
1689 'label' => wpforo_phrase( 'Author Name', false ),
1690 'title' => wpforo_phrase( 'Author Name', false ),
1691 'placeholder' => wpforo_phrase( 'Your name', false ),
1692 'minLength' => 0,
1693 'maxLength' => 0,
1694 'faIcon' => 'fas fa-id-card',
1695 'name' => 'name',
1696 'cantBeInactive' => [],
1697 'canEdit' => $all_groupids,
1698 'canView' => $all_groupids,
1699 'can' => '',
1700 'isSearchable' => 0,
1701 'isOnlyForGuests' => 1,
1702 ];
1703
1704 $fields['email'] = [
1705 'fieldKey' => 'email',
1706 'type' => 'text',
1707 'isDefault' => 1,
1708 'isRemovable' => 0,
1709 'isRequired' => 0,
1710 'isEditable' => 1,
1711 'label' => wpforo_phrase( 'Author Email', false ),
1712 'title' => wpforo_phrase( 'Author Email', false ),
1713 'placeholder' => wpforo_phrase( 'Your email', false ),
1714 'minLength' => 0,
1715 'maxLength' => 0,
1716 'faIcon' => 'fas fa-at',
1717 'name' => 'email',
1718 'cantBeInactive' => [],
1719 'canEdit' => $all_groupids,
1720 'canView' => $all_groupids,
1721 'can' => '',
1722 'isSearchable' => 0,
1723 'isOnlyForGuests' => 1,
1724 ];
1725
1726 /*$fields['tags'] = array(
1727 'fieldKey' => 'tags',
1728 'type' => 'text',
1729 'isDefault' => 1,
1730 'isRemovable' => 0,
1731 'isRequired' => 0,
1732 'isEditable' => 1,
1733 'label' => wpforo_phrase( 'Topic Tags', false ) . ' ' . wpforo_phrase( 'Separate tags using a comma', false ),
1734 'title' => wpforo_phrase( 'Tags', false ),
1735 'placeholder' => wpforo_phrase( 'Start typing tags here (maximum %d tags are allowed)...', false ),
1736 'minLength' => 0,
1737 'maxLength' => 0,
1738 'faIcon' => 'fas fa-tag',
1739 'name' => 'tags',
1740 'cantBeInactive' => array(),
1741 'canEdit' => $all_groupids,
1742 'canView' => $all_groupids,
1743 'can' => '',
1744 'isSearchable' => 1
1745 );
1746
1747 $fields['sticky'] = array(
1748 'fieldKey' => 'sticky',
1749 'type' => 'checkbox',
1750 'isDefault' => 1,
1751 'isRemovable' => 0,
1752 'isRequired' => 0,
1753 'isEditable' => 1,
1754 'label' => wpforo_phrase( 'Set Topic Sticky', false ),
1755 'title' => wpforo_phrase( 'Set Topic Sticky', false ),
1756 'placeholder' => wpforo_phrase( 'Set Topic Sticky', false ),
1757 'faIcon' => 'fas fa-exclamation',
1758 'name' => 'type',
1759 'values' => '1 => ' . wpforo_phrase( 'Set Topic Sticky', false ),
1760 'cantBeInactive' => array(),
1761 'canEdit' => $all_groupids,
1762 'canView' => $all_groupids,
1763 'can' => '',
1764 'isSearchable' => 1
1765 );
1766
1767 $fields['private'] = array(
1768 'fieldKey' => 'private',
1769 'type' => 'checkbox',
1770 'isDefault' => 1,
1771 'isRemovable' => 0,
1772 'isRequired' => 0,
1773 'isEditable' => 1,
1774 'label' => wpforo_phrase( 'Private Topic', false ),
1775 'title' => wpforo_phrase( 'Only Admins and Moderators can see your private topics.', false ),
1776 'placeholder' => wpforo_phrase( 'Private Topic', false ),
1777 'faIcon' => 'fas fa-eye-slash',
1778 'name' => 'private',
1779 'values' => '1 => ' . wpforo_phrase( 'Private Topic', false ),
1780 'cantBeInactive' => array(),
1781 'canEdit' => $all_groupids,
1782 'canView' => $all_groupids,
1783 'can' => '',
1784 'isSearchable' => 1
1785 );
1786
1787 $fields['subscribe'] = array(
1788 'fieldKey' => 'subscribe',
1789 'type' => 'checkbox',
1790 'isDefault' => 1,
1791 'isRemovable' => 0,
1792 'isRequired' => 0,
1793 'isEditable' => 1,
1794 'label' => wpforo_phrase( 'Subscribe to this topic', false ),
1795 'title' => wpforo_phrase( 'Subscribe to this topic', false ),
1796 'placeholder' => wpforo_phrase( 'Subscribe to this topic', false ),
1797 'faIcon' => 'fas fa-eye-slash',
1798 'name' => 'wpforo_topic_subs',
1799 'values' => '1 => ' . wpforo_phrase( 'Subscribe to this topic', false ),
1800 'cantBeInactive' => array(),
1801 'canEdit' => $all_groupids,
1802 'canView' => $all_groupids,
1803 'can' => '',
1804 'isSearchable' => 0
1805 );*/
1806
1807 $fields = (array) apply_filters( 'wpforo_post_after_init_fields', $fields, $type, $forum );
1808
1809 $this->fields[ $type ] = array_map( [ $this, 'fix_field' ], $fields );
1810 }
1811
1812 public function fix_field( $field ) {
1813 $field = array_merge( WPF()->form->default, (array) $field );
1814 if( is_scalar( $field['values'] ) ) $field['values'] = explode( "\n", $field['values'] );
1815
1816 return $field;
1817 }
1818
1819 public function get_fields( $only_defaults = false, $type = 'topic', $forum = [] ) {
1820 $this->init_fields( $type, $forum );
1821 $fields = (array) wpfval( $this->fields, $type );
1822 if( $only_defaults ) foreach( $fields as $k => $v ) if( ! wpfval( $v, 'isDefault' ) ) unset( $fields[ $k ] );
1823
1824 return $fields;
1825 }
1826
1827 public function get_field( $key, $type = 'topic', $forum = [] ) {
1828 if( is_string( $key ) ) {
1829 $this->init_fields( $type, $forum );
1830
1831 return (array) wpfval( $this->fields, $type, $key );
1832 } elseif( $this->is_field( $key ) ) {
1833 return $key;
1834 }
1835
1836 return [];
1837 }
1838
1839 public function is_field( $field ) {
1840 return wpfval( $field, 'fieldKey' ) && wpfval( $field, 'type' ) && wpfkey( $field, 'isDefault' );
1841 }
1842
1843 public function get_field_key( $field ) {
1844 return is_string( $field ) ? $field : (string) wpfval( $field, 'fieldKey' );
1845 }
1846
1847 public function fields_structure_full_array( $fields, &$used_fields = [], $type = 'topic', $forum = [] ) {
1848 if( is_string( $fields ) ) $fields = maybe_unserialize( $fields );
1849 $fs = [ [ [] ] ];
1850 if( ! is_array( $fields ) ) return $fs;
1851 foreach( $fields as $kr => $row ) {
1852 if( is_array( $row ) ) {
1853 foreach( $row as $kc => $cols ) {
1854 if( is_array( $cols ) ) {
1855 foreach( $cols as $kf => $field ) {
1856 $used_fields[] = $field_key = $this->get_field_key( $field );
1857 $fs[ $kr ][ $kc ][ $field_key ] = $this->get_field( $field, $type, $forum );
1858 }
1859 }
1860 }
1861 }
1862 }
1863
1864 return $fs;
1865 }
1866
1867 public function strip_guest_fields( $fields, $forum ) {
1868 $topic_fields_list = $this->get_topic_fields_list( false, $forum, true );
1869 if( ! in_array( 'name', $topic_fields_list, true ) ) {
1870 foreach( $fields as $rkey => $row ) {
1871 foreach( $row as $ckey => $col ) {
1872 foreach( $col as $fkey => $field ) {
1873 if( $field === 'name' ) unset( $fields[ $rkey ][ $ckey ][ $fkey ] );
1874 }
1875 }
1876 }
1877 }
1878 if( ! in_array( 'email', $topic_fields_list, true ) ) {
1879 foreach( $fields as $rkey => $row ) {
1880 foreach( $row as $ckey => $col ) {
1881 foreach( $col as $fkey => $field ) {
1882 if( $field === 'email' ) unset( $fields[ $rkey ][ $ckey ][ $fkey ] );
1883 }
1884 }
1885 }
1886 }
1887
1888 return $fields;
1889 }
1890
1891 // -- START -- get topic fields
1892 public function get_topic_fields_structure( $only_defaults = false, $forum = [], $guest = false ) {
1893 if( $guest ) {
1894 $fields[0][0][0] = 'name';
1895 $fields[0][1][0] = 'email';
1896 }
1897 $fields[][] = [ 'title', 'body' ];
1898 if( ! $only_defaults ) $fields = apply_filters( 'wpforo_get_topic_fields_structure', $fields, $forum, $guest );
1899
1900 return $fields;
1901 }
1902
1903 public function get_topic_fields( $forum, $values = [], $guest = false ) {
1904 $fields = $this->fields_structure_full_array( $this->get_topic_fields_structure( false, $forum, $guest ), $used_fields, 'topic', $forum );
1905 if( ! in_array( 'title', $used_fields, true ) ) $fields[][][] = $this->get_field( 'title', 'topic', $forum );
1906 if( ! in_array( 'body', $used_fields, true ) ) $fields[][][] = $this->get_field( 'body', 'topic', $forum );
1907
1908 /**
1909 * apply old options to fields
1910 */
1911 $values = wp_slash( $values );
1912 foreach( $fields as $kr => $row ) {
1913 foreach( $row as $kc => $cols ) {
1914 foreach( $cols as $kf => $field ) {
1915 if( $field ) {
1916 $field['value'] = wpfval( $values, $kf );
1917 switch( $kf ) {
1918 case 'body':
1919 if( $field['type'] === 'tinymce' ) {
1920 $field['wp_editor_settings'] = WPF()->tpl->editor_buttons( 'topic' );
1921 }
1922 $field['textareaid'] = uniqid( 'wpf_topic_body_' );
1923 $field['minLength'] = wpforo_setting( 'posting', 'topic_body_min_length' );
1924 $field['maxLength'] = wpforo_setting( 'posting', 'topic_body_max_length' );
1925 $field['form_type'] = 'topic';
1926 $field['meta'] = [ 'forum' => $forum, 'values' => $values ];
1927 break;
1928 case 'title':
1929 if( intval( $forum['layout'] ) === 3 ) $field['label'] = wpforo_phrase( 'Your question', false );
1930 $field['minLength'] = wpforo_setting( 'posting', 'topic_title_min_length' );
1931 $field['maxLength'] = wpforo_setting( 'posting', 'topic_title_max_length' ) ?: 250;
1932 break;
1933 }
1934 if( $field && intval( $field['isOnlyForGuests'] ) || in_array( $kf, [ 'name', 'email' ], true ) ) {
1935 if( $guest ) {
1936 if( ! $values ) {
1937 $g = WPF()->member->get_guest_cookies();
1938 if( $kf === 'name' ) {
1939 $field['value'] = $g['name'];
1940 } elseif( $kf === 'email' ) {
1941 $field['value'] = $g['email'];
1942 }
1943 }
1944 } else {
1945 $field = [];
1946 }
1947 }
1948 $field = apply_filters( 'wpforo_topic_field', $field, $forum, $values, $guest );
1949 }
1950
1951 if( $field ) {
1952 $fields[ $kr ][ $kc ][ $kf ] = $field;
1953 } else {
1954 unset( $fields[ $kr ][ $kc ][ $kf ] );
1955 if( ! $fields[ $kr ][ $kc ] ) {
1956 unset( $fields[ $kr ][ $kc ] );
1957 if( ! $fields[ $kr ] ) unset( $fields[ $kr ] );
1958 }
1959 }
1960
1961 }
1962 }
1963 }
1964
1965 return $fields;
1966 }
1967
1968 public function get_topic_fields_list( $only_defaults = false, $forum = [], $guest = false ) {
1969 $fields_list = [ 'title', 'body' ];
1970 $fields_structure = $this->get_topic_fields_structure( $only_defaults, $forum, $guest );
1971 foreach( $fields_structure as $r ) {
1972 foreach( $r as $c ) {
1973 foreach( $c as $f ) {
1974 $fields_list[] = $f;
1975 }
1976 }
1977 }
1978
1979 return array_values( array_unique( $fields_list ) );
1980 }
1981 // -- END -- get topic fields
1982
1983 // -- START -- get post fields
1984 public function get_post_fields_structure( $only_defaults = false, $forum = [], $guest = false ) {
1985 if( $guest ) {
1986 $fields[0][0][0] = 'name';
1987 $fields[0][1][0] = 'email';
1988 }
1989 $fields[][] = [ 'title', 'body' ];
1990 if( ! $only_defaults ) $fields = apply_filters( 'wpforo_get_post_fields_structure', $fields, $forum, $guest );
1991
1992 if( $guest ) $fields = $this->strip_guest_fields( $fields, $forum );
1993
1994 return $fields;
1995 }
1996
1997 public function get_post_fields( $forum, $topic, $values = [], $guest = false ) {
1998 $fields = $this->fields_structure_full_array( $this->get_post_fields_structure( false, $forum, $guest ), $used_fields, 'post', $forum );
1999 if( ! in_array( 'body', $used_fields, true ) ) $fields[][][] = $this->get_field( 'body', 'post', $forum );
2000
2001 /**
2002 * apply old options to fields
2003 */
2004 $values = wp_slash( $values );
2005 foreach( $fields as $kr => $row ) {
2006 foreach( $row as $kc => $cols ) {
2007 foreach( $cols as $kf => $field ) {
2008 if( $field ) {
2009 $field['value'] = wpfval( $values, $kf );
2010 switch( $kf ) {
2011 case 'body':
2012 if( $field['type'] === 'tinymce' ) {
2013 $field['wp_editor_settings'] = WPF()->tpl->editor_buttons( 'post' );
2014 }
2015 $field['textareaid'] = uniqid( 'wpf_post_body_' );
2016 $field['minLength'] = wpforo_setting( 'posting', 'post_body_min_length' );
2017 $field['maxLength'] = wpforo_setting( 'posting', 'post_body_max_length' );
2018 $field['form_type'] = 'post';
2019 $field['meta'] = [ 'forum' => $forum, 'topic' => $topic, 'values' => $values ];
2020 break;
2021 case 'title':
2022 $prefix_answer = wpforo_phrase( 'Answer to', false, 'default' );
2023 $prefix_re = wpforo_phrase( 'RE', false, 'default' );
2024 $prefix_patterns = [ $prefix_answer, $prefix_re ];
2025 $pattern = array_map( 'preg_quote', $prefix_patterns );
2026 $pattern = implode( '|', $pattern );
2027 $title = preg_replace( '#^\s*(?:' . $pattern . ')\s*: #isu', '', trim( (string) $field['value'] ), 1 );
2028 if( WPF()->forum->get_layout( $forum ) === 3 ) {
2029 $field['label'] = wpforo_phrase( 'Your question', false );
2030 if( $title ) $field['value'] = $prefix_answer . ': ' . $title;
2031 } else {
2032 $field['label'] = wpforo_phrase( 'Title', false );
2033 if( $title ) $field['value'] = $prefix_re . ': ' . $title;
2034 }
2035 break;
2036 }
2037 if( $field && intval( $field['isOnlyForGuests'] ) || in_array( $kf, [ 'name', 'email' ], true ) ) {
2038 if( $guest ) {
2039 if( ! $values || ( count( $values ) === 1 && wpfkey( $values, 'title' ) ) ) {
2040 $g = WPF()->member->get_guest_cookies();
2041 if( $kf === 'name' ) {
2042 $field['value'] = $g['name'];
2043 } elseif( $kf === 'email' ) {
2044 $field['value'] = $g['email'];
2045 }
2046 }
2047 } else {
2048 $field = [];
2049 }
2050 }
2051 $fields[ $kr ][ $kc ][ $kf ] = apply_filters( 'wpforo_post_field', $field, $forum, $topic, $values, $guest );
2052 }
2053 }
2054 }
2055 }
2056
2057 return $fields;
2058 }
2059
2060 public function get_post_fields_list( $only_defaults = false, $forum = [], $guest = false ) {
2061 $fields_list = [ 'body' ];
2062 $fields_structure = $this->get_post_fields_structure( $only_defaults, $forum, $guest );
2063 foreach( $fields_structure as $r ) {
2064 foreach( $r as $c ) {
2065 foreach( $c as $f ) {
2066 $fields_list[] = $f;
2067 }
2068 }
2069 }
2070
2071 return array_values( array_unique( $fields_list ) );
2072 }
2073
2074 // -- END -- get post fields
2075
2076 public function get_search_fields( $values ) {
2077 $values = (array) $values;
2078 $values = wp_slash( $values );
2079
2080 $topic_fields = WPF()->post->get_topic_fields_list( false, [], ! WPF()->current_userid );
2081 $topic_fields = array_flip( $topic_fields );
2082 $fields = $this->get_fields();
2083 $fields = array_intersect_key( $fields, $topic_fields );
2084
2085 $search_fields = [];
2086 foreach( $fields as $kf => $field ) {
2087 if( ! $field['isDefault'] && (int) wpfval( $field, 'isSearchable' ) && ! (int) wpfval( $field, 'isOnlyForGuests' ) && wpfval( $field, 'type' ) !== 'file' ) {
2088 $field['value'] = wpfval( $values, $kf );
2089 if( in_array( $field['type'], [ 'text', 'textarea', 'email', 'url' ], true ) ) $field['type'] = 'search';
2090 $field['isRequired'] = 0;
2091 $search_fields[0][0][ $field['fieldKey'] ] = $field;
2092 }
2093 }
2094
2095 return $search_fields;
2096 }
2097
2098 public function print_custom_fields( $content, $post ) {
2099 if( (int) wpfval( $post, 'is_first_post' ) && ( $postmetas = WPF()->postmeta->get_postmeta( $post['postid'], '', true ) ) ) {
2100 $forum = WPF()->forum->get_forum( $post['forumid'] );
2101 $fields = WPF()->post->get_topic_fields_list( false, $forum, ! WPF()->current_userid );
2102 $htmls = [ 'before' => [], 'after' => [] ];
2103 foreach( $fields as $field ) {
2104 $display = apply_filters( 'wpforo_topic_fields_filter', true, $field, $post );
2105 if( $display && $postmeta = wpfval( $postmetas, $field ) ) {
2106 $field = WPF()->post->get_field( $field, 'topic', $forum );
2107 if( ! (int) wpfval( $field, 'isDefault' ) ) {
2108 if( (int) $field['isSearchable'] && in_array( $field['type'], [ 'text', 'number', 'select', 'radio', 'checkbox', 'autocomplete' ], true ) ) {
2109 if( is_scalar( $postmeta ) ) {
2110 $postmeta = wpforo_post_search_link( $field['name'], $postmeta );
2111 } else {
2112 $postmeta = array_map( function( $field_value ) use ( $field ) {
2113 return wpforo_post_search_link( $field['name'], $field_value );
2114 }, $postmeta );
2115 }
2116 }
2117 $field['value'] = $postmeta;
2118 $field = WPF()->form->prepare_values( WPF()->form->esc_field( $field ) );
2119 $printTheValue = wpfval( $field, 'printTheValue' ) === 'before' ? 'before' : 'after';
2120 $htmls[ $printTheValue ][] = sprintf(
2121 '<div class="wpf-topic-field"><div class="wpf-topic-field-label"> <i class="%1$s"></i> %2$s</div><div class="wpf-topic-field-value">%3$s</div></div>',
2122 wpfval( $field, 'faIcon' ),
2123 wpfval( $field, 'label' ),
2124 wpfval( $field, 'value' )
2125 );
2126 }
2127 }
2128 }
2129
2130 $content = sprintf(
2131 '%1$s%2$s%3$s',
2132 ( $htmls['before'] ? sprintf(
2133 '<div class="wpf-topic-fields">%1$s%2$s</div>',
2134 apply_filters( 'wpforo_topic_fields_before', '', $post ),
2135 implode( '', $htmls['before'] )
2136 ) : '' ),
2137 $content,
2138 ( $htmls['after'] ? sprintf(
2139 '<div class="wpf-topic-fields">%1$s%2$s</div>',
2140 apply_filters( 'wpforo_topic_fields_before', '', $post ),
2141 implode( '', $htmls['after'] )
2142 ) : '' )
2143 );
2144 }
2145
2146 return $content;
2147 }
2148
2149 /**
2150 * @param int $postid
2151 * @param int $reaction
2152 * @param int $userid
2153 *
2154 * @return bool
2155 * @since 2.0.0
2156 *
2157 */
2158 public function has_user_voted_post( $postid, $reaction = null, $userid = 0 ) {
2159 $reaction = in_array( $reaction, [ 1, - 1 ], true ) ? $reaction : null;
2160 $user_post_vote = WPF()->reaction->get_user_reaction_reaction( $postid, $userid );
2161 if( is_null( $reaction ) ) return (bool) $user_post_vote;
2162
2163 return $reaction === $user_post_vote;
2164 }
2165
2166 /**
2167 * @param int $postid
2168 * @param int $reaction
2169 * @param int $userid
2170 *
2171 * @return int
2172 * @since 2.0.0
2173 *
2174 */
2175 public function clear_user_post_votes( $postid, $reaction = null, $userid = 0 ) {
2176 $reaction = in_array( $reaction, [ 1, - 1 ], true ) ? $reaction : null;
2177 if( ! $userid = wpforo_bigintval( $userid ) ) $userid = WPF()->current_userid;
2178 WPF()->reaction->delete( [ 'postid' => $postid, 'userid' => $userid, 'reaction_include' => $reaction ] );
2179
2180 return WPF()->reaction->get_sum( $postid );
2181 }
2182
2183 /**
2184 * @param int $postid
2185 * @param int $reaction
2186 * @param int $userid
2187 *
2188 * @return false|int
2189 * @since 2.0.0
2190 *
2191 */
2192 public function vote_post( $postid, $reaction, $userid = 0 ) {
2193 if( in_array( ( $reaction = intval( $reaction ) ), [ 1, - 1 ], true ) ) {
2194 $postid = wpforo_bigintval( $postid );
2195 if( ! $userid = wpforo_bigintval( $userid ) ) $userid = WPF()->current_userid;
2196
2197 if( $post = $this->get_post( $postid, false ) ) {
2198 if( false !== WPF()->reaction->add(
2199 [ 'userid' => $userid, 'postid' => $postid, 'post_userid' => $post['userid'], 'reaction' => $reaction, 'type' => ( $reaction === 1 ? 'up' : 'down' ) ]
2200 ) ) {
2201 $votes = WPF()->reaction->get_sum( $postid );
2202
2203 do_action( 'wpforo_vote', $reaction, $post, $userid );
2204 WPF()->notice->add( 'Successfully voted', 'success' );
2205
2206 return $votes;
2207 }
2208 }
2209 }
2210
2211 return false;
2212 }
2213
2214 /**
2215 * @param int $postid
2216 *
2217 * @return int
2218 * @since 2.0.0
2219 *
2220 */
2221 public function rebuild_votes_stats( $postid ) {
2222 if( $postid = wpforo_bigintval( $postid ) ) {
2223 $votes = WPF()->reaction->get_sum( $postid );
2224
2225 $tu = WPF()->db->update( WPF()->tables->topics, [ 'votes' => $votes ], [ 'first_postid' => $postid ], [ '%d' ], [ '%d' ] );
2226
2227 $pu = WPF()->db->update( WPF()->tables->posts, [ 'votes' => $votes ], [ 'postid' => $postid ], [ '%d' ], [ '%d' ] );
2228
2229 if( $tu || $pu ) wpforo_clean_cache( 'post', $postid );
2230
2231 return $votes;
2232 }
2233
2234 return 0;
2235 }
2236
2237 public function get_userids_for_forum( $forum, $childs = true, $check_permission = true ) {
2238 if( wpforo_is_id( $forum ) ) {
2239 // The old and very heavy way to get forum userids, this way does
2240 // lots of recursive SQL queries on each page load. There is no cache for this process.
2241 // The solution has been added in 2.2.4 version by collecting all x participants
2242 // in last_userid field, which were changed from INT to VARCHAR. This solution
2243 // brings userids with forum object without dozens of additional SQLs
2244 if( $childs ) {
2245 $forumids = WPF()->forum->get_forumid_and_childids( $forum, $check_permission );
2246 } else {
2247 $forumids = [ $forum ];
2248 }
2249 $sql = "SELECT DISTINCT `userid` FROM `" . WPF()->tables->posts . "` WHERE `forumid` IN( " . implode( ',', $forumids ) . " ) ORDER BY `created` DESC";
2250
2251 return array_map( 'wpforo_bigintval', WPF()->db->get_col( $sql ) );
2252 }
2253 // The new high performance way to get forum participants
2254 $last_userids = wpfval( $forum, 'last_userid' );
2255 $last_userids = empty( $last_userids ) ? [] : explode( ',', $last_userids );
2256
2257 return array_map( 'wpforo_bigintval', $last_userids );
2258 }
2259
2260 public function get_userids_for_topic( $topicid, $first_post = false, $limit = 0, $offset = 0 ) {
2261 $sql = "SELECT DISTINCT `userid`
2262 FROM `" . WPF()->tables->posts . "`
2263 WHERE `topicid` = %d";
2264
2265 if( ! is_null( $first_post ) ) {
2266 $sql .= " AND `is_first_post` = " . (int) (bool) $first_post;
2267 }
2268
2269 $sql .= " ORDER BY `created` DESC";
2270
2271 if( $limit = intval( $limit ) ) {
2272 $offset = intval( $offset );
2273 $sql .= " LIMIT $offset,$limit";
2274 }
2275 $sql = WPF()->db->prepare( $sql, $topicid );
2276
2277 return array_map( 'wpforo_bigintval', WPF()->db->get_col( $sql ) );
2278 }
2279
2280 public function get_users_count_for_topic( $topicid ) {
2281 $sql = "SELECT COUNT( DISTINCT `userid` ) AS quantity
2282 FROM `" . WPF()->tables->posts . "`
2283 WHERE `topicid` = %d";
2284 $sql = WPF()->db->prepare( $sql, $topicid );
2285
2286 return (int) WPF()->db->get_var( $sql );
2287 }
2288
2289 public function get_users_stats_for_topic( $topicid, $limit = 0 ) {
2290 $sql = "SELECT `userid`, COUNT(`userid`) AS `posts`
2291 FROM `" . WPF()->tables->posts . "`
2292 WHERE `topicid` = %d
2293 GROUP BY `userid`
2294 ORDER BY `posts` DESC";
2295 $sql = WPF()->db->prepare( $sql, $topicid );
2296 if( $limit = intval( $limit ) ) $sql .= " LIMIT $limit";
2297
2298 $users_stats_for_topic = array_map( function( $row ) {
2299 $row['userid'] = wpforo_bigintval( $row['userid'] );
2300 $row['posts'] = intval( $row['posts'] );
2301
2302 return $row;
2303 }, (array) WPF()->db->get_results( $sql, ARRAY_A ) );
2304
2305 return apply_filters( 'wpforo_get_users_stats_for_topic', $users_stats_for_topic, $topicid, $limit );
2306 }
2307
2308 public function get_first_level_replies( $topicid, $row_count = 0, $offset = 0 ) {
2309 $sql = "SELECT *
2310 FROM `" . WPF()->tables->posts . "`
2311 WHERE `topicid` = %d
2312 AND `parentid` = 0
2313 AND `is_first_post` = 0
2314 ORDER BY `created` ASC";
2315 $sql = WPF()->db->prepare( $sql, $topicid );
2316 if( $row_count = intval( $row_count ) ) $sql .= WPF()->db->prepare( " LIMIT %d,%d", $offset, $row_count );
2317
2318 return (array) WPF()->db->get_results( $sql, ARRAY_A );
2319 }
2320
2321 public function after_delete_user( $userid, $reassign ) {
2322 if( $boardids = WPF()->board->get_active_boardids() ) {
2323 if( is_null( $reassign ) ) {
2324
2325 foreach( $boardids as $boardid ) {
2326 WPF()->change_board( $boardid );
2327 if( $postids = WPF()->db->get_col( WPF()->db->prepare( "SELECT `postid` FROM `" . WPF()->tables->posts . "` WHERE userid = %d", $userid ) ) ) {
2328 foreach( $postids as $postid ) $this->delete( $postid, true, true, $exclude, false );
2329 }
2330 }
2331
2332 } else {
2333 if( $reassign = wpforo_bigintval( $reassign ) ) {
2334 $data = [ 'userid' => $reassign ];
2335 $format = [ '%d' ];
2336 } else {
2337 $data = [
2338 'userid' => 0,
2339 'name' => wpforo_phrase( 'Anonymous', false ) . " " . $userid,
2340 'email' => "anonymous_$userid@example.com",
2341 ];
2342 $format = [ '%d', '%s', '%s' ];
2343 }
2344
2345 foreach( $boardids as $boardid ) {
2346 WPF()->change_board( $boardid );
2347 WPF()->db->update( WPF()->tables->posts, $data, [ 'userid' => $userid ], $format, [ '%d' ] );
2348 }
2349
2350 }
2351 }
2352 }
2353
2354 public function before_delete_reaction( $args, $operator ) {
2355 $this->touched_postids = array_merge( $this->touched_postids, WPF()->reaction->_get_reactions_col( 'postid', $args, $operator ) );
2356 }
2357
2358 public function after_delete_reaction() {
2359 foreach( array_unique( array_filter( $this->touched_postids ) ) as $postid ) $this->rebuild_votes_stats( $postid );
2360 }
2361
2362 public function after_add_reaction( $reaction ) {
2363 $this->rebuild_votes_stats( wpfval( $reaction, 'postid' ) );
2364 }
2365 }
2366