PluginProbe
wpForo Forum / 3.0.7
wpForo Forum v3.0.7
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 / Forums.php

Forums.php in wpForo Forum 3.0.7, at classes/Forums.php

1,459 lines 54.8 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 Forums {
9 static $cache = [ 'forums' => [], 'forum' => [], 'item' => [] ];
10
11 public static function _get_forumids( $table, $layout = null ) {
12 $sql = "SELECT `forumid` FROM `$table` WHERE `status` = 1";
13 if( ! is_null( $layout ) ) $sql .= WPF()->db->prepare( " AND `layout` = %d", $layout );
14
15 return WPF()->db->get_col( $sql );
16 }
17
18 public static function get_forumids( $table, $layout = null ) {
19 return wpforo_ram_get( [ self::class, '_get_forumids' ], $table, $layout );
20 }
21
22 public function __construct() {
23 $this->init_hooks();
24 }
25
26 public function reset() {
27 self::$cache = [ 'forums' => [], 'forum' => [], 'item' => [] ];
28 }
29
30 private function init_hooks() {
31 add_action( 'wpforo_after_add_usergroup', [ $this, 'after_add_usergroup' ] );
32
33 add_action( 'wpforo_after_merge_forum', [ $this, 'after_merge_forum' ], 10, 2 );
34
35 add_action( 'wpforo_after_add_topic', [ $this, 'after_add_topic' ] );
36 add_action( 'wpforo_after_delete_topic', [ $this, 'after_delete_topic' ] );
37 add_action( 'wpforo_topic_status_update', [ $this, 'after_topic_status_update' ] );
38 add_action( 'wpforo_topic_private_update', [ $this, 'topic_private_update' ] );
39 add_action( 'wpforo_after_merge_topic', [ $this, 'after_merge_topic' ], 10, 2 );
40 add_action( 'wpforo_after_move_topic', [ $this, 'after_move_topic' ], 10, 2 );
41
42 add_action( 'wpforo_after_add_post', [ $this, 'after_add_post' ], 10, 3 );
43 add_action( 'wpforo_after_delete_post', [ $this, 'after_delete_post' ] );
44 add_action( 'wpforo_post_status_update', [ $this, 'after_post_status_update' ] );
45 }
46
47 public function get_cache( $var ) {
48 if( isset( self::$cache[ $var ] ) ) return self::$cache[ $var ];
49 }
50
51 private function unique_slug( $slug, $parentid = 0, $forumid = 0 ) {
52 $new_slug = wpforo_text( $slug, 250, false );
53 $forumid = intval( $forumid );
54 $i = 2;
55 while( ! WPF()->can_use_this_slug( $new_slug ) || WPF()->db->get_var(
56 "SELECT `forumid` FROM " . WPF()->tables->forums . " WHERE `slug` = '" . esc_sql( $new_slug ) . "'" . ( $forumid ? ' AND `forumid` != ' . intval( $forumid ) : '' )
57 ) ) {
58 if( ! isset( $parent_slug ) && $parentid = intval( $parentid ) ) {
59 $parent_slug = WPF()->db->get_var( "SELECT `slug` FROM " . WPF()->tables->forums . " WHERE `forumid` = " . intval( $parentid ) );
60 $new_slug = $parent_slug . "-" . wpforo_text( $slug, 250, false );
61 } else {
62 $new_slug = wpforo_text( $slug, 250, false ) . '-' . $i ++;
63 }
64 }
65
66 return $new_slug;
67 }
68
69 public function copy( $forumid ) {
70 if( ( $forumid = intval( $forumid ) ) && ( $forum = $this->get_forum( $forumid ) ) ) {
71 $forum['title'] .= ' - COPY';
72 $forum['last_topicid'] = $forum['last_postid'] = $forum['last_userid'] = $forum['topics'] = $forum['posts'] = 0;
73 $forum['last_post_date'] = '0000-00-00 00:00:00';
74 $forum['permission'] = unserialize( $forum['permissions'] );
75 unset( $forum['forumid'], $forum['url'], $forum['cover_url'], $forum['permissions'] );
76
77 $forum['forumid'] = $this->add( $forum, false );
78
79 do_action( 'wpforo_after_copy_forum', $forum, $forumid );
80
81 return $forum['forumid'];
82 }
83
84 WPF()->notice->add( 'Can\'t copy forum', 'error' );
85
86 return false;
87 }
88
89 public function add( $args = [], $checkperm = true ) {
90 if( $checkperm && ! WPF()->usergroup->can_manage_forum() ) {
91 WPF()->notice->add( 'Permission denied for add forum', 'error' );
92
93 return false;
94 }
95
96 if( empty( $args ) && empty( $_REQUEST['forum'] ) ) return false;
97 if( empty( $args ) && ! empty( $_REQUEST['forum'] ) ) $args = $_REQUEST['forum'];
98
99 $args['title'] = sanitize_text_field( stripslashes( (string) wpfval( $args, 'title' ) ) );
100 if( ! $args['title'] ) {
101 WPF()->notice->add( 'Please insert required fields!', 'error' );
102
103 return false;
104 }
105
106 $args['title'] = wpforo_text( $args['title'], 250, false );
107 $args['parentid'] = (int) wpfval( $args, 'parentid' );
108
109 $args['slug'] = ( $ss = trim( sanitize_title( (string) wpfval( $args, 'slug' ) ) ) ) ? $ss : ( ( $st = trim( sanitize_title( $args['title'] ) ) ) ? $st : md5( time() ) );
110 $args['slug'] = $this->unique_slug( $args['slug'], $args['parentid'] );
111
112 $args['description'] = trim( wpforo_kses( stripslashes( (string) wpfval( $args, 'description' ) ) ) );
113
114 $group_access_relation = WPF()->usergroup->get_usergroup_access_relation();
115 $args['permission'] = ( (array) wpfval( $args, 'permission' ) ) + $group_access_relation;
116 $args['permission'] = serialize( array_map( 'sanitize_text_field', $args['permission'] ) );
117
118 $args['meta_key'] = sanitize_text_field( stripslashes( (string) wpfval( $args, 'meta_key' ) ) );
119 $args['meta_desc'] = sanitize_text_field( stripslashes( (string) wpfval( $args, 'meta_desc' ) ) );
120 $args['icon'] = sanitize_text_field( (string) wpfval( $args, 'icon' ) );
121 $args['cover'] = wpforo_bigintval( wpfval( $args, 'cover' ) );
122 $args['cover_height'] = intval( wpfval( $args, 'cover_height' ) );
123 if( ! $args['cover_height'] ) $args['cover_height'] = 150;
124 $args['topics'] = (int) wpfval( $args, 'topics' );
125 $args['posts'] = (int) wpfval( $args, 'posts' );
126 $args['order'] = (int) wpfval( $args, 'order' );
127
128 $args['status'] = (int) wpfval( $args, 'status' );
129 if( ! $args['status'] ) $args['status'] = 1;
130
131 $args['color'] = sanitize_text_field( stripslashes( (string) wpfval( $args, 'color' ) ) );
132 if( ! $args['color'] ) $args['color'] = '#888888';
133
134 $args['is_cat'] = (int) wpfval( $args, 'is_cat' );
135 if( ! $args['parentid'] ) $args['is_cat'] = 1;
136
137 $args['layout'] = (int) wpfval( $args, 'layout' );
138 if( $args['parentid'] ) {
139 $layout = (int) WPF()->db->get_var( "SELECT `layout` FROM `" . WPF()->tables->forums . "` WHERE `forumid` = " . $args['parentid'] );
140 if( $layout ) $args['layout'] = $layout;
141 }
142 if( ! $args['layout'] ) $args['layout'] = 1;
143
144 // Forum type: 'forum' (default) or 'ticket_forum' (all topics private)
145 // Only non-category forums can be ticket forums
146 $args['type'] = sanitize_text_field( (string) wpfval( $args, 'type' ) );
147 if( ! in_array( $args['type'], [ 'forum', 'ticket_forum' ], true ) ) {
148 $args['type'] = 'forum';
149 }
150 // Categories cannot be ticket forums
151 if( $args['is_cat'] ) {
152 $args['type'] = 'forum';
153 }
154
155 $args = apply_filters( 'wpforo_before_add_forum', $args, $checkperm );
156 if( ! $args ) return false;
157
158 if( WPF()->db->insert(
159 WPF()->tables->forums,
160 [
161 'title' => $args['title'],
162 'slug' => $args['slug'],
163 'description' => $args['description'],
164 'parentid' => $args['parentid'],
165 'icon' => $args['icon'],
166 'cover' => $args['cover'],
167 'cover_height' => $args['cover_height'],
168 'topics' => $args['topics'],
169 'posts' => $args['posts'],
170 'permissions' => $args['permission'],
171 'meta_key' => $args['meta_key'],
172 'meta_desc' => $args['meta_desc'],
173 'status' => $args['status'],
174 'is_cat' => $args['is_cat'],
175 'layout' => $args['layout'],
176 'order' => $args['order'],
177 'color' => $args['color'],
178 'type' => $args['type'],
179 ], [
180 '%s',
181 '%s',
182 '%s',
183 '%d',
184 '%s',
185 '%d',
186 '%d',
187 '%d',
188 '%d',
189 '%s',
190 '%s',
191 '%s',
192 '%d',
193 '%d',
194 '%d',
195 '%d',
196 '%s',
197 '%s',
198 ]
199 ) ) {
200 $args['forumid'] = WPF()->db->insert_id;
201
202 do_action( 'wpforo_after_add_forum', $args, $checkperm );
203
204 $this->delete_tree_cache();
205 wpforo_clean_cache();
206 WPF()->notice->add( 'Your forum successfully added', 'success' );
207
208 return $args['forumid'];
209 }
210
211 WPF()->notice->add( 'Can\'t add forum', 'error' );
212
213 return false;
214 }
215
216 public function edit( $args = [], $checkperm = true ) {
217 if( $checkperm && ! WPF()->usergroup->can_manage_forum() ) {
218 WPF()->notice->add( 'Permission denied for edit forum', 'error' );
219
220 return false;
221 }
222
223 if( empty( $args ) && empty( $_REQUEST['forum'] ) ) return false;
224 if( empty( $args ) && ! empty( $_REQUEST['forum'] ) ) $args = $_REQUEST['forum'];
225 if( ! isset( $args['forumid'] ) && isset( $_GET['id'] ) ) $args['forumid'] = $_GET['id'];
226
227 $args['forumid'] = (int) wpfval( $args, 'forumid' );
228 if( ! $forum = $this->get_forum( $args['forumid'] ) ) {
229 WPF()->notice->add( 'Forum update error', 'error' );
230
231 return false;
232 }
233
234 $args['title'] = sanitize_text_field( stripslashes( (string) wpfval( $args, 'title' ) ) );
235 if( ! $args['title'] ) {
236 WPF()->notice->add( 'Please insert required fields!', 'error' );
237
238 return false;
239 }
240
241 $args['title'] = wpforo_text( $args['title'], 250, false );
242 $args['parentid'] = (int) wpfval( $args, 'parentid' );
243 if( $args['forumid'] === $args['parentid'] ) $args['parentid'] = (int) $forum['parentid'];
244
245 $args['slug'] = ( $ss = trim( sanitize_title( (string) wpfval( $args, 'slug' ) ) ) ) ? $ss : ( ( $st = trim( sanitize_title( $args['title'] ) ) ) ? $st : md5( time() ) );
246 $args['slug'] = $this->unique_slug( $args['slug'], $args['parentid'], $args['forumid'] );
247
248 $args['description'] = wpforo_kses( stripslashes( (string) wpfval( $args, 'description' ) ) );
249
250 $group_access_relation = WPF()->usergroup->get_usergroup_access_relation();
251 $args['permission'] = ( (array) wpfval( $args, 'permission' ) ) + $group_access_relation;
252 $args['permission'] = serialize( array_map( 'sanitize_text_field', $args['permission'] ) );
253
254 $args['meta_key'] = sanitize_text_field( stripslashes( (string) wpfval( $args, 'meta_key' ) ) );
255 $args['meta_desc'] = sanitize_text_field( stripslashes( (string) wpfval( $args, 'meta_desc' ) ) );
256 $args['icon'] = sanitize_text_field( wpfval( $args, 'icon' ) );
257 $args['cover'] = wpforo_bigintval( wpfval( $args, 'cover' ) );
258 $args['cover_height'] = intval( wpfval( $args, 'cover_height' ) );
259 if( ! $args['cover_height'] ) $args['cover_height'] = 150;
260 $args['topics'] = (int) wpfval( $args, 'topics' );
261 $args['posts'] = (int) wpfval( $args, 'posts' );
262 $args['order'] = (int) wpfval( $args, 'order' );
263
264 $args['status'] = (int) wpfval( $args, 'status' );
265 if( ! $args['status'] ) $args['status'] = 1;
266
267 $args['color'] = sanitize_text_field( stripslashes( (string) wpfval( $args, 'color' ) ) );
268 if( ! $args['color'] ) $args['color'] = '#888888';
269
270 $args['is_cat'] = (int) wpfval( $args, 'is_cat' );
271 if( ! $args['parentid'] ) $args['is_cat'] = 1;
272
273 $args['layout'] = (int) wpfval( $args, 'layout' );
274 if( $args['parentid'] ) {
275 $layout = (int) WPF()->db->get_var( "SELECT `layout` FROM `" . WPF()->tables->forums . "` WHERE `forumid` = " . $args['parentid'] );
276 if( $layout ) $args['layout'] = $layout;
277 }
278 if( ! $args['layout'] ) $args['layout'] = 1;
279
280 // Forum type: 'forum' (default) or 'ticket_forum' (all topics private)
281 // Only non-category forums can be ticket forums
282 $args['type'] = sanitize_text_field( (string) wpfval( $args, 'type' ) );
283 if( ! in_array( $args['type'], [ 'forum', 'ticket_forum' ], true ) ) {
284 $args['type'] = 'forum';
285 }
286 // Categories cannot be ticket forums
287 if( $args['is_cat'] ) {
288 $args['type'] = 'forum';
289 }
290
291 $args = apply_filters( 'wpforo_before_edit_forum', $args, $forum, $checkperm );
292 if( ! $args ) return false;
293
294 if( false !== WPF()->db->update(
295 WPF()->tables->forums,
296 [
297 'title' => $args['title'],
298 'slug' => $args['slug'],
299 'description' => $args['description'],
300 'parentid' => $args['parentid'],
301 'icon' => $args['icon'],
302 'cover' => $args['cover'],
303 'cover_height' => $args['cover_height'],
304 'permissions' => $args['permission'],
305 'meta_key' => $args['meta_key'],
306 'meta_desc' => $args['meta_desc'],
307 'status' => $args['status'],
308 'is_cat' => $args['is_cat'],
309 'layout' => $args['layout'],
310 'color' => $args['color'],
311 'type' => $args['type'],
312 ],
313 [ 'forumid' => $args['forumid'] ],
314 [ '%s', '%s', '%s', '%d', '%s', '%d', '%d', '%s', '%s', '%s', '%d', '%d', '%d', '%s', '%s' ], [ '%d' ]
315 ) ) {
316 if( $childs = $this->get_childs( $args['forumid'] ) ) {
317 $sql = "UPDATE `" . WPF()->tables->forums . "` SET `layout` = " . $args['layout'] . " WHERE `forumid` IN(" . implode( ',', $childs ) . ")";
318 WPF()->db->query( $sql );
319 }
320
321 do_action( 'wpforo_after_edit_forum', $args, $forum, $checkperm );
322
323 $this->delete_tree_cache();
324 wpforo_clean_cache();
325 WPF()->notice->add( 'Forum successfully updated', 'success' );
326
327 return $args['forumid'];
328 }
329
330 WPF()->notice->add( 'Forum update error', 'error' );
331
332 return false;
333 }
334
335 public function delete( $forumid = 0, $checkperm = true ) {
336 $forumid = intval( $forumid );
337 if( ! $forumid && isset( $_REQUEST['id'] ) ) $forumid = intval( $_REQUEST['id'] );
338
339 if( $checkperm && ! WPF()->usergroup->can_manage_forum() ) {
340 WPF()->notice->add( 'Permission denied for delete forum', 'error' );
341
342 return false;
343 }
344
345 $forumids = $this->get_childs( $forumid );
346 $forumids[] = $forumid;
347 foreach( $forumids as $forumid ) $this->__delete( $forumid );
348
349 $this->delete_tree_cache();
350 wpforo_clean_cache();
351 WPF()->notice->add( 'Your forum successfully deleted', 'success' );
352
353 return true;
354 }
355
356 private function __delete( $forumid ) {
357 $forumid = intval( $forumid );
358 // START delete topic posts include first post
359 if( $topicids = WPF()->db->get_col( "SELECT `topicid` FROM " . WPF()->tables->topics . " WHERE `forumid` = " . $forumid ) ) {
360 foreach( $topicids as $topicid ) {
361 WPF()->topic->delete( $topicid, false, false );
362 }
363 }
364 // END delete topic posts include first post
365
366 WPF()->db->delete( WPF()->tables->forums, [ 'forumid' => $forumid ], [ '%d' ] );
367
368 do_action( 'wpforo_after_delete_forum', $forumid );
369 }
370
371 public function merge( $forumid = 0, $mergeid = 0 ) {
372 $forumid = intval( $forumid );
373 $mergeid = intval( $mergeid );
374
375 if( ! $forumid && isset( $_REQUEST['id'] ) ) $forumid = intval( $_REQUEST['id'] );
376 if( ! $mergeid && isset( $_REQUEST['forum']['mergeid'] ) ) $mergeid = intval( $_REQUEST['forum']['mergeid'] );
377
378 if( ! $forumid || ! $mergeid ) return false;
379
380 if( $child_forumids = $this->get_child_forums( $forumid ) ) {
381 $forumids = implode( ',', $child_forumids );
382 $merge_layout = $this->get_layout( $mergeid );
383
384 if( ! WPF()->db->query( "UPDATE " . WPF()->tables->forums . " SET `parentid` = " . $mergeid . ", `layout` = " . $merge_layout . " WHERE `forumid` IN(" . $forumids . ")" ) ) {
385 WPF()->notice->add( 'Forum merging error', 'error' );
386
387 return false;
388 }
389
390 if( $childs = $this->get_childs( $forumid ) ) {
391 WPF()->db->query( "UPDATE " . WPF()->tables->forums . " SET `layout` = " . $merge_layout . " WHERE `forumid` IN(" . implode( ',', $childs ) . ")" );
392 }
393 }
394
395 WPF()->db->update( WPF()->tables->topics, [ 'forumid' => $mergeid ], [ 'forumid' => $forumid ], [ '%d' ], [ '%d' ] );
396 WPF()->db->update( WPF()->tables->posts, [ 'forumid' => $mergeid ], [ 'forumid' => $forumid ], [ '%d' ], [ '%d' ] );
397
398 if( WPF()->db->delete( WPF()->tables->forums, [ 'forumid' => $forumid ], [ '%d' ] ) ) {
399
400 do_action( 'wpforo_after_merge_forum', $forumid, $mergeid );
401
402 $this->delete_tree_cache();
403 wpforo_clean_cache( 'forum' );
404 WPF()->notice->add( 'Forum is successfully merged', 'success' );
405
406 return true;
407 }
408
409 WPF()->notice->add( 'Forum merging error', 'error' );
410
411 return false;
412 }
413
414 public function rebuild_last_infos( $forumid, $update_parent_forums = true ) {
415 if( ! $forumid = intval( $forumid ) ) return;
416
417 $last_topicid = 0;
418 $last_postid = 0;
419 $last_userids = [];
420 $last_post_date = '0000-00-00 00:00:00';
421 $last_users_number = apply_filters( 'wpforo_item_participant_avatars_count', 3 );
422
423 if( $last_topics = WPF()->topic->get_topics( [
424 'forumid' => $forumid,
425 'status' => 0,
426 'private' => 0,
427 'orderby' => 'topicid',
428 'order' => 'DESC',
429 'row_count' => 1,
430 ] ) ) {
431 $last_topic = $last_topics[0];
432 $last_topicid = $last_topic['topicid'];
433 }
434
435 // Collect participant userids from current forum and child forums
436 // Don't use simple GROUP BY, it requires aggregation using INNER JOIN
437 // otherwise it returns unexpected result with missing userids
438 $forumids = WPF()->forum->get_forumid_and_childids( $forumid );
439 $sql = "SELECT p.userid FROM `" . WPF()->tables->posts . "` p
440 INNER JOIN (
441 SELECT `userid`, MAX(`created`) AS LatestCreated
442 FROM `" . WPF()->tables->posts . "`
443 WHERE `status` = 0 AND `private` = 0 AND `forumid` IN (" . esc_sql( implode( ',', $forumids ) ) . ")
444 GROUP BY `userid`
445 ) AS latest_posts ON p.userid = latest_posts.userid AND p.created = latest_posts.LatestCreated
446 WHERE p.`status` = 0 AND p.`private` = 0 AND p.`forumid` IN (" . esc_sql( implode( ',', $forumids ) ) . ")
447 ORDER BY p.`created` DESC, p.`postid` DESC LIMIT " . intval( $last_users_number );
448
449 if( $last_posts = WPF()->db->get_results( $sql, ARRAY_A ) ) {
450 foreach( $last_posts as $last_post ) {
451 $last_userids[] = $last_post['userid'];
452 }
453 }
454
455 $sql = "SELECT `postid`, `created` FROM `" . WPF()->tables->posts . "` WHERE `status` = 0 AND `private` = 0 AND `forumid` IN(" . esc_sql(
456 implode( ',', $forumids )
457 ) . ") ORDER BY `created` DESC, `postid` DESC LIMIT 1";
458 if( $last_post = WPF()->db->get_row( $sql, ARRAY_A ) ) {
459 $last_postid = $last_post['postid'];
460 $last_post_date = $last_post['created'];
461 }
462
463 //Update last information of current forum only (use its own and child participants)
464 $sql = "UPDATE `" . WPF()->tables->forums . "`
465 SET `last_topicid` = %d,
466 `last_postid` = %d,
467 `last_userid` = %s,
468 `last_post_date` = %s
469 WHERE `forumid` = " . intval( $forumid );
470 WPF()->db->query( WPF()->db->prepare( $sql, $last_topicid, $last_postid, implode( ',', $last_userids ), $last_post_date ) );
471 wpforo_clean_cache( 'forum-soft', $forumid );
472
473 if( $update_parent_forums ) {
474 // Update parent forums last info if topics and posts are changed.
475 // Parent forums must not be updated on [reset_forums_stats] action and on recursive calls.
476 $parent_ids = [];
477 $this->get_parents( $forumid, $parent_ids );
478 $parent_ids = array_unique( array_filter( array_map( 'wpforo_bigintval', $parent_ids ) ) );
479 if( ! empty( $parent_ids ) ) {
480 // Remove current forumid to only update the parent forums
481 // and prevent updating last userids with parent forum participants
482 if( ( $current_forumid_key = array_search( $forumid, $parent_ids ) ) !== false ) {
483 unset( $parent_ids[ $current_forumid_key ] );
484 }
485 if( ! empty( $parent_ids ) ) {
486 foreach( $parent_ids as $parent_id ) {
487 $this->rebuild_last_infos( $parent_id, false );
488 wpforo_clean_cache( 'forum-soft', $parent_id );
489 }
490 wpforo_clean_cache( 'forum-soft' );
491 }
492 }
493 }
494 }
495
496 public function rebuild_stats( $forumid ) {
497 if( ! $forumid = intval( $forumid ) ) return false;
498 $topics = WPF()->topic->get_count( [ 'forumid' => $forumid, 'status' => 0, 'private' => 0 ] );
499 $posts = WPF()->post->get_count( [ 'forumid' => $forumid, 'status' => 0, 'private' => 0 ] );
500
501 if( false !== WPF()->db->update( WPF()->tables->forums, [ 'topics' => $topics, 'posts' => $posts ], [ 'forumid' => $forumid ], [ '%d', '%d' ], [ '%d' ] ) ) {
502 $parent_ids = $this->get_parent_forumids_static( $forumid );
503 if( ! empty( $parent_ids ) ) {
504 foreach( $parent_ids as $parent_id ) {
505 wpforo_clean_cache( 'forum-soft', $parent_id );
506 }
507 }
508 wpforo_clean_cache( 'forum-soft' );
509
510 return true;
511 }
512
513 return false;
514 }
515
516 function get_parent_forumids_static( $forumid ) {
517 $parent_ids = [];
518 $level_0 = wpforo_forum( $forumid );
519 if( wpfval( $level_0, 'parentid' ) ) {
520 $level_1 = wpforo_forum( $level_0['parentid'] );
521 $parent_ids[] = $level_1['forumid'];
522 if( wpfval( $level_1, 'parentid' ) ) {
523 $level_2 = wpforo_forum( $level_1['parentid'] );
524 $parent_ids[] = $level_2['forumid'];
525 if( wpfval( $level_2, 'parentid' ) ) {
526 $level_3 = wpforo_forum( $level_2['parentid'] );
527 $parent_ids[] = $level_3['forumid'];
528 }
529 }
530 }
531
532 return $parent_ids;
533 }
534
535 function _get_forum( $args ) {
536 $forum = [];
537 if( ! $args ) return $forum;
538
539 $default = [
540 'forumid' => null,
541 'slug' => '',
542 'status' => null,
543 'type' => 'all',
544 ];
545 if( ! is_array( $args ) ) {
546 if( wpforo_is_id( $args ) ) {
547 $default['forumid'] = intval( $args );
548 if( $default['forumid'] === WPF()->current_object['forumid'] ) return WPF()->current_object['forum'];
549 } elseif( is_string( $args ) ) {
550 $default['slug'] = trim( (string) $args );
551 if( $default['slug'] && $default['slug'] === wpfval( WPF()->current_object['forum'], 'slug' ) ) return WPF()->current_object['forum'];
552 }
553 }
554 $args = wpforo_parse_args( $args, $default );
555
556 $wheres = [];
557 if( $args['forumid'] ) $wheres[] = "`forumid` = " . intval( $args['forumid'] );
558 if( $args['slug'] ) $wheres[] = "`slug` = '" . esc_sql( $args['slug'] ) . "'";
559 if( ! is_null( $args['status'] ) ) $wheres[] = "`status` = " . intval( $args['status'] );
560 switch( $args['type'] ) {
561 case 'category':
562 $wheres[] = "`is_cat` = 1";
563 break;
564 case 'forum':
565 $wheres[] = "`is_cat` = 0";
566 break;
567 }
568
569 if( $wheres ) {
570 $sql = "SELECT * FROM `" . WPF()->tables->forums . "` WHERE " . implode( " AND ", $wheres );
571 if( $forum = WPF()->db->get_row( $sql, ARRAY_A ) ) {
572 if( ! $forum['layout'] ) $forum['layout'] = 1;
573 $forum['url'] = $this->get_forum_url( $forum );
574 if( $forum['cover'] = wpforo_bigintval( wpfval( $forum, 'cover' ) ) ) {
575 $image = wp_get_attachment_image_src( $forum['cover'], 'full' );
576 $forum['cover_url'] = (string) wpfval( $image, 0 );
577 } else {
578 $forum['cover_url'] = '';
579 }
580 }
581 }
582
583 return apply_filters( 'wpforo_get_forum', $forum, $args );
584 }
585
586 public function get_forum( $args ) {
587 return wpforo_ram_get( [ $this, '_get_forum' ], $args );
588 }
589
590 function get_forums( $args = [], &$items_count = 0, $count = false ) {
591 $cache = WPF()->cache->on( 'forum' );
592
593 $default = [
594 'include' => [], // array( 2, 10, 25 )
595 'exclude' => [], // array( 2, 10, 25 )
596 'parent_include' => [], // array( 2, 10, 25 )
597 'parent_exclude' => [], // array( 2, 10, 25 )
598 'parentid' => null,
599 'parent_slug' => '',
600 'status' => null,
601 'type' => 'all', // category, forum
602 'orderby' => 'order', // order by `field`
603 'order' => 'ASC', // ASC DESC
604 'offset' => null, // OFFSET
605 'row_count' => null, // ROW COUNT
606 'layout' => null, // 1, 2, 3, 4
607 ];
608
609 $args = wpforo_parse_args( $args, $default );
610
611 // Security: extract only expected keys instead of extract()
612 $include = $args['include'];
613 $exclude = $args['exclude'];
614 $parent_include = $args['parent_include'];
615 $parent_exclude = $args['parent_exclude'];
616 $parentid = $args['parentid'];
617 $parent_slug = $args['parent_slug'];
618 $status = $args['status'];
619 $type = $args['type'];
620 $orderby = $args['orderby'];
621 $order = $args['order'];
622 $offset = $args['offset'];
623 $row_count = $args['row_count'];
624 $layout = $args['layout'];
625
626 $include = wpforo_parse_args( $include );
627 $exclude = wpforo_parse_args( $exclude );
628 $parent_include = wpforo_parse_args( $parent_include );
629 $parent_exclude = wpforo_parse_args( $parent_exclude );
630
631 $sql = "SELECT * FROM `" . WPF()->tables->forums . "`";
632 $wheres = [];
633
634 if( ! empty( $include ) ) $wheres[] = "`forumid` IN(" . implode( ', ', array_map( 'intval', $include ) ) . ")";
635 if( ! empty( $exclude ) ) $wheres[] = "`forumid` NOT IN(" . implode( ', ', array_map( 'intval', $exclude ) ) . ")";
636 if( ! empty( $parent_include ) ) $wheres[] = "`parentid` IN(" . implode( ', ', array_map( 'intval', $parent_include ) ) . ")";
637 if( ! empty( $parent_exclude ) ) $wheres[] = "`parentid` NOT IN(" . implode( ', ', array_map( 'intval', $parent_exclude ) ) . ")";
638 if( $parentid != null ) $wheres[] = " `parentid` = " . intval( $parentid );
639 if( $layout != null ) $wheres[] = " `layout` = " . intval( $layout );
640 if( $status != null ) $wheres[] = " `status` = " . intval( $status );
641
642 if( $type === 'category' ) {
643 $wheres[] = " `is_cat` = 1";
644 } elseif( $type === 'forum' ) {
645 $wheres[] = " `is_cat` = 0";
646 }
647
648 if( $parent_slug != '' ) $wheres[] = "`slug` = '" . esc_sql( $parent_slug ) . "'";
649
650 if( ! empty( $wheres ) ) $sql .= " WHERE " . implode( " AND ", $wheres );
651
652 if( $count ) {
653 $item_count_sql = preg_replace( '#SELECT.+?FROM#isu', 'SELECT count(*) FROM', $sql );
654 // $item_count_sql = preg_replace('#ORDER.+$#is', '', $item_count_sql);
655 if( $item_count_sql ) $items_count = WPF()->db->get_var( $item_count_sql );
656 }
657
658 $sql .= esc_sql( " ORDER BY `$orderby` " . $order );
659
660 if( $row_count != null ) {
661 if( $offset != null ) {
662 $sql .= esc_sql( " LIMIT $offset,$row_count" );
663 } else {
664 $sql .= esc_sql( " LIMIT $row_count" );
665 }
666 }
667
668 if( $cache ) {
669 $object_key = md5( $sql . WPF()->current_user_groupid );
670 $object_cache = WPF()->cache->get( $object_key );
671 if( ! empty( $object_cache ) ) {
672 $items_count = $object_cache['items_count'];
673
674 return $object_cache['items'];
675 }
676 }
677
678 $forums = WPF()->db->get_results( $sql, ARRAY_A );
679
680 $forums = array_map( function( $forum ) {
681 if( $forum['cover'] = wpforo_bigintval( wpfval( $forum, 'cover' ) ) ) {
682 $image = wp_get_attachment_image_src( $forum['cover'], 'full' );
683 $forum['cover_url'] = (string) wpfval( $image, 0 );
684 } else {
685 $forum['cover_url'] = '';
686 }
687
688 return $forum;
689 }, $forums );
690
691 $forums = apply_filters( 'wpforo_get_forums', $forums, $args );
692
693 if( $cache && isset( $object_key ) && ! empty( $forums ) ) {
694 self::$cache['forums'][ $object_key ]['items'] = $forums;
695 self::$cache['forums'][ $object_key ]['items_count'] = $items_count;
696 }
697
698 return $forums;
699 }
700
701 function search( $needle, $fields = [] ) {
702
703 if( $needle ) {
704 $needle = sanitize_text_field( $needle );
705 if( empty( $fields ) ) {
706 $fields = [
707 'title',
708 'description',
709 'meta_key',
710 'meta_desc',
711 ];
712 }
713
714 $sql = "SELECT `forumid` FROM `" . WPF()->tables->forums . "`";
715 $wheres = [];
716
717 foreach( $fields as $field ) {
718 $wheres[] = "`" . esc_sql( $field ) . "` LIKE '%" . esc_sql( $needle ) . "%'";
719 }
720
721 $sql .= " WHERE " . implode( " OR ", $wheres );
722
723 return WPF()->db->get_col( $sql );
724 }
725
726 return [];
727 }
728
729 function update_hierarchy() {
730 if( is_array( $_REQUEST['forum'] ) && ! empty( $_REQUEST['forum'] ) ) {
731 $i = 0;
732 foreach( $_REQUEST['forum'] as $hierarchy ) {
733
734 // Security: extract only expected keys instead of extract()
735 $forumid = isset( $hierarchy['forumid'] ) ? $hierarchy['forumid'] : null;
736 $parentid = isset( $hierarchy['parentid'] ) ? $hierarchy['parentid'] : null;
737 $order = isset( $hierarchy['order'] ) ? $hierarchy['order'] : null;
738
739 if( ! isset( $forumid ) || ! $forumid = intval( $forumid ) ) continue;
740
741 if( false !== WPF()->db->update( WPF()->tables->forums, [
742 'parentid' => ( isset( $parentid ) ? intval( $parentid ) : 0 ),
743 'order' => ( isset( $order ) ? intval( $order ) : 0 ),
744 ], [ 'forumid' => intval( $forumid ) ], [
745 '%d',
746 '%d',
747 ], [ '%d' ] ) ) {
748 $i ++;
749 }
750
751 if( isset( $parentid ) && $parentid = intval( $parentid ) ) {
752 $layout = WPF()->db->get_var( "SELECT `layout` FROM `" . WPF()->tables->forums . "` WHERE `forumid` = " . intval( $parentid ) );
753 WPF()->db->query( "UPDATE `" . WPF()->tables->forums . "` SET `layout` = " . intval( $layout ) . " WHERE `forumid` = " . intval( $forumid ) );
754 }
755
756 }
757
758 WPF()->db->query( "UPDATE `" . WPF()->tables->forums . "` SET `is_cat` = 0" );
759 WPF()->db->query( "UPDATE `" . WPF()->tables->forums . "` SET `is_cat` = 1 WHERE `parentid` = 0" );
760
761 if( $i ) {
762 $this->delete_tree_cache();
763 WPF()->notice->add( 'Forum hierarchy successfully updated', 'success' );
764 } else {
765 WPF()->notice->add( 'Cannot update forum hierarchy', 'error' );
766 }
767
768 }
769 }
770
771 /**
772 * @param int $forumid
773 *
774 * @return array array of child forumids
775 */
776 public function get_childs( $forumid ) {
777 $key = [ 'forums', 'get_childs', $forumid ];
778 if( WPF()->ram_cache->exists( $key ) ) return WPF()->ram_cache->get( $key );
779
780 if( wpforo_is_db_mysql8() ) {
781 $forumids = $this->__get_childs_mysql8( $forumid );
782 } else {
783 $forumids = $this->__get_childs( $forumid );
784 }
785
786 WPF()->ram_cache->set( $key, $forumids );
787
788 return $forumids;
789 }
790
791 /**
792 * @param int $forumid
793 *
794 * @return array array of child forumids
795 */
796 private function __get_childs( $forumid ) {
797 if( $forumid = intval( $forumid ) ) {
798 $sql = "SELECT GROUP_CONCAT(
799 @id := (
800 SELECT GROUP_CONCAT(`forumid` ORDER BY `order` ASC)
801 FROM `" . WPF()->tables->forums . "`
802 WHERE FIND_IN_SET( `parentid`, @id )
803 )
804 ) AS forumids
805 FROM ( SELECT @id := %s ) vars
806 STRAIGHT_JOIN `" . WPF()->tables->forums . "`
807 WHERE @id IS NOT NULL";
808 $sql = WPF()->db->prepare( $sql, $forumid );
809
810 $forumids = explode( ',', (string) WPF()->db->get_var( $sql ) );
811
812 return array_values( array_unique( array_filter( array_map( 'intval', $forumids ) ) ) );
813 }
814
815 return [];
816 }
817
818 /**
819 * @param int $forumid
820 *
821 * @return array array of child forumids
822 */
823 private function __get_childs_mysql8( $forumid ) {
824 if( $forumid = intval( $forumid ) ) {
825 $sql = "WITH RECURSIVE `forum_path` AS (
826 SELECT `forumid`, `parentid`, `order`
827 FROM `" . WPF()->tables->forums . "`
828 WHERE `parentid` = %d
829 UNION
830 SELECT f.`forumid`, f.`parentid`, f.`order`
831 FROM `" . WPF()->tables->forums . "` AS f
832 INNER JOIN `forum_path` AS fp ON fp.`parentid` <> fp.`forumid` AND fp.`forumid` = f.`parentid`
833 ) SELECT * FROM `forum_path` ORDER BY `order`, `parentid` ASC";
834 $sql = WPF()->db->prepare( $sql, $forumid );
835
836 $forumids = WPF()->db->get_col( $sql );
837
838 return array_values( array_unique( array_filter( array_map( 'intval', $forumids ) ) ) );
839 }
840
841 return [];
842 }
843
844 // get forums tree for drop down menu
845
846 /**
847 * Returns depth for this item.
848 *
849 * @param int $forumid
850 *
851 * @param int $depth
852 *
853 * @since 1.0.0
854 *
855 */
856 function count_depth( $forumid, &$depth ) {
857 if( wpforo_is_db_mysql8() ) {
858 $depth = $this->__count_depth_mysql8( $forumid );
859
860 return;
861 }
862
863 $sql = "SELECT `parentid` FROM `" . WPF()->tables->forums . "` WHERE `forumid` = %d AND `parentid` <> %d";
864 $sql = WPF()->db->prepare( $sql, intval( $forumid ), intval( $forumid ) );
865
866 if( WPF()->ram_cache->exists( $sql ) ) {
867 $parentid = WPF()->ram_cache->get( $sql );
868 } else {
869 $parentid = WPF()->db->get_var( $sql );
870 WPF()->ram_cache->set( $sql, $parentid );
871 }
872
873 if( $parentid ) {
874 $depth ++;
875 $this->count_depth( $parentid, $depth );
876 }
877 }
878
879 private function __count_depth_mysql8( $forumid ) {
880 $sql = "WITH RECURSIVE `forum_path` AS(
881 SELECT `forumid`, `parentid`, 0 AS `depth`
882 FROM `" . WPF()->tables->forums . "`
883 WHERE `forumid` = %d
884 UNION
885 SELECT f.`forumid`, f.`parentid`, `depth` + 1
886 FROM `" . WPF()->tables->forums . "` f
887 INNER JOIN `forum_path` fp ON fp.`parentid` <> fp.`forumid` AND fp.`parentid` = f.`forumid`
888 ) SELECT `depth` FROM `forum_path` ORDER BY `depth` DESC LIMIT 1";
889
890 return (int) WPF()->db->get_var( WPF()->db->prepare( $sql, intval( $forumid ) ) );
891 }
892
893 /**
894 * @param int $forumid
895 *
896 * @return array
897 */
898 function get_child_forums( $forumid ) {
899 $sql = "SELECT `forumid`
900 FROM `" . WPF()->tables->forums . "`
901 WHERE `parentid` = " . intval( $forumid ) . "
902 AND `forumid` <> " . intval( $forumid ) . "
903 ORDER BY `order`";
904
905 return array_map( 'intval', WPF()->db->get_col( $sql ) );
906 }
907
908 function forum_list( $forumids, $type = 'select_box', $selected = [], $cats = true, $disabled = [] ) {
909 static $old_depth;
910 $disabled = (array) $disabled;
911 $selected = (array) $selected;
912
913 foreach( $forumids as $forumid ) {
914 $forumid = intval( $forumid );
915 if( ! $forumid || ! ( WPF()->usergroup->can_manage_forum() || WPF()->perm->forum_can( 'vf', $forumid ) ) || ( $type === 'drag_menu' && ! WPF()->usergroup->can_manage_forum() ) ) {
916 continue;
917 }
918 if( $type === 'subscribe_manager_form' && ! WPF()->perm->forum_can( 'sb', $forumid ) ) continue;
919 $depth = 0;
920 $this->count_depth( $forumid, $depth );
921 $forum = wpforo_forum( $forumid );
922 $name = wpfval( $forum, 'title' );
923 if( $type === 'select_box' ) { ?>
924 <option value="<?php echo $forumid ?>" <?php echo( ( ! $cats && $depth == 0 || ( in_array( $forumid, $disabled ) ) ) ? ' disabled ' : '' );
925 echo( in_array( $forumid, $selected ) ? ' selected ' : '' ) ?> > <?php echo esc_html( str_repeat( '— ', $depth ) . trim( (string) $name ) ) ?></option><?php
926 } elseif( $type === 'drag_menu' ) {
927 switch( $forum['layout'] ) {
928 case 2:
929 $layout_name = 'Simplified Layout';
930 break;
931 case 3:
932 $layout_name = 'Q&A Layout';
933 break;
934 case 4:
935 $layout_name = 'Threaded Layout';
936 break;
937 case 5:
938 $layout_name = 'Boxed Layout';
939 break;
940 default:
941 $layout_name = 'Extended Layout';
942 }
943 ?>
944
945 <li id="menu-item-<?php echo $forumid ?>" class="menu-item menu-item-depth-<?php echo esc_attr( $depth ) ?>">
946 <input id="forumid-<?php echo $forumid ?>" type="hidden" name="forum[<?php echo $forumid ?>][forumid]"/>
947 <input id="parentid-<?php echo $forumid ?>" type="hidden" name="forum[<?php echo $forumid ?>][parentid]"/>
948 <input id="order-<?php echo $forumid ?>" type="hidden" name="forum[<?php echo $forumid ?>][order]"/>
949 <dl class="menu-item-bar">
950 <dt class="menu-item-handle forum_width">
951 <span class="item-title forumtitle"><span style="font-weight:400; cursor:help;" title="Forum ID"><?php echo $forumid; ?> &nbsp;|&nbsp;</span> <?php echo apply_filters(
952 'wpforo_dashboard_forums_item_name',
953 esc_html( $name ),
954 $forumid
955 ); ?></span>
956 <span class="item-controls">
957 <span class="wpforo-cat-layout"><?php echo( $depth != 0 ? __( 'Topics', 'wpforo' ) . '&nbsp;(' . intval( $forum['topics'] ) . ')&nbsp;,&nbsp;' . __(
958 'Posts',
959 'wpforo'
960 ) . '&nbsp;(' . intval( $forum['posts'] ) . ')&nbsp; | &nbsp;' : '' ) ?><?php echo( $depth == 0 ? '(&nbsp;<i>' . esc_html(
961 $layout_name
962 ) . '</i>&nbsp;)&nbsp; | &nbsp;' : '' ); ?></span>
963 <span class="menu_add">
964 <a href="<?php echo admin_url( 'admin.php?page=' . wpforo_prefix_slug( 'forums' ) . '&action=add&parentid=' . $forumid ) ?>">
965 <span class="dashicons dashicons-plus"
966 title="<?php if( $depth ) : _e( 'Add a new Subforum', 'wpforo' ); else: _e( 'Add a new Forum in this Category', 'wpforo' ); endif; ?>"></span>
967 </a>
968 </span> &nbsp;|&nbsp;
969 <span class="menu_copy">
970 <a href="<?php echo wp_nonce_url(
971 admin_url( 'admin.php?page=' . wpforo_prefix_slug( 'forums' ) . '&wpfaction=forum_copy&forumid=' . $forumid ),
972 'forum_copy',
973 '_wpfnonce'
974 ) ?>" onclick="return confirm( '<?php wpforo_phrase( 'Are you sure you want to copy this forum?' ) ?>' )">
975 <span class="dashicons dashicons-admin-page" title="<?php _e( 'Copy', 'wpforo' ); ?>"></span>
976 </a>
977 </span> &nbsp;|&nbsp;
978 <span class="menu_edit">
979 <a href="<?php echo admin_url( 'admin.php?page=' . wpforo_prefix_slug( 'forums' ) . '&id=' . $forumid . '&action=edit' ) ?>">
980 <span class="dashicons dashicons-edit" title="<?php _e( 'edit', 'wpforo' ) ?>"></span>
981 </a>
982 </span>&nbsp;|&nbsp;
983 <?php if( WPF()->usergroup->can_manage_forum() ): ?>
984 <span class="menu_delete">
985 <a href="<?php echo admin_url( 'admin.php?page=' . wpforo_prefix_slug( 'forums' ) . '&id=' . $forumid . '&action=del' ) ?>">
986 <span class="dashicons dashicons-trash" title="<?php _e( 'delete', 'wpforo' ) ?>"></span>
987 </a>
988 </span>&nbsp;|&nbsp;
989 <?php endif; ?>
990 <span class="menu_view">
991 <a href="<?php echo esc_url( (string) wpforo_forum( $forumid, 'url' ) ); ?>">
992 <span class="dashicons dashicons-visibility" title="<?php _e( 'View', 'wpforo' ) ?>"></span>
993 </a>
994 </span>
995
996 </span>
997 </dt>
998 </dl>
999 <ul class="menu-item-transport"></ul>
1000 </li>
1001
1002 <?php
1003 } elseif( $type === 'front_list' ) {
1004 if( isset( $old_depth ) && $old_depth == $depth ) echo '</dd><dd>';
1005 if( isset( $old_depth ) && $old_depth < $depth ) echo '<dl><dd>';
1006 if( isset( $old_depth ) && $old_depth > $depth ) echo '</dd></dl>';
1007 $old_depth = $depth;
1008 printf(
1009 '<span class="wpf-dl-item %1$s"><a href="%2$s"><span class="wpf-circle wpf-s" style="display: inline-flex; %3$s"><i class="%4$s"></i></span><span class="wpf-dl-item-label">%5$s</span></a></span>',
1010 ( in_array( $forumid, $selected ) ? 'wpf-dl-current' : '' ),
1011 esc_url( (string) wpforo_forum( $forumid, 'url' ) ),
1012 ( ( $forum['color'] = trim( (string) $forum['color'] ) ) ? 'color: ' . $forum['color'] . ';' : '' ),
1013 $forum['icon'],
1014 esc_html( $name )
1015 );
1016 } elseif( $type === 'subscribe_manager_form' ) {
1017 ?>
1018 <li>
1019 <?php if( $depth > 0 ) :
1020 $forum_topic_attr = '';
1021 $forum_attr = '';
1022 if( key_exists( $forumid, $selected ) ) {
1023 if( $selected[ $forumid ] === 'forum-topic' ) {
1024 $forum_topic_attr = ' checked ';
1025 } elseif( $selected[ $forumid ] === 'forum' ) {
1026 $forum_attr = ' checked ';
1027 }
1028 }
1029 ?>
1030 <div class="wpf-sbs-div wpf-sbs-checkbox">
1031 <input id="wpf_sbs_allposts_<?php echo $forumid ?>" type="checkbox" name="wpforo[forums][<?php echo $forumid ?>]" value="forum-topic" <?php echo $forum_topic_attr ?>><label
1032 class="wpf-sbsp" for="wpf_sbs_allposts_<?php echo $forumid ?>"><?php wpforo_phrase( 'topics and posts' ) ?></label>
1033 <input id="wpf_sbs_alltopics_<?php echo $forumid ?>" type="checkbox" name="wpforo[forums][<?php echo $forumid ?>]" value="forum" <?php echo $forum_attr ?>><label
1034 class="wpf-sbst" for="wpf_sbs_alltopics_<?php echo $forumid ?>"><?php wpforo_phrase( 'topics' ) ?></label>
1035 </div>
1036 <?php endif; ?>
1037 <div class="wpf-sbs-div wpf-sbs-form-title<?php echo ( $depth > 0 ) ? ' wpf-sbs-forum' : ' wpf-sbs-cat'; ?>"><?php echo esc_html( str_repeat( '— ', $depth ) ) . trim(
1038 (string) $name
1039 ) ?></div>
1040 </li>
1041 <?php
1042 }
1043 $subforums = $this->get_child_forums( $forumid );
1044 if( ! empty( $subforums ) ) {
1045 $this->forum_list( $subforums, $type, $selected, true, $disabled );
1046 }
1047 }
1048 }
1049
1050 function tree( $type = 'select_box', $cats = true, $selected = [], $cache = true, $disabled = [], $parentids = [] ) {
1051 $disabled = (array) $disabled;
1052 $selected = (array) $selected;
1053 $parentids = (array) $parentids;
1054 if( ! $parentids ) $parentids = WPF()->db->get_col( "SELECT `forumid` FROM `" . WPF()->tables->forums . "` WHERE `parentid` = 0 ORDER BY `order`" );
1055 if( ! empty( $parentids ) ) {
1056 if( $cache && ! wpforo_is_admin() ) {
1057 $key = md5( serialize( $parentids ) . $type . (int) $cats . wp_json_encode( WPF()->current_user_groupids ) );
1058 $html = wpforo_get_option( 'forum_tree_' . $key, '' );
1059 $pattern_strip_selected = '#(<(?:option|input)[^<>]*?)[\r\n\t\s]*(?:selected|checked)[^\r\n\t\s]*?((?:[\r\n\t\s][^<>]*)?>)#isu';
1060 if( $html ) {
1061 if( $type === 'select_box' || $type === 'subscribe_manager_form' ) $html = preg_replace( $pattern_strip_selected, '$1$2', (string) $html );
1062 if( $selected ) {
1063 if( $type === 'select_box' ) {
1064 foreach( $selected as $sfid ) {
1065 $html = str_replace( 'value="' . $sfid . '"', 'value="' . $sfid . '" selected ', $html );
1066 }
1067 } elseif( $type === 'subscribe_manager_form' ) {
1068 foreach( $selected as $forumid => $stype ) {
1069 $html = preg_replace(
1070 '#(name=[\'"]wpforo\[forums\]\[' . intval( $forumid ) . '\][\'"][^<>]*?value=[\'"]' . preg_quote( $stype ) . '[\'"]|value=[\'"]' . preg_quote(
1071 $stype
1072 ) . '[\'"][^<>]*?name=[\'"]wpforo\[forums\]\[' . intval( $forumid ) . '\][\'"])#isu',
1073 '$1 checked',
1074 (string) $html
1075 );
1076 }
1077 }
1078 }
1079 echo $html;
1080 } elseif( function_exists( 'ob_start' ) ) {
1081 ob_start();
1082 $this->forum_list( $parentids, $type, $selected, $cats, $disabled );
1083 $html = ob_get_clean();
1084 $cache_html = ( $type === 'select_box' ? preg_replace( $pattern_strip_selected, '$1$2', (string) $html ) : $html );
1085 if( $type !== 'drag_menu' ) wpforo_update_option( 'forum_tree_' . $key, $cache_html );
1086 echo $html;
1087 }
1088 } else {
1089 $this->forum_list( $parentids, $type, $selected, $cats, $disabled );
1090 }
1091 }
1092 }
1093
1094 public function delete_tree_cache() {
1095 WPF()->db->query( "DELETE FROM `" . WPF()->db->options . "` WHERE `option_name` REGEXP '^" . wpforo_prefix( 'forum_tree_' ) . "'" );
1096 }
1097
1098 function parentid( $topicid = 0 ) {
1099 if( isset( $_GET['page'] ) && preg_match( '#^wpforo-(?:\d+-)?forums$#iu', (string) $_GET['page'] ) ) {
1100 if( isset( $_GET['id'] ) ) return WPF()->db->get_var( "SELECT `parentid` FROM `" . WPF()->tables->forums . "` WHERE `forumid` = " . intval( $_GET['id'] ) );
1101 } elseif( isset( $_GET['page'] ) && preg_match( '#^wpforo-(?:\d+-)?topics$#iu', (string) $_GET['page'] ) ) {
1102 if( isset( $_GET['id'] ) ) return WPF()->db->get_var( "SELECT `forumid` FROM `" . WPF()->tables->topics . "` WHERE `topicid` = " . wpforo_bigintval( $_GET['id'] ) );
1103 } else {
1104 if( $topicid ) return WPF()->db->get_var( "SELECT `forumid` FROM `" . WPF()->tables->topics . "` WHERE `topicid` = " . wpforo_bigintval( $topicid ) );
1105 }
1106 }
1107
1108 function permissions() {
1109 $access_arr = WPF()->perm->get_accesses();
1110 if( ! empty( $access_arr ) ) {
1111
1112 if( isset( $_GET['id'] ) ) {
1113 if( $permissions_srlz = WPF()->db->get_var( "SELECT `permissions` FROM `" . WPF()->tables->forums . "` WHERE `forumid` = " . intval( $_GET['id'] ) ) ) {
1114 $permissions_arr = unserialize( $permissions_srlz );
1115 }
1116 }
1117
1118 if( $usergroups = WPF()->db->get_results( "SELECT `groupid`, `name` FROM `" . WPF()->tables->usergroups . "`", ARRAY_A ) ) {
1119 foreach( $usergroups as $usergroup ) {
1120 $groupid = $usergroup['groupid'];
1121 $name = $usergroup['name'];
1122 echo '
1123 <tr>
1124 <td>' . esc_html( $name ) . '</td>
1125 <td>
1126 <select name="forum[permission][' . intval( $groupid ) . ']">';
1127 foreach( $access_arr as $value ) {
1128
1129 echo '<option value="' . esc_attr(
1130 $value['access']
1131 ) . '" ' . ( ( isset( $permissions_arr[ $groupid ] ) && $value['access'] == $permissions_arr[ $groupid ] ) || ( ! isset( $permissions_arr[ $groupid ] ) && ( ( $name == 'Guest' && $value['access'] == 'read_only' ) || ( $name == 'Registered' && $value['access'] == 'standard' ) || ( $name == 'Customer' && $value['access'] == 'standard' ) || ( $name == 'Moderator' && $value['access'] == 'moderator' ) || ( $name == 'Admin' && $value['access'] == 'full' ) || ( $name != 'Guest' && $name != 'Registered' && $name != 'Customer' && $name != 'Moderator' && $name != 'Admin' && $value['access'] == 'standard' ) ) ) ? ' selected ' : '' ) . '>' . esc_html(
1132 __( $value['title'], 'wpforo' )
1133 ) . '</option>';
1134 }
1135 echo '
1136 </select>
1137 </td>
1138 </tr>
1139 ';
1140 }
1141
1142
1143 }
1144 }
1145 }
1146
1147 /**
1148 * array get_counts(array or id(num))
1149 *
1150 * @param array defined arguments array for returning
1151 *
1152 * @return array array('topics' => 0, 'posts' => 0)
1153 * @since 1.0.0
1154 *
1155 */
1156 function get_counts( $forumids ): array {
1157 $result = [ 'topics' => 0, 'posts' => 0 ];
1158 $forumids = array_filter( array_map( 'intval', (array) $forumids ) );
1159 if( $forumids ) {
1160 $sql = "SELECT SUM(`topics`) as topics, SUM(`posts`) as posts FROM `" . WPF()->tables->forums . "` WHERE `forumid` IN(" . implode( ',', $forumids ) . ")";
1161 $row = (array) WPF()->db->get_row( $sql, ARRAY_A );
1162 $result['topics'] = (int) wpfval( $row, 'topics' );
1163 $result['posts'] = (int) wpfval( $row, 'posts' );
1164 }
1165
1166 return $result;
1167 }
1168
1169 /**
1170 * array get_layout(array)
1171 *
1172 * @param array defined arguments array for returning
1173 *
1174 * @return int layout id
1175 * @since 1.0.0
1176 *
1177 */
1178 function get_layout( $args = null ) {
1179 if( is_null( $args ) ) $args = WPF()->current_object['forum'];
1180 if( ! $args ) return 1;
1181 if( $layout = (int) wpfval( $args, 'layout' ) ) return $layout;
1182 if( is_array( $args ) ) {
1183 $default = [
1184 'forumid' => null, // forum id
1185 'topicid' => null, // topic id
1186 'postid' => null, // post id
1187 ];
1188 } else {
1189 $default = [
1190 'forumid' => $args, // forumid
1191 'topicid' => null, // topic id
1192 'postid' => null, // post id
1193 ];
1194 }
1195 $args = wpforo_parse_args( $args, $default );
1196 if( ! empty( $args ) ) {
1197 if( $args['forumid'] ) {
1198 $layout = (int) wpforo_forum( $args['forumid'], 'layout' );
1199
1200 return ( $layout ?: 1 );
1201 } elseif( $args['topicid'] ) {
1202 $sql = "SELECT `forumid` FROM `" . WPF()->tables->topics . "` WHERE `topicid` = " . intval( $args['topicid'] );
1203 $forumid = WPF()->db->get_var( $sql );
1204
1205 return $this->get_layout( [ 'forumid' => $forumid ] );
1206 } elseif( $args['postid'] ) {
1207 $sql = "SELECT `forumid` FROM `" . WPF()->tables->posts . "` WHERE `postid` = " . intval( $args['postid'] );
1208 $forumid = WPF()->db->get_var( $sql );
1209
1210 return $this->get_layout( [ 'forumid' => $forumid ] );
1211 }
1212 }
1213
1214 return 1;
1215 }
1216
1217 function get_forum_url( $forum ) {
1218
1219 if( ! is_array( $forum ) ) {
1220 if( wpforo_is_id( $forum ) ) {
1221 $forum = $this->get_forum( $forum );
1222 } else {
1223 $forum = [ 'slug' => $forum ];
1224 }
1225 }
1226
1227 if( is_array( $forum ) && ! empty( $forum ) ) {
1228 return wpforo_home_url( utf8_uri_encode( $forum['slug'] ) );
1229 } else {
1230 return wpforo_home_url();
1231 }
1232 }
1233
1234 function get_parents( $forumid, &$relative_ids ) {
1235 if( wpforo_is_db_mysql8() ) {
1236 $relative_ids = $this->__get_parents_mysql8( $forumid );
1237
1238 return;
1239 }
1240
1241 $sql = "SELECT `parentid`, `forumid` FROM `" . WPF()->tables->forums . "` WHERE `forumid` = %d";
1242 $sql = WPF()->db->prepare( $sql, $forumid );
1243 if( WPF()->ram_cache->exists( $sql ) ) {
1244 $forum = WPF()->ram_cache->get( $sql );
1245 } else {
1246 $forum = WPF()->db->get_row( $sql, ARRAY_A );
1247 WPF()->ram_cache->set( $sql, $forum );
1248 }
1249 if( $forum ) {
1250 $relative_ids[] = $forum['forumid'];
1251 if( $forum['parentid'] ) {
1252 $this->get_parents( $forum['parentid'], $relative_ids );
1253 } else {
1254 $relative_ids = array_reverse( $relative_ids );
1255 }
1256 }
1257 }
1258
1259 /**
1260 * @param int $forumid
1261 *
1262 * @return array
1263 */
1264 private function __get_parents_mysql8( $forumid ) {
1265 $sql = "WITH RECURSIVE `forum_path` AS(
1266 SELECT `forumid`, `parentid`, 0 AS `depth`
1267 FROM `" . WPF()->tables->forums . "`
1268 WHERE `forumid` = %d
1269 UNION
1270 SELECT f.`forumid`, f.`parentid`, `depth` + 1
1271 FROM `" . WPF()->tables->forums . "` f
1272 INNER JOIN `forum_path` fp ON fp.`parentid` <> fp.`forumid` AND fp.`parentid` = f.`forumid`
1273 ) SELECT `forumid` FROM `forum_path` ORDER BY `depth` DESC";
1274 $sql = WPF()->db->prepare( $sql, intval( $forumid ) );
1275 if( WPF()->ram_cache->exists( $sql ) ) {
1276 $relative_ids = WPF()->ram_cache->get( $sql );
1277 } else {
1278 $relative_ids = (array) WPF()->db->get_col( $sql );
1279 WPF()->ram_cache->set( $sql, $relative_ids );
1280 }
1281
1282 return $relative_ids;
1283 }
1284
1285 function get_count( $args = [] ) {
1286 $sql = "SELECT SQL_NO_CACHE COUNT(*) FROM `" . WPF()->tables->forums . "`";
1287 if( ! empty( $args ) ) {
1288 $wheres = [];
1289 foreach( $args as $key => $value ) $wheres[] = "`$key` = " . intval( $value );
1290 if( $wheres ) $sql .= " WHERE " . implode( ' AND ', $wheres );
1291 }
1292
1293 return WPF()->db->get_var( $sql );
1294 }
1295
1296 function get_lastinfo( $ids = [] ) {
1297 $lastinfo = [];
1298 if( ! empty( $ids ) ) {
1299 $ids = implode( ',', array_map( 'intval', $ids ) );
1300 $lastinfo = WPF()->db->get_row(
1301 "SELECT `userid` as last_userid, `topicid` as last_topicid, `postid` as last_postid, `created` as last_post_date FROM `" . WPF(
1302 )->tables->posts . "` WHERE `status` = 0 AND `private` = 0 AND forumid IN(" . $ids . ") ORDER BY `created` DESC LIMIT 1",
1303 ARRAY_A
1304 );
1305 }
1306
1307 return $lastinfo;
1308 }
1309
1310 function forums() {
1311 $forums = $this->get_forums( [ 'parentid' => 0 ] );
1312
1313 return $this->children( $forums );
1314 }
1315
1316 function children( $forums, $parentId = 0, $level = 0 ) {
1317 if( empty( $forums ) || ! is_array( $forums ) ) return;
1318 $items = [];
1319 $level = $level + 1;
1320 foreach( $forums as $forum ) {
1321 if( ! isset( $forum['forumid'] ) || ! WPF()->perm->forum_can( 'vf', $forum['forumid'] ) ) continue;
1322 $forum['level'] = $level + 1;
1323 if( $forum['parentid'] == $parentId ) {
1324 $children = $this->children( $forums, $forum['forumid'], $level );
1325 if( $children ) {
1326 $forum['children'] = $children;
1327 }
1328 $items[] = $forum;
1329 }
1330 }
1331
1332 return $items;
1333 }
1334
1335 function dropdown( $forums = [] ) {
1336 if( empty( $forums ) ) {
1337 $forums = $this->forums();
1338 }
1339 foreach( $forums as $forum ) {
1340 if( isset( $forum['level'] ) ) $forum['level'] = $forum['level'] - 2;
1341 $prefix = ( $forum['level'] == 0 ) ? '' : str_repeat( '&mdash;', $forum['level'] );
1342 echo '<option value="' . esc_attr( $forum['forumid'] ) . '"> ' . $prefix . '&nbsp;' . esc_html( $forum['title'] ) . '</option>';
1343 if( ! empty( $forum['children'] ) ) {
1344 $this->dropdown( $forum['children'] );
1345 }
1346 }
1347 }
1348
1349 function private_forum( $forumid, $groupids = [] ) {
1350 if( $forumid = intval( $forumid ) ) {
1351 if( ! $groupids ) $groupids = WPF()->current_user_groupids;
1352 if( ( $groupids = array_map( 'intval', (array) $groupids ) ) && ! WPF()->perm->forum_can( 'vf', $forumid, $groupids ) ) return true;
1353 }
1354
1355 return false;
1356 }
1357
1358 public function get_forumid_and_childids( int $forumid, bool $check_permission = true ): array {
1359 $forumids = array_merge( [ $forumid ], $this->get_childs( $forumid ) );
1360
1361 if( $check_permission ) {
1362 $forumids = array_filter( $forumids, function( $forumid ) {
1363 return WPF()->perm->forum_can( 'vf', $forumid );
1364 } );
1365 }
1366
1367 return $forumids;
1368 }
1369
1370 public function get_sum_of_topics_posts( int $forumid, bool $check_permission = true ): array {
1371 $forumids = $this->get_forumid_and_childids( $forumid, $check_permission );
1372
1373 return $this->get_counts( $forumids );
1374 }
1375
1376 public function after_add_usergroup( $group ) {
1377 if( $group['groupid'] ) {
1378 if( $forums = $this->get_forums() ) {
1379 foreach( $forums as $forum ) {
1380 if( $permissions = (string) wpfval( $forum, 'permissions' ) ) {
1381 if( $permissions = maybe_unserialize( $permissions ) ) {
1382 $permissions[ $group['groupid'] ] = $group['access'];
1383 WPF()->db->update( WPF()->tables->forums, [ 'permissions' => serialize( $permissions ) ], [ 'forumid' => $forum['forumid'] ], [ '%s' ], [ '%d' ] );
1384 }
1385 }
1386 }
1387 }
1388 }
1389 }
1390
1391 public function after_merge_forum( $forumid, $mergeid ) {
1392 $this->rebuild_last_infos( $mergeid );
1393 $this->rebuild_stats( $mergeid );
1394 }
1395
1396 public function after_add_topic( $topic ) {
1397 if( ! intval( $topic['status'] ) && ! intval( $topic['private'] ) ) {
1398 $this->rebuild_last_infos( $topic['forumid'] );
1399 $this->rebuild_stats( $topic['forumid'] );
1400 }
1401 }
1402
1403 public function after_delete_topic( $topic ) {
1404 if( ! intval( $topic['status'] ) && ! intval( $topic['private'] ) ) {
1405 $this->rebuild_last_infos( $topic['forumid'] );
1406 $this->rebuild_stats( $topic['forumid'] );
1407 }
1408 }
1409
1410 public function after_topic_status_update( $topic ) {
1411 if( ! intval( $topic['private'] ) ) {
1412 $this->rebuild_last_infos( $topic['forumid'] );
1413 $this->rebuild_stats( $topic['forumid'] );
1414 }
1415 }
1416
1417 public function topic_private_update( $topicid ) {
1418 if( $topic = WPF()->topic->get_topic( $topicid ) ) {
1419 $this->rebuild_last_infos( $topic['forumid'] );
1420 $this->rebuild_stats( $topic['forumid'] );
1421 }
1422 }
1423
1424 public function after_merge_topic( $target, $current ) {
1425 $this->rebuild_last_infos( $target['forumid'] );
1426 $this->rebuild_stats( $target['forumid'] );
1427 $this->rebuild_last_infos( $current['forumid'] );
1428 $this->rebuild_stats( $current['forumid'] );
1429 }
1430
1431 public function after_move_topic( $topic, $forumid ) {
1432 $this->rebuild_last_infos( $topic['forumid'] );
1433 $this->rebuild_stats( $topic['forumid'] );
1434 $this->rebuild_last_infos( $forumid );
1435 $this->rebuild_stats( $forumid );
1436 }
1437
1438 public function after_add_post( $post, $topic, $forum ) {
1439 if( ! intval( $post['status'] ) ) {
1440 $this->rebuild_last_infos( $forum['forumid'] );
1441 $this->rebuild_stats( $forum['forumid'] );
1442 }
1443 }
1444
1445 public function after_delete_post( $post ) {
1446 if( ! intval( $post['status'] ) ) {
1447 $this->rebuild_last_infos( $post['forumid'] );
1448 $this->rebuild_stats( $post['forumid'] );
1449 }
1450 }
1451
1452 public function after_post_status_update( $post ) {
1453 if( ! intval( $post['private'] ) ) {
1454 $this->rebuild_last_infos( $post['forumid'] );
1455 $this->rebuild_stats( $post['forumid'] );
1456 }
1457 }
1458 }
1459