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

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