PluginProbe
wpForo Forum / 3.0.8
wpForo Forum v3.0.8
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 / classes / Posts.php

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

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