PluginProbe
wpForo Forum / 2.4.11
wpForo Forum v2.4.11
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 2.4.11, at classes/Posts.php

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