# wpforo/2.3.0/classes/Forums.php

wpForo Forum, version 2.3.0. 1,360 lines.

- Page: https://pluginprobe.com/plugins/wpforo/2.3.0/code/classes/Forums.php
- Raw: https://pluginprobe.com/plugins/wpforo/2.3.0/raw/classes/Forums.php
- Modified: 2024-01-18T08:59:42+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/wpforo/2.3.0/code/classes/Forums.php#L10-L20`.

```php
<?php

namespace wpforo\classes;

use stdClass;

// Exit if accessed directly
if( ! defined( 'ABSPATH' ) ) exit;

class Forums {
	static $cache = [ 'forums' => [], 'forum' => [], 'item' => [] ];

	public static function _get_forumids( $table, $layout = null ) {
        $sql = "SELECT `forumid` FROM `$table` WHERE `status` = 1";
        if( !is_null( $layout ) ) $sql .= WPF()->db->prepare( " AND `layout` = %d", $layout );
		return WPF()->db->get_col($sql);
    }

	public static function get_forumids( $table, $layout = null ) {
        return wpforo_ram_get( [self::class, '_get_forumids'], $table, $layout );
    }

	public function __construct() {
		$this->init_hooks();
	}

	public function reset() {
		self::$cache = [ 'forums' => [], 'forum' => [], 'item' => [] ];
	}

	private function init_hooks() {
		add_action( 'wpforo_after_add_usergroup',  [$this, 'after_add_usergroup'] );

        add_action( 'wpforo_after_merge_forum',    [$this, 'after_merge_forum'], 10, 2 );

        add_action( 'wpforo_after_add_topic',      [$this, 'after_add_topic'] );
		add_action( 'wpforo_after_delete_topic',   [$this, 'after_delete_topic'] );
		add_action( 'wpforo_topic_status_update',  [$this, 'after_topic_status_update'] );
        add_action( 'wpforo_topic_private_update', [$this, 'topic_private_update'] );
        add_action( 'wpforo_after_merge_topic',    [$this, 'after_merge_topic'], 10, 2 );
        add_action( 'wpforo_after_move_topic',     [$this, 'after_move_topic'], 10, 2 );

        add_action( 'wpforo_after_add_post',       [$this, 'after_add_post'], 10, 3 );
        add_action( 'wpforo_after_delete_post',    [$this, 'after_delete_post'] );
		add_action( 'wpforo_post_status_update',   [$this, 'after_post_status_update'] );
	}

	public function get_cache( $var ) {
		if( isset( self::$cache[ $var ] ) ) return self::$cache[ $var ];
	}

	private function unique_slug( $slug, $parentid = 0, $forumid = 0 ) {
		$new_slug = wpforo_text( $slug, 250, false );
		$forumid  = intval( $forumid );
		$i        = 2;
		while( ! WPF()->can_use_this_slug( $new_slug ) || WPF()->db->get_var( "SELECT `forumid` FROM " . WPF()->tables->forums . " WHERE `slug` = '" . esc_sql( $new_slug ) . "'" . ( $forumid ? ' AND `forumid` != ' . intval( $forumid ) : '' ) ) ) {
			if( ! isset( $parent_slug ) && $parentid = intval( $parentid ) ) {
				$parent_slug = WPF()->db->get_var( "SELECT `slug` FROM " . WPF()->tables->forums . " WHERE `forumid` = " . intval( $parentid ) );
				$new_slug    = $parent_slug . "-" . wpforo_text( $slug, 250, false );
			} else {
				$new_slug = wpforo_text( $slug, 250, false ) . '-' . $i ++;
			}
		}

		return $new_slug;
	}

	public function copy( $forumid ) {
        if( ( $forumid = intval( $forumid ) )
           && ( $forum = $this->get_forum( $forumid ) )
        ){
            $forum['title'] .= ' - COPY';
            $forum['last_topicid'] = $forum['last_postid'] = $forum['last_userid'] = $forum['topics'] = $forum['posts'] = 0;
            $forum['last_post_date'] = '0000-00-00 00:00:00';
            $forum['permission'] = unserialize( $forum['permissions'] );
            unset( $forum['forumid'], $forum['url'], $forum['cover_url'], $forum['permissions'] );

	        $forum['forumid'] = $this->add( $forum, false );

	        do_action( 'wpforo_after_copy_forum', $forum, $forumid );
	        return $forum['forumid'];
        }

		WPF()->notice->add( 'Can\'t copy forum', 'error' );

		return false;
    }

	public function add( $args = [], $checkperm = true ) {
		if( $checkperm && ! WPF()->usergroup->can_manage_forum() ) {
			WPF()->notice->add( 'Permission denied for add forum', 'error' );

			return false;
		}

		if( empty( $args ) && empty( $_REQUEST['forum'] ) ) return false;
		if( empty( $args ) && ! empty( $_REQUEST['forum'] ) ) $args = $_REQUEST['forum'];

		$args['title'] = sanitize_text_field( stripslashes( (string) wpfval( $args, 'title' ) ) );
		if( ! $args['title'] ) {
			WPF()->notice->add( 'Please insert required fields!', 'error' );

			return false;
		}

		$args['title']    = wpforo_text( $args['title'], 250, false );
		$args['parentid'] = (int) wpfval( $args, 'parentid' );

		$args['slug'] = ( $ss = trim( sanitize_title( (string) wpfval( $args, 'slug' ) ) ) ) ? $ss : ( ( $st = trim( sanitize_title( $args['title'] ) ) ) ? $st : md5( time() ) );
		$args['slug'] = $this->unique_slug( $args['slug'], $args['parentid'] );

		$args['description'] = wpforo_kses( stripslashes( (string) wpfval( $args, 'description' ) ) );

		$group_access_relation = WPF()->usergroup->get_usergroup_access_relation();
		$args['permission']    = ( (array) wpfval( $args, 'permission' ) ) + $group_access_relation;
		$args['permission']    = serialize( array_map( 'sanitize_text_field', $args['permission'] ) );

		$args['meta_key']  = sanitize_text_field( stripslashes( (string) wpfval( $args, 'meta_key' ) ) );
		$args['meta_desc'] = sanitize_text_field( stripslashes( (string) wpfval( $args, 'meta_desc' ) ) );
		$args['icon']      = sanitize_text_field( (string) wpfval( $args, 'icon' ) );
		$args['cover']     = wpforo_bigintval( wpfval( $args, 'cover' ) );
		$args['cover_height'] = intval( wpfval( $args, 'cover_height' ) );
        if( ! $args['cover_height'] ) $args['cover_height'] = 150;
		$args['topics']    = (int) wpfval( $args, 'topics' );
		$args['posts']     = (int) wpfval( $args, 'posts' );
		$args['order']     = (int) wpfval( $args, 'order' );

		$args['status'] = (int) wpfval( $args, 'status' );
		if( ! $args['status'] ) $args['status'] = 1;

		$args['color'] = sanitize_text_field( stripslashes( (string) wpfval( $args, 'color' ) ) );
		if( ! $args['color'] ) $args['color'] = '#888888';

		$args['is_cat'] = (int) wpfval( $args, 'is_cat' );
		if( ! $args['parentid'] ) $args['is_cat'] = 1;

		$args['layout'] = (int) wpfval( $args, 'layout' );
		if( $args['parentid'] ) {
			$layout = (int) WPF()->db->get_var( "SELECT `layout` FROM `" . WPF()->tables->forums . "` WHERE `forumid` = " . $args['parentid'] );
			if( $layout ) $args['layout'] = $layout;
		}
		if( ! $args['layout'] ) $args['layout'] = 1;

		$args = apply_filters( 'wpforo_before_add_forum', $args, $checkperm );
		if( ! $args ) return false;

		if( WPF()->db->insert( WPF()->tables->forums,
		                       [
			                       'title'        => $args['title'],
			                       'slug'         => $args['slug'],
			                       'description'  => $args['description'],
			                       'parentid'     => $args['parentid'],
			                       'icon'         => $args['icon'],
			                       'cover'        => $args['cover'],
			                       'cover_height' => $args['cover_height'],
			                       'topics'       => $args['topics'],
			                       'posts'        => $args['posts'],
			                       'permissions'  => $args['permission'],
			                       'meta_key'     => $args['meta_key'],
			                       'meta_desc'    => $args['meta_desc'],
			                       'status'       => $args['status'],
			                       'is_cat'       => $args['is_cat'],
			                       'layout'   => $args['layout'],
			                       'order'        => $args['order'],
			                       'color'        => $args['color'],
		                       ],
		                       [ '%s', '%s', '%s', '%d', '%s', '%d', '%d', '%d', '%d', '%s', '%s', '%s', '%d', '%d', '%d', '%d', '%s', ]
        ) ) {
			$args['forumid'] = WPF()->db->insert_id;

			do_action( 'wpforo_after_add_forum', $args, $checkperm );

			$this->delete_tree_cache();
			wpforo_clean_cache();
			WPF()->notice->add( 'Your forum successfully added', 'success' );

			return $args['forumid'];
		}

		WPF()->notice->add( 'Can\'t add forum', 'error' );

		return false;
	}

	public function edit( $args = [], $checkperm = true ) {
		if( $checkperm && ! WPF()->usergroup->can_manage_forum() ) {
			WPF()->notice->add( 'Permission denied for edit forum', 'error' );

			return false;
		}

		if( empty( $args ) && empty( $_REQUEST['forum'] ) ) return false;
		if( empty( $args ) && ! empty( $_REQUEST['forum'] ) ) $args = $_REQUEST['forum'];
		if( ! isset( $args['forumid'] ) && isset( $_GET['id'] ) ) $args['forumid'] = $_GET['id'];

		$args['forumid'] = (int) wpfval( $args, 'forumid' );
		if( ! $forum = $this->get_forum( $args['forumid'] ) ) {
			WPF()->notice->add( 'Forum update error', 'error' );

			return false;
		}

		$args['title'] = sanitize_text_field( stripslashes( (string) wpfval( $args, 'title' ) ) );
		if( ! $args['title'] ) {
			WPF()->notice->add( 'Please insert required fields!', 'error' );

			return false;
		}

		$args['title']    = wpforo_text( $args['title'], 250, false );
		$args['parentid'] = (int) wpfval( $args, 'parentid' );
		if( $args['forumid'] === $args['parentid'] ) $args['parentid'] = (int) $forum['parentid'];

		$args['slug'] = ( $ss = trim( sanitize_title( (string) wpfval( $args, 'slug' ) ) ) ) ? $ss : ( ( $st = trim( sanitize_title( $args['title'] ) ) ) ? $st : md5( time() ) );
		$args['slug'] = $this->unique_slug( $args['slug'], $args['parentid'], $args['forumid'] );

		$args['description'] = wpforo_kses( stripslashes( (string) wpfval( $args, 'description' ) ) );

		$group_access_relation = WPF()->usergroup->get_usergroup_access_relation();
		$args['permission']    = ( (array) wpfval( $args, 'permission' ) ) + $group_access_relation;
		$args['permission']    = serialize( array_map( 'sanitize_text_field', $args['permission'] ) );

		$args['meta_key']  = sanitize_text_field( stripslashes( (string) wpfval( $args, 'meta_key' ) ) );
		$args['meta_desc'] = sanitize_text_field( stripslashes( (string) wpfval( $args, 'meta_desc' ) ) );
		$args['icon']      = sanitize_text_field( wpfval( $args, 'icon' ) );
		$args['cover']     = wpforo_bigintval( wpfval( $args, 'cover' ) );
		$args['cover_height'] = intval( wpfval( $args, 'cover_height' ) );
		if( ! $args['cover_height'] ) $args['cover_height'] = 150;
		$args['topics']    = (int) wpfval( $args, 'topics' );
		$args['posts']     = (int) wpfval( $args, 'posts' );
		$args['order']     = (int) wpfval( $args, 'order' );

		$args['status'] = (int) wpfval( $args, 'status' );
		if( ! $args['status'] ) $args['status'] = 1;

		$args['color'] = sanitize_text_field( stripslashes( (string) wpfval( $args, 'color' ) ) );
		if( ! $args['color'] ) $args['color'] = '#888888';

		$args['is_cat'] = (int) wpfval( $args, 'is_cat' );
		if( ! $args['parentid'] ) $args['is_cat'] = 1;

		$args['layout'] = (int) wpfval( $args, 'layout' );
		if( $args['parentid'] ) {
			$layout = (int) WPF()->db->get_var( "SELECT `layout` FROM `" . WPF()->tables->forums . "` WHERE `forumid` = " . $args['parentid'] );
			if( $layout ) $args['layout'] = $layout;
		}
		if( ! $args['layout'] ) $args['layout'] = 1;

		$args = apply_filters( 'wpforo_before_edit_forum', $args, $forum, $checkperm );
		if( ! $args ) return false;

		if( false !== WPF()->db->update( WPF()->tables->forums,
		                                 [
			                                 'title'        => $args['title'],
			                                 'slug'         => $args['slug'],
			                                 'description'  => $args['description'],
			                                 'parentid'     => $args['parentid'],
			                                 'icon'         => $args['icon'],
			                                 'cover'        => $args['cover'],
			                                 'cover_height' => $args['cover_height'],
			                                 'permissions'  => $args['permission'],
			                                 'meta_key'     => $args['meta_key'],
			                                 'meta_desc'    => $args['meta_desc'],
			                                 'status'       => $args['status'],
			                                 'is_cat'       => $args['is_cat'],
			                                 'layout'   => $args['layout'],
			                                 'color'        => $args['color'],
		                                 ],
		                                 [ 'forumid' => $args['forumid'] ],
		                                 [ '%s', '%s', '%s', '%d', '%s', '%d', '%d', '%s', '%s', '%s', '%d', '%d', '%d', '%s' ],
		                                 [ '%d' ] ) ) {
			if( $childs = $this->get_childs( $args['forumid'] ) ) {
				$sql = "UPDATE `" . WPF()->tables->forums . "` SET `layout` = " . $args['layout'] . " WHERE `forumid` IN(" . implode( ',', $childs ) . ")";
				WPF()->db->query( $sql );
			}

			do_action( 'wpforo_after_edit_forum', $args, $forum, $checkperm );

			$this->delete_tree_cache();
			wpforo_clean_cache();
			WPF()->notice->add( 'Forum successfully updated', 'success' );

			return $args['forumid'];
		}

		WPF()->notice->add( 'Forum update error', 'error' );

		return false;
	}

	public function delete( $forumid = 0, $checkperm = true ) {
		$forumid = intval( $forumid );
		if( ! $forumid && isset( $_REQUEST['id'] ) ) $forumid = intval( $_REQUEST['id'] );

		if( $checkperm && ! WPF()->usergroup->can_manage_forum() ) {
			WPF()->notice->add( 'Permission denied for delete forum', 'error' );

			return false;
		}

		$forumids   = $this->get_childs( $forumid );
		$forumids[] = $forumid;
		foreach( $forumids as $forumid ) $this->__delete( $forumid );

		$this->delete_tree_cache();
		wpforo_clean_cache();
		WPF()->notice->add( 'Your forum successfully deleted', 'success' );

		return true;
	}

	private function __delete( $forumid ) {
		$forumid = intval( $forumid );
		// START delete topic posts include first post
		if( $topicids = WPF()->db->get_col( "SELECT `topicid` FROM " . WPF()->tables->topics . " WHERE `forumid` = " . $forumid ) ) {
			foreach( $topicids as $topicid ) {
				WPF()->topic->delete( $topicid, false, false );
			}
		}
		// END delete topic posts include first post

		WPF()->db->delete( WPF()->tables->forums, [ 'forumid' => $forumid ], [ '%d' ] );

		do_action( 'wpforo_after_delete_forum', $forumid );
	}

	public function merge( $forumid = 0, $mergeid = 0 ) {
		$forumid = intval( $forumid );
		$mergeid = intval( $mergeid );

		if( ! $forumid && isset( $_REQUEST['id'] ) ) $forumid = intval( $_REQUEST['id'] );
		if( ! $mergeid && isset( $_REQUEST['forum']['mergeid'] ) ) $mergeid = intval( $_REQUEST['forum']['mergeid'] );

		if( ! $forumid || ! $mergeid ) return false;

		if( $child_forumids = $this->get_child_forums( $forumid ) ) {
			$forumids = implode( ',', $child_forumids );
			$merge_layout = $this->get_layout( $mergeid );

			if( ! WPF()->db->query( "UPDATE " . WPF()->tables->forums . " SET `parentid` = " . $mergeid . ", `layout` = " . $merge_layout . " WHERE `forumid` IN(" . $forumids . ")" ) ) {
				WPF()->notice->add( 'Forum merging error', 'error' );

				return false;
			}

            if( $childs = $this->get_childs( $forumid ) ){
	            WPF()->db->query( "UPDATE " . WPF()->tables->forums . " SET `layout` = " . $merge_layout . " WHERE `forumid` IN(" . implode( ',', $childs ) . ")" );
            }
		}

		WPF()->db->update( WPF()->tables->topics, [ 'forumid' => $mergeid ], [ 'forumid' => $forumid ], [ '%d' ], [ '%d' ] );
		WPF()->db->update( WPF()->tables->posts,  [ 'forumid' => $mergeid ], [ 'forumid' => $forumid ], [ '%d' ], [ '%d' ] );

		if( WPF()->db->delete( WPF()->tables->forums, [ 'forumid' => $forumid ], [ '%d' ] ) ) {

            do_action( 'wpforo_after_merge_forum', $forumid, $mergeid );

			$this->delete_tree_cache();
			wpforo_clean_cache( 'forum' );
			WPF()->notice->add( 'Forum is successfully merged', 'success' );
			return true;
		}

		WPF()->notice->add( 'Forum merging error', 'error' );

		return false;
	}

	public function rebuild_last_infos( $forumid, $update_parent_forums = true ) {
		if( ! $forumid = intval( $forumid ) ) return;

		$last_topicid    = 0;
		$last_postid     = 0;
		$last_userids    = [];
		$last_post_date  = '0000-00-00 00:00:00';
		$last_users_number = apply_filters( 'wpforo_item_participant_avatars_count', 3 );

		if( $last_topics = WPF()->topic->get_topics( [
			                                             'forumid'   => $forumid,
			                                             'status'    => 0,
			                                             'private'   => 0,
			                                             'orderby'   => 'topicid',
			                                             'order'     => 'DESC',
			                                             'row_count' => 1,
		                                             ] ) ) {
			$last_topic   = $last_topics[0];
			$last_topicid = $last_topic['topicid'];
		}

		// Collect participant userids from current forum and child forums
		// Don't use simple GROUP BY, it requires aggregation using INNER JOIN
		// otherwise it returns unexpected result with missing userids
		$forumids = WPF()->forum->get_forumid_and_childids( $forumid );
		$sql = "SELECT p.userid FROM `" . WPF()->tables->posts . "` p
				INNER JOIN (
				    SELECT `userid`, MAX(`created`) AS LatestCreated
				    FROM `" . WPF()->tables->posts . "`
				    WHERE `status` = 0 AND `private` = 0 AND `forumid` IN (" . esc_sql( implode( ',', $forumids ) ) . ")
				    GROUP BY `userid`
				) AS latest_posts ON p.userid = latest_posts.userid AND p.created = latest_posts.LatestCreated
				WHERE p.`status` = 0 AND p.`private` = 0 AND p.`forumid` IN (" . esc_sql( implode( ',', $forumids ) ) . ")
				ORDER BY p.`created` DESC, p.`postid` DESC LIMIT " . intval($last_users_number);

		if( $last_posts = WPF()->db->get_results( $sql, ARRAY_A ) ) {
			foreach ( $last_posts as $last_post ){
				$last_userids[] = $last_post['userid'];
			}
		}

		$sql = "SELECT `postid`, `created` FROM `" . WPF()->tables->posts . "` WHERE `status` = 0 AND `private` = 0 AND `forumid` IN(" . esc_sql( implode( ',', $forumids ) ) . ") ORDER BY `created` DESC, `postid` DESC LIMIT 1";
		if( $last_post = WPF()->db->get_row( $sql, ARRAY_A ) ) {
			$last_postid    = $last_post['postid'];
			$last_post_date = $last_post['created'];
		}

		//Update last information of current forum only (use its own and child participants)
		$sql = "UPDATE `" . WPF()->tables->forums . "`
                SET `last_topicid` = %d,
                `last_postid` = %d,
                `last_userid` = %s,
                `last_post_date` = %s
                WHERE `forumid` = " . intval( $forumid );
		WPF()->db->query( WPF()->db->prepare( $sql, $last_topicid, $last_postid, implode(',', $last_userids), $last_post_date ) );
		wpforo_clean_cache( 'forum-soft', $forumid);

		if( $update_parent_forums ){
			// Update parent forums last info if topics and posts are changed.
			// Parent forums must not be updated on [reset_forums_stats] action and on recursive calls.
			$parent_ids = [];
			$this->get_parents( $forumid, $parent_ids );
			$parent_ids = array_unique( array_filter( array_map( 'wpforo_bigintval', $parent_ids ) ) );
			if( !empty( $parent_ids ) ) {
				// Remove current forumid to only update the parent forums
				// and prevent updating last userids with parent forum participants
				if ( ( $current_forumid_key = array_search( $forumid, $parent_ids ) ) !== false ) {
					unset( $parent_ids[ $current_forumid_key ] );
				}
				if( !empty( $parent_ids ) ){
					foreach ( $parent_ids as $parent_id ){
						$this->rebuild_last_infos($parent_id, false);
						wpforo_clean_cache( 'forum-soft', $parent_id );
					}
					wpforo_clean_cache( 'forum-soft' );
				}
			}
		}
	}

	public function rebuild_stats( $forumid ) {
		if( ! $forumid = intval( $forumid ) ) return false;
		$topics = WPF()->topic->get_count( [ 'forumid' => $forumid, 'status' => 0, 'private' => 0 ] );
		$posts  = WPF()->post->get_count( [ 'forumid' => $forumid, 'status' => 0, 'private' => 0 ] );

		if( false !== WPF()->db->update( WPF()->tables->forums, [ 'topics' => $topics, 'posts' => $posts ], [ 'forumid' => $forumid ], [ '%d', '%d' ], [ '%d' ] ) ) {
            $parent_ids = $this->get_parent_forumids_static($forumid);
            if( !empty( $parent_ids ) ){
                foreach( $parent_ids as $parent_id ){
                    wpforo_clean_cache( 'forum-soft', $parent_id );
                }
            }
            wpforo_clean_cache( 'forum-soft' );
			return true;
		}

		return false;
	}

    function get_parent_forumids_static( $forumid ){
        $parent_ids = [];
        $level_0 = wpforo_forum( $forumid );
        if( wpfval( $level_0, 'parentid') ) {
            $level_1 = wpforo_forum( $level_0['parentid'] );
            $parent_ids[] = $level_1['forumid'];
            if( wpfval( $level_1, 'parentid') ){
                $level_2 = wpforo_forum( $level_1['parentid'] );
                $parent_ids[] = $level_2['forumid'];
                if( wpfval( $level_2, 'parentid') ){
                    $level_3 = wpforo_forum( $level_2['parentid'] );
                    $parent_ids[] = $level_3['forumid'];
                }
            }
        }
        return $parent_ids;
    }

	function _get_forum( $args ) {
		$forum = [];
		if( ! $args ) return $forum;

		$default = [
			'forumid' => null,
			'slug'    => '',
			'status'  => null,
			'type'    => 'all',
		];
		if( ! is_array( $args ) ) {
			if( is_numeric( $args ) ) {
				$default['forumid'] = intval( $args );
				if( $default['forumid'] === WPF()->current_object['forumid'] ) return WPF()->current_object['forum'];
			} elseif( is_string( $args ) ) {
				$default['slug'] = trim( (string) $args );
				if( $default['slug'] && $default['slug'] === wpfval( WPF()->current_object['forum'], 'slug' ) ) return WPF()->current_object['forum'];
			}
		}
		$args = wpforo_parse_args( $args, $default );

		$wheres = [];
		if( $args['forumid'] ) $wheres[] = "`forumid` = " . intval( $args['forumid'] );
		if( $args['slug'] ) $wheres[] = "`slug` = '" . esc_sql( $args['slug'] ) . "'";
		if( ! is_null( $args['status'] ) ) $wheres[] = "`status` = " . intval( $args['status'] );
		switch( $args['type'] ) {
			case 'category':
				$wheres[] = "`is_cat` = 1";
			break;
			case 'forum':
				$wheres[] = "`is_cat` = 0";
			break;
		}

		if( $wheres ) {
			$sql = "SELECT * FROM `" . WPF()->tables->forums . "` WHERE " . implode( " AND ", $wheres );
			if( $forum = WPF()->db->get_row( $sql, ARRAY_A ) ) {
				if( ! $forum['layout'] ) $forum['layout'] = 1;
				$forum['url'] = $this->get_forum_url( $forum );
                if( $forum['cover'] = wpforo_bigintval( wpfval($forum, 'cover') ) ){
	                $image = wp_get_attachment_image_src( $forum['cover'], 'full' );
	                $forum['cover_url'] = (string) wpfval($image, 0);
                }else{
	                $forum['cover_url'] = '';
                }
			}
		}

		return apply_filters( 'wpforo_get_forum', $forum, $args );
	}

	public function get_forum( $args ) {
		return wpforo_ram_get( [ $this, '_get_forum' ], $args );
	}

	function get_forums( $args = [], &$items_count = 0, $count = false ) {
		$cache = WPF()->cache->on('forum');

		$default = [
			'include'        => [],  // array( 2, 10, 25 )
			'exclude'        => [],  // array( 2, 10, 25 )
			'parent_include' => [],  // array( 2, 10, 25 )
			'parent_exclude' => [],  // array( 2, 10, 25 )
			'parentid'       => null,
			'parent_slug'    => '',
			'status'         => null,
			'type'           => 'all',   // category, forum
			'orderby'        => 'order', // order by `field`
			'order'          => 'ASC',   // ASC DESC
			'offset'         => null,    // OFFSET
			'row_count'      => null,    // ROW COUNT
			'layout'         => null,    // 1, 2, 3, 4
		];

		$args = wpforo_parse_args( $args, $default );
		extract( $args, EXTR_OVERWRITE );

		$include        = wpforo_parse_args( $include );
		$exclude        = wpforo_parse_args( $exclude );
		$parent_include = wpforo_parse_args( $parent_include );
		$parent_exclude = wpforo_parse_args( $parent_exclude );

		$sql    = "SELECT * FROM `" . WPF()->tables->forums . "`";
		$wheres = [];

		if( ! empty( $include ) ) $wheres[] = "`forumid` IN(" . implode( ', ', array_map( 'intval', $include ) ) . ")";
		if( ! empty( $exclude ) ) $wheres[] = "`forumid` NOT IN(" . implode( ', ', array_map( 'intval', $exclude ) ) . ")";
		if( ! empty( $parent_include ) ) $wheres[] = "`parentid` IN(" . implode( ', ', array_map( 'intval', $parent_include ) ) . ")";
		if( ! empty( $parent_exclude ) ) $wheres[] = "`parentid` NOT IN(" . implode( ', ', array_map( 'intval', $parent_exclude ) ) . ")";
		if( $parentid != null ) $wheres[] = " `parentid` = " . intval( $parentid );
		if( $layout != null ) $wheres[] = " `layout` = " . intval( $layout );
		if( $status != null ) $wheres[] = " `status` = " . intval( $status );

		if( $type === 'category' ) {
			$wheres[] = " `is_cat` = 1";
		} elseif( $type === 'forum' ) {
			$wheres[] = " `is_cat` = 0";
		}

		if( $parent_slug != '' ) $wheres[] = "`slug` = '" . esc_sql( $parent_slug ) . "'";

		if( ! empty( $wheres ) ) $sql .= " WHERE " . implode( " AND ", $wheres );

		if( $count ) {
			$item_count_sql = preg_replace( '#SELECT.+?FROM#isu', 'SELECT count(*) FROM', $sql );
			//				$item_count_sql = preg_replace('#ORDER.+$#is', '', $item_count_sql);
			if( $item_count_sql ) $items_count = WPF()->db->get_var( $item_count_sql );
		}

		$sql .= esc_sql( " ORDER BY `$orderby` " . $order );

		if( $row_count != null ) {
			if( $offset != null ) {
				$sql .= esc_sql( " LIMIT $offset,$row_count" );
			} else {
				$sql .= esc_sql( " LIMIT $row_count" );
			}
		}

		if( $cache ) {
			$object_key   = md5( $sql . WPF()->current_user_groupid );
			$object_cache = WPF()->cache->get( $object_key );
			if( ! empty( $object_cache ) ) {
				$items_count = $object_cache['items_count'];

				return $object_cache['items'];
			}
		}

		$forums = WPF()->db->get_results( $sql, ARRAY_A );

        $forums = array_map( function( $forum ){
	        if( $forum['cover'] = wpforo_bigintval( wpfval($forum, 'cover') ) ){
		        $image = wp_get_attachment_image_src( $forum['cover'], 'full' );
		        $forum['cover_url'] = (string) wpfval($image, 0);
	        }else{
		        $forum['cover_url'] = '';
	        }

            return $forum;
        }, $forums );

		$forums = apply_filters( 'wpforo_get_forums', $forums, $args );

		if( $cache && isset( $object_key ) && ! empty( $forums ) ) {
			self::$cache['forums'][ $object_key ]['items']       = $forums;
			self::$cache['forums'][ $object_key ]['items_count'] = $items_count;
		}

		return $forums;
	}

	function search( $needle, $fields = [] ) {

		if( $needle ) {
			$needle = sanitize_text_field( $needle );
			if( empty( $fields ) ) {
				$fields = [
					'title',
					'description',
					'meta_key',
					'meta_desc',
				];
			}

			$sql    = "SELECT `forumid` FROM `" . WPF()->tables->forums . "`";
			$wheres = [];

			foreach( $fields as $field ) {
				$wheres[] = "`" . esc_sql( $field ) . "` LIKE '%" . esc_sql( $needle ) . "%'";
			}

			$sql .= " WHERE " . implode( " OR ", $wheres );

			return WPF()->db->get_col( $sql );
		}

		return [];
	}

	function update_hierarchy() {
		if( is_array( $_REQUEST['forum'] ) && ! empty( $_REQUEST['forum'] ) ) {
			$i = 0;
			foreach( $_REQUEST['forum'] as $hierarchy ) {

				extract( $hierarchy );

				if( ! isset( $forumid ) || ! $forumid = intval( $forumid ) ) continue;

				if( false !== WPF()->db->update( WPF()->tables->forums, [
					                                                      'parentid' => ( isset( $parentid ) ? intval( $parentid ) : 0 ),
					                                                      'order'    => ( isset( $order ) ? intval( $order ) : 0 ),
				                                                      ], [ 'forumid' => intval( $forumid ) ], [
					                                 '%d',
					                                 '%d',
				                                 ], [ '%d' ] ) ) {
					$i ++;
				}

				if( isset( $parentid ) && $parentid = intval( $parentid ) ) {
					$layout = WPF()->db->get_var( "SELECT `layout` FROM `" . WPF()->tables->forums . "` WHERE `forumid` = " . intval( $parentid ) );
					WPF()->db->query( "UPDATE `" . WPF()->tables->forums . "` SET `layout` = " . intval( $layout ) . " WHERE `forumid` = " . intval( $forumid ) );
				}

			}

			WPF()->db->query( "UPDATE `" . WPF()->tables->forums . "` SET `is_cat` = 0" );
			WPF()->db->query( "UPDATE `" . WPF()->tables->forums . "` SET `is_cat` = 1 WHERE `parentid` = 0" );

			if( $i ) {
				$this->delete_tree_cache();
				WPF()->notice->add( 'Forum hierarchy successfully updated', 'success' );
			} else {
				WPF()->notice->add( 'Cannot update forum hierarchy', 'error' );
			}

		}
	}

	/**
	 * @param int $forumid
	 *
	 * @return array array of child forumids
	 */
	public function get_childs( $forumid ) {
		$key = [ 'forums', 'get_childs', $forumid ];
		if( WPF()->ram_cache->exists( $key ) ) return WPF()->ram_cache->get( $key );

		if( wpforo_is_db_mysql8() ) {
			$forumids = $this->__get_childs_mysql8( $forumid );
		} else {
			$forumids = $this->__get_childs( $forumid );
		}

		WPF()->ram_cache->set( $key, $forumids );

		return $forumids;
	}

	/**
	 * @param int $forumid
	 *
	 * @return array array of child forumids
	 */
	private function __get_childs( $forumid ) {
		if( $forumid = intval( $forumid ) ) {
			$sql = "SELECT GROUP_CONCAT(
                        @id :=  (
                        SELECT GROUP_CONCAT(`forumid` ORDER BY `order` ASC)
                            FROM  `" . WPF()->tables->forums . "`
                            WHERE FIND_IN_SET( `parentid`, @id )
                        )
                    ) AS forumids
                    FROM ( SELECT  @id := %s ) vars
                    STRAIGHT_JOIN `" . WPF()->tables->forums . "`
                    WHERE @id IS NOT NULL";
			$sql = WPF()->db->prepare( $sql, $forumid );

			$forumids = explode( ',', (string) WPF()->db->get_var( $sql ) );

			return array_values( array_unique( array_filter( array_map( 'intval', $forumids ) ) ) );
		}

		return [];
	}

	/**
	 * @param int $forumid
	 *
	 * @return array array of child forumids
	 */
	private function __get_childs_mysql8( $forumid ) {
		if( $forumid = intval( $forumid ) ) {
			$sql = "WITH RECURSIVE `forum_path` AS (
              SELECT `forumid`, `parentid`
                FROM `" . WPF()->tables->forums . "`
                WHERE `parentid` = %d
              UNION
              SELECT f.`forumid`, f.`parentid`
                FROM `" . WPF()->tables->forums . "` AS f
                INNER JOIN `forum_path` AS fp ON fp.`parentid` <> fp.`forumid` AND fp.`forumid` = f.`parentid`
            ) SELECT * FROM `forum_path`";
			$sql = WPF()->db->prepare( $sql, $forumid );

			$forumids = WPF()->db->get_col( $sql );

			return array_values( array_unique( array_filter( array_map( 'intval', $forumids ) ) ) );
		}

		return [];
	}

	// get forums tree for drop down menu

	/**
	 * Returns depth for this item.
	 *
	 * @param int $forumid
	 *
	 * @param int $depth
	 *
	 * @since 1.0.0
	 *
	 */
	function count_depth( $forumid, &$depth ) {
		if( wpforo_is_db_mysql8() ) {
			$depth = $this->__count_depth_mysql8( $forumid );

			return;
		}

		$sql = "SELECT `parentid` FROM `" . WPF()->tables->forums . "` WHERE `forumid` = %d AND `parentid` <> %d";
		$sql = WPF()->db->prepare( $sql, intval( $forumid ), intval( $forumid ) );

		if( WPF()->ram_cache->exists( $sql ) ) {
			$parentid = WPF()->ram_cache->get( $sql );
		} else {
			$parentid = WPF()->db->get_var( $sql );
			WPF()->ram_cache->set( $sql, $parentid );
		}

		if( $parentid ) {
			$depth ++;
			$this->count_depth( $parentid, $depth );
		}
	}

	private function __count_depth_mysql8( $forumid ) {
		$sql = "WITH RECURSIVE `forum_path` AS(
            SELECT `forumid`, `parentid`, 0 AS `depth`
            FROM `" . WPF()->tables->forums . "`
            WHERE `forumid` = %d
            UNION
            SELECT f.`forumid`, f.`parentid`, `depth` + 1
            FROM `" . WPF()->tables->forums . "` f
            INNER JOIN `forum_path` fp ON fp.`parentid` <> fp.`forumid` AND fp.`parentid` = f.`forumid`
        ) SELECT `depth` FROM `forum_path` ORDER BY `depth` DESC LIMIT 1";

		return (int) WPF()->db->get_var( WPF()->db->prepare( $sql, intval( $forumid ) ) );
	}

	/**
	 * @param int $forumid
	 *
	 * @return array
	 */
	function get_child_forums( $forumid ) {
		$sql = "SELECT `forumid`
                  FROM `" . WPF()->tables->forums . "`
                  WHERE `parentid` = " . intval( $forumid ) . "
                  AND `forumid` <> " . intval( $forumid ) . "
                  ORDER BY `order`";

		return array_map( 'intval', WPF()->db->get_col( $sql ) );
	}

	function forum_list( $forumids, $type = 'select_box', $selected = [], $cats = true, $disabled = [] ) {
		static $old_depth;
		$disabled = (array) $disabled;
		$selected = (array) $selected;

		foreach( $forumids as $forumid ) {
			$forumid = intval( $forumid );
			if( ! $forumid || ( ! wpforo_is_admin() && ! WPF()->perm->forum_can( 'vf', $forumid ) ) || ( wpforo_is_admin() && ! WPF()->usergroup->can_manage_forum() ) ) continue;
            if( $type === 'subscribe_manager_form'  && ! WPF()->perm->forum_can( 'sb', $forumid ) ) continue;
			$depth = 0;
			$this->count_depth( $forumid, $depth );
            $forum = wpforo_forum( $forumid );
            $name = wpfval($forum, 'title');
            if( $type === 'select_box' ) { ?>
                <option value="<?php echo $forumid ?>" <?php echo( ( ! $cats && $depth == 0 || ( in_array( $forumid, $disabled ) ) ) ? ' disabled ' : '' );
                echo( in_array( $forumid, $selected ) ? ' selected ' : '' ) ?> > <?php echo esc_html( str_repeat( '— ', $depth ) . trim( (string) $name ) ) ?></option><?php
			} elseif( $type === 'drag_menu' ) {
				switch( $forum['layout'] ) {
					case 2:
						$layout_name = 'Simplified Layout';
					break;
					case 3:
						$layout_name = 'Q&A Layout';
					break;
					case 4:
						$layout_name = 'Threaded Layout';
					break;
					default:
						$layout_name = 'Extended Layout';
				}
				?>

                <li id="menu-item-<?php echo $forumid ?>" class="menu-item menu-item-depth-<?php echo esc_attr( $depth ) ?>">
                    <input id="forumid-<?php echo $forumid ?>" type="hidden" name="forum[<?php echo $forumid ?>][forumid]"/>
                    <input id="parentid-<?php echo $forumid ?>" type="hidden" name="forum[<?php echo $forumid ?>][parentid]"/>
                    <input id="order-<?php echo $forumid ?>" type="hidden" name="forum[<?php echo $forumid ?>][order]"/>
                    <dl class="menu-item-bar">
                        <dt class="menu-item-handle forum_width">
                            <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( 'wpforo_dashboard_forums_item_name', esc_html($name), $forumid); ?></span>
                            <span class="item-controls">
                            	<span class="wpforo-cat-layout"><?php echo( $depth != 0 ? __( 'Topics', 'wpforo' ) . '&nbsp;(' . intval( $forum['topics'] ) . ')&nbsp;,&nbsp;' . __( 'Posts', 'wpforo' ) . '&nbsp;(' . intval( $forum['posts'] ) . ')&nbsp; | &nbsp;' : '' ) ?><?php echo( $depth == 0 ? '(&nbsp;<i>' . esc_html( $layout_name ) . '</i>&nbsp;)&nbsp; | &nbsp;' : '' ); ?></span>
                                <span class="menu_add">
                                    <a href="<?php echo admin_url( 'admin.php?page=' . wpforo_prefix_slug( 'forums' ) . '&action=add&parentid=' . $forumid ) ?>">
                                        <span class="dashicons dashicons-plus" title="<?php if( $depth ) : _e( 'Add a new Subforum', 'wpforo' ); else: _e( 'Add a new Forum in this Category', 'wpforo' ); endif; ?>"></span>
                                    </a>
                                </span> &nbsp;|&nbsp;
                                <span class="menu_copy">
                                    <a href="<?php echo wp_nonce_url( admin_url( 'admin.php?page=' . wpforo_prefix_slug( 'forums' ) . '&wpfaction=forum_copy&forumid=' . $forumid ), 'forum_copy', '_wpfnonce' ) ?>" onclick="return confirm( '<?php wpforo_phrase( 'Are you sure you want to copy this forum?' ) ?>' )">
                                        <span class="dashicons dashicons-admin-page" title="<?php _e( 'Copy', 'wpforo' ); ?>"></span>
                                    </a>
                                </span> &nbsp;|&nbsp;
                                <span class="menu_edit">
                                    <a href="<?php echo admin_url( 'admin.php?page=' . wpforo_prefix_slug( 'forums' ) . '&id=' . $forumid . '&action=edit' ) ?>">
                                        <span class="dashicons dashicons-edit" title="<?php _e( 'edit', 'wpforo' ) ?>"></span>
                                    </a>
                                </span>&nbsp;|&nbsp;
                                <?php if( WPF()->usergroup->can_manage_forum() ): ?>
                                    <span class="menu_delete">
                                        <a href="<?php echo admin_url( 'admin.php?page=' . wpforo_prefix_slug( 'forums' ) . '&id=' . $forumid . '&action=del' ) ?>">
                                            <span class="dashicons dashicons-trash" title="<?php _e( 'delete', 'wpforo' ) ?>"></span>
                                        </a>
                                    </span>&nbsp;|&nbsp;
                                <?php endif; ?>
								<span class="menu_view">
                                    <a href="<?php echo esc_url( (string) wpforo_forum( $forumid, 'url' ) ); ?>">
                                        <span class="dashicons dashicons-visibility" title="<?php _e( 'View', 'wpforo' ) ?>"></span>
                                    </a>
                                </span>

                            </span>
                        </dt>
                    </dl>
                    <ul class="menu-item-transport"></ul>
                </li>

				<?php
			} elseif( $type === 'front_list' ) {
				if( isset( $old_depth ) && $old_depth == $depth ) echo '</dd><dd>';
				if( isset( $old_depth ) && $old_depth < $depth ) echo '<dl><dd>';
				if( isset( $old_depth ) && $old_depth > $depth ) echo '</dd></dl>';
				$old_depth = $depth;
                printf(
	                '<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>',
	                ( in_array( $forumid, $selected ) ? 'wpf-dl-current' : '' ),
	                esc_url( (string) wpforo_forum( $forumid, 'url' ) ),
                    ( ($forum['color'] = trim( (string) $forum['color'] )) ? 'color: ' . $forum['color'] . ';' : ''),
	                $forum['icon'],
	                esc_html( $name )
                );
			} elseif( $type === 'subscribe_manager_form' ) {
				?>
                <li>
					<?php if( $depth > 0 ) :
						$forum_topic_attr = '';
						$forum_attr = '';
						if( key_exists( $forumid, $selected ) ) {
							if( $selected[ $forumid ] === 'forum-topic' ) {
								$forum_topic_attr = ' checked ';
							} elseif( $selected[ $forumid ] === 'forum' ) {
								$forum_attr = ' checked ';
							}
						}
						?>
                        <div class="wpf-sbs-div wpf-sbs-checkbox">
                            <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 class="wpf-sbsp" for="wpf_sbs_allposts_<?php echo $forumid ?>"><?php wpforo_phrase( 'topics and posts' ) ?></label>
                            <input id="wpf_sbs_alltopics_<?php echo $forumid ?>" type="checkbox" name="wpforo[forums][<?php echo $forumid ?>]" value="forum" <?php echo $forum_attr ?>><label class="wpf-sbst" for="wpf_sbs_alltopics_<?php echo $forumid ?>"><?php wpforo_phrase( 'topics' ) ?></label>
                        </div>
					<?php endif; ?>
                    <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( (string) $name ) ?></div>
                </li>
				<?php
			}
			$subforums = $this->get_child_forums( $forumid );
			if( ! empty( $subforums ) ) {
				$this->forum_list( $subforums, $type, $selected, true, $disabled );
			}
		}
	}

	function tree( $type = 'select_box', $cats = true, $selected = [], $cache = true, $disabled = [], $parentids = [] ) {
		$disabled  = (array) $disabled;
		$selected  = (array) $selected;
		$parentids = (array) $parentids;
		if( ! $parentids ) $parentids = WPF()->db->get_col( "SELECT `forumid` FROM `" . WPF()->tables->forums . "` WHERE `parentid` = 0 ORDER BY `order`" );
		if( ! empty( $parentids ) ) {
			if( $cache && ! wpforo_is_admin() ) {
				$key                    = md5( serialize( $parentids ) . $type . (int) $cats . WPF()->current_user_groupid );
				$html                   = wpforo_get_option( 'forum_tree_' . $key, '' );
				$pattern_strip_selected = '#(<(?:option|input)[^<>]*?)[\r\n\t\s]*(?:selected|checked)[^\r\n\t\s]*?((?:[\r\n\t\s][^<>]*)?>)#isu';

				if( $html ) {
					if( $type === 'select_box' || $type === 'subscribe_manager_form' ) $html = preg_replace( $pattern_strip_selected, '$1$2', (string) $html );
					if( $selected ) {
						if( $type === 'select_box' ) {
							foreach( $selected as $sfid ) {
								$html = str_replace( 'value="' . $sfid . '"', 'value="' . $sfid . '" selected ', $html );
							}
						} elseif( $type === 'subscribe_manager_form' ) {
							foreach( $selected as $forumid => $stype ) {
								$html = preg_replace( '#(name=[\'"]wpforo\[forums\]\[' . intval( $forumid ) . '\][\'"][^<>]*?value=[\'"]' . preg_quote( $stype ) . '[\'"]|value=[\'"]' . preg_quote( $stype ) . '[\'"][^<>]*?name=[\'"]wpforo\[forums\]\[' . intval( $forumid ) . '\][\'"])#isu', '$1 checked', (string) $html );
							}
						}
					}
					echo $html;
				} elseif( function_exists( 'ob_start' ) ) {
					ob_start();
					$this->forum_list( $parentids, $type, $selected, $cats, $disabled );
					$html       = ob_get_clean();
					$cache_html = ( $type === 'select_box' ? preg_replace( $pattern_strip_selected, '$1$2', (string) $html ) : $html );
					if( $type !== 'drag_menu' ) wpforo_update_option( 'forum_tree_' . $key, $cache_html );
					echo $html;
				}
			} else {
				$this->forum_list( $parentids, $type, $selected, $cats, $disabled );
			}
		}
	}

	public function delete_tree_cache() {
		WPF()->db->query( "DELETE FROM `" . WPF()->db->options . "` WHERE `option_name` REGEXP '^" . wpforo_prefix( 'forum_tree_' ) . "'" );
	}

	function parentid( $topicid = 0 ) {
		if( isset( $_GET['page'] ) && preg_match( '#^wpforo-(?:\d+-)?forums$#iu', (string) $_GET['page'] ) ) {
			if( isset( $_GET['id'] ) ) return WPF()->db->get_var( "SELECT `parentid` FROM `" . WPF()->tables->forums . "` WHERE `forumid` = " . intval( $_GET['id'] ) );
		} elseif( isset( $_GET['page'] ) && preg_match( '#^wpforo-(?:\d+-)?topics$#iu', (string) $_GET['page'] ) ) {
			if( isset( $_GET['id'] ) ) return WPF()->db->get_var( "SELECT `forumid` FROM `" . WPF()->tables->topics . "` WHERE `topicid` = " . wpforo_bigintval( $_GET['id'] ) );
		} else {
			if( $topicid ) return WPF()->db->get_var( "SELECT `forumid` FROM `" . WPF()->tables->topics . "` WHERE `topicid` = " . wpforo_bigintval( $topicid ) );
		}
	}

	function permissions() {
		$access_arr = WPF()->perm->get_accesses();
		if( ! empty( $access_arr ) ) {

			if( isset( $_GET['id'] ) ) {
				if( $permissions_srlz = WPF()->db->get_var( "SELECT `permissions` FROM `" . WPF()->tables->forums . "` WHERE `forumid` = " . intval( $_GET['id'] ) ) ) {
					$permissions_arr = unserialize( $permissions_srlz );
				}
			}

			if( $usergroups = WPF()->db->get_results( "SELECT `groupid`, `name` FROM `" . WPF()->tables->usergroups . "`", ARRAY_A ) ) {
				foreach( $usergroups as $usergroup ) {
					extract( $usergroup, EXTR_OVERWRITE );
					echo '
						<tr>
							<td>' . esc_html( $name ) . '</td>
							<td>
								<select name="forum[permission][' . intval( $groupid ) . ']">';
					foreach( $access_arr as $value ) {

						echo '<option value="' . esc_attr(
								$value['access']
							) . '" ' . ( ( 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(
							     __( $value['title'], 'wpforo' )
						     ) . '</option>';
					}
					echo '
								</select>
							</td>
						</tr>
					';
				}


			}
		}
	}

	/**
	 * array get_counts(array or id(num))
	 *
	 * @param array defined arguments array for returning
	 *
	 * @return array array('topics' => 0, 'posts' => 0)
	 * @since 1.0.0
	 *
	 */
	function get_counts( $forumids ): array {
		$result   = [ 'topics' => 0, 'posts' => 0 ];
		$forumids = array_filter( array_map( 'intval', (array) $forumids ) );
		if( $forumids ) {
			$sql              = "SELECT SUM(`topics`) as topics, SUM(`posts`) as posts FROM `" . WPF()->tables->forums . "` WHERE `forumid` IN(" . implode( ',', $forumids ) . ")";
			$row              = (array) WPF()->db->get_row( $sql, ARRAY_A );
			$result['topics'] = (int) wpfval( $row, 'topics' );
			$result['posts']  = (int) wpfval( $row, 'posts' );
		}

		return $result;
	}

	/**
	 * array get_layout(array)
	 *
	 * @param array defined arguments array for returning
	 *
	 * @return int layout id
	 * @since 1.0.0
	 *
	 */
	function get_layout( $args = null ) {
		if( is_null( $args ) ) $args = WPF()->current_object['forum'];
		if( ! $args ) return 1;
		if( $layout = (int) wpfval( $args, 'layout' ) ) return $layout;
		if( is_array( $args ) ) {
			$default = [
				'forumid' => null, // forum id
				'topicid' => null, // topic id
				'postid'  => null, // post id
			];
		} else {
			$default = [
				'forumid' => $args,    // forumid
				'topicid' => null,    // topic id
				'postid'  => null,    // post id
			];
		}
		$args = wpforo_parse_args( $args, $default );
		if( ! empty( $args ) ) {
			extract( $args, EXTR_OVERWRITE );

			if( $args['forumid'] ) {
				$layout = (int) wpforo_forum( $args['forumid'], 'layout' );

				return ( $layout ?: 1 );
			} elseif( $args['topicid'] ) {
				$sql     = "SELECT `forumid` FROM `" . WPF()->tables->topics . "` WHERE `topicid` = " . intval( $args['topicid'] );
				$forumid = WPF()->db->get_var( $sql );

				return $this->get_layout( [ 'forumid' => $forumid ] );
			} elseif( $args['postid'] ) {
				$sql     = "SELECT `forumid` FROM `" . WPF()->tables->posts . "` WHERE `postid` = " . intval( $args['postid'] );
				$forumid = WPF()->db->get_var( $sql );

				return $this->get_layout( [ 'forumid' => $forumid ] );
			}
		}

		return 1;
	}

	function get_forum_url( $forum ) {

		if( ! is_array( $forum ) ) {
			if( is_numeric( $forum ) ) {
				$forum = $this->get_forum( $forum );
			} else {
				$forum = [ 'slug' => $forum ];
			}
		}

		if( is_array( $forum ) && ! empty( $forum ) ) {
			return wpforo_home_url( utf8_uri_encode( $forum['slug'] ) );
		} else {
			return wpforo_home_url();
		}
	}

	function get_parents( $forumid, &$relative_ids ) {
		if( wpforo_is_db_mysql8() ) {
			$relative_ids = $this->__get_parents_mysql8( $forumid );

			return;
		}

		$sql = "SELECT `parentid`, `forumid` FROM `" . WPF()->tables->forums . "` WHERE `forumid` = %d";
		$sql = WPF()->db->prepare( $sql, $forumid );
		if( WPF()->ram_cache->exists( $sql ) ) {
			$forum = WPF()->ram_cache->get( $sql );
		} else {
			$forum = WPF()->db->get_row( $sql, ARRAY_A );
			WPF()->ram_cache->set( $sql, $forum );
		}
		if( $forum ) {
			$relative_ids[] = $forum['forumid'];
			if( $forum['parentid'] ) {
				$this->get_parents( $forum['parentid'], $relative_ids );
			} else {
				$relative_ids = array_reverse( $relative_ids );
			}
		}
	}

	/**
	 * @param int $forumid
	 *
	 * @return array
	 */
	private function __get_parents_mysql8( $forumid ) {
		$sql = "WITH RECURSIVE `forum_path` AS(
                SELECT `forumid`, `parentid`, 0 AS `depth`
                FROM `" . WPF()->tables->forums . "`
                WHERE `forumid` = %d
                UNION
                SELECT f.`forumid`, f.`parentid`, `depth` + 1
                FROM `" . WPF()->tables->forums . "` f
                INNER JOIN `forum_path` fp ON fp.`parentid` <> fp.`forumid` AND fp.`parentid` = f.`forumid`
            ) SELECT `forumid` FROM `forum_path` ORDER BY `depth` DESC";
		$sql = WPF()->db->prepare( $sql, intval( $forumid ) );
		if( WPF()->ram_cache->exists( $sql ) ) {
			$relative_ids = WPF()->ram_cache->get( $sql );
		} else {
			$relative_ids = (array) WPF()->db->get_col( $sql );
			WPF()->ram_cache->set( $sql, $relative_ids );
		}

		return $relative_ids;
	}

	function get_count( $args = [] ) {
		$sql = "SELECT SQL_NO_CACHE COUNT(*) FROM `" . WPF()->tables->forums . "`";
		if( ! empty( $args ) ) {
			$wheres = [];
			foreach( $args as $key => $value ) $wheres[] = "`$key` = " . intval( $value );
			if( $wheres ) $sql .= " WHERE " . implode( ' AND ', $wheres );
		}

		return WPF()->db->get_var( $sql );
	}

	function get_lastinfo( $ids = [] ) {
		$lastinfo = [];
		if( ! empty( $ids ) ) {
			$ids      = implode( ',', array_map( 'intval', $ids ) );
			$lastinfo = WPF()->db->get_row( "SELECT `userid` as last_userid, `topicid` as last_topicid, `postid` as last_postid, `created` as last_post_date FROM `" . WPF()->tables->posts . "` WHERE `status` = 0 AND `private` = 0 AND forumid IN(" . $ids . ") ORDER BY `created` DESC LIMIT 1", ARRAY_A );
		}

		return $lastinfo;
	}

	function forums() {
		$forums = $this->get_forums( [ 'parentid' => 0 ] );

		return $this->children( $forums );
	}

	function children( $forums, $parentId = 0, $level = 0 ) {
		if( empty( $forums ) || ! is_array( $forums ) ) return;
		$items = [];
		$level = $level + 1;
		foreach( $forums as $forum ) {
			if( ! isset( $forum['forumid'] ) || ! WPF()->perm->forum_can( 'vf', $forum['forumid'] ) ) continue;
			$forum['level'] = $level + 1;
			if( $forum['parentid'] == $parentId ) {
				$children = $this->children( $forums, $forum['forumid'], $level );
				if( $children ) {
					$forum['children'] = $children;
				}
				$items[] = $forum;
			}
		}

		return $items;
	}

	function dropdown( $forums = [] ) {
		if( empty( $forums ) ) {
			$forums = $this->forums();
		}
		foreach( $forums as $forum ) {
			if( isset( $forum['level'] ) ) $forum['level'] = $forum['level'] - 2;
			$prefix = ( $forum['level'] == 0 ) ? '' : str_repeat( '&mdash;', $forum['level'] );
			echo '<option value="' . esc_attr( $forum['forumid'] ) . '"> ' . $prefix . '&nbsp;' . esc_html( $forum['title'] ) . '</option>';
			if( ! empty( $forum['children'] ) ) {
				$this->dropdown( $forum['children'] );
			}
		}
	}

	function private_forum( $forumid, $groupids = [] ) {
		if( $forumid = intval( $forumid ) ) {
			if( ! $groupids ) $groupids = WPF()->current_user_groupids;
			if( ($groupids = array_map( 'intval', (array) $groupids)) && !WPF()->perm->forum_can( 'vf', $forumid, $groupids ) ) return true;
		}

		return false;
	}
	
	public function get_forumid_and_childids( int $forumid, bool $check_permission = true ): array {
		$forumids = array_merge( [$forumid], $this->get_childs( $forumid ) );
		
		if( $check_permission ) $forumids = array_filter( $forumids, function( $forumid ){
			return WPF()->perm->forum_can( 'vf', $forumid );
		} );
		
		return $forumids;
	}
	
	public function get_sum_of_topics_posts( int $forumid, bool $check_permission = true ): array {
		$forumids = $this->get_forumid_and_childids( $forumid, $check_permission );
		
		return $this->get_counts( $forumids );
	}

	public function after_add_usergroup( $group ) {
		if( $group['groupid'] ) {
			if( $forums = $this->get_forums() ) {
				foreach( $forums as $forum ) {
					if( $permissions = (string) wpfval( $forum, 'permissions' ) ) {
						if( $permissions = maybe_unserialize( $permissions ) ) {
							$permissions[ $group['groupid'] ] = $group['access'];
							WPF()->db->update( WPF()->tables->forums, [ 'permissions' => serialize( $permissions ) ], [ 'forumid' => $forum['forumid'] ], [ '%s' ], [ '%d' ] );
						}
					}
				}
			}
		}
	}

	public function after_merge_forum( $forumid, $mergeid ) {
		$this->rebuild_last_infos( $mergeid );
		$this->rebuild_stats( $mergeid );
    }

	public function after_add_topic( $topic ) {
        if( !intval($topic['status']) && !intval($topic['private']) ){
	        $this->rebuild_last_infos( $topic['forumid'] );
	        $this->rebuild_stats( $topic['forumid'] );
        }
    }

	public function after_delete_topic( $topic ) {
        if( !intval($topic['status']) && !intval($topic['private']) ){
	        $this->rebuild_last_infos( $topic['forumid'] );
	        $this->rebuild_stats( $topic['forumid'] );
        }
    }

	public function after_topic_status_update( $topic ) {
		if( !intval($topic['private']) ){
			$this->rebuild_last_infos( $topic['forumid'] );
			$this->rebuild_stats( $topic['forumid'] );
		}
    }

	public function topic_private_update( $topicid ) {
        if( $topic = WPF()->topic->get_topic( $topicid ) ){
	        $this->rebuild_last_infos( $topic['forumid'] );
	        $this->rebuild_stats( $topic['forumid'] );
        }
    }

	public function after_merge_topic( $target, $current ) {
		$this->rebuild_last_infos( $target['forumid'] );
		$this->rebuild_stats( $target['forumid'] );
		$this->rebuild_last_infos( $current['forumid'] );
		$this->rebuild_stats( $current['forumid'] );
    }

	public function after_move_topic( $topic, $forumid ) {
		$this->rebuild_last_infos( $topic['forumid'] );
		$this->rebuild_stats( $topic['forumid'] );
		$this->rebuild_last_infos( $forumid );
		$this->rebuild_stats( $forumid );
    }

	public function after_add_post( $post, $topic, $forum ) {
        if( !intval( $post['status'] ) ){
	        $this->rebuild_last_infos( $forum['forumid'] );
	        $this->rebuild_stats( $forum['forumid'] );
        }
    }

	public function after_delete_post( $post ) {
		if( !intval( $post['status'] ) ){
			$this->rebuild_last_infos( $post['forumid'] );
			$this->rebuild_stats( $post['forumid'] );
		}
    }

	public function after_post_status_update( $post ) {
		if( !intval( $post['private'] ) ){
			$this->rebuild_last_infos( $post['forumid'] );
			$this->rebuild_stats( $post['forumid'] );
		}
    }
}

```
