PluginProbe
wpForo Forum / 2.4.2
wpForo Forum v2.4.2
3.1.5 3.1.4 3.1.2 3.1.1 3.1.0 3.0.9 3.0.8 3.0.7 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 1.4.11 1.4.12 1.4.13 1.4.2 All 137 releases
wpforo / classes / Posts.php

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

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