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

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

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