<?php
/**
 * This file implements Category handling functions.
 *
 * This file is part of the evoCore framework - {@link http://evocore.net/}
 * See also {@link https://github.com/b2evolution/b2evolution}.
 *
 * @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
 *
 * @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
 * Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}.
 * Parts of this file are copyright (c)2004 by The University of North Carolina at Charlotte as
 * contributed by Jason Edgecombe {@link http://tst.uncc.edu/team/members/jason_bio.php}.
 *
 * @package evocore
 *
 * @todo implement CategoryCache based on LinkCache
 */
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );


/**
 * Create a new category
 *
 * This funtion has to handle all needed DB dependencies!
 *
 * @param string Category name
 * @param string Category ID ('NULL' as string(!) for root)
 * @param integer|NULL Blog ID (will be taken from parent cat, if empty)
 * @param string Category description
 * @param boolean Set to true if the new object needs to be added into the ChapterCache after it was created
 * @param integer Category order
 * @param boolean Is meta category?
 * @param string Name of default Item Type
 * @return integer ID of new created Category
 */
function cat_create( $cat_name, $cat_parent_ID, $cat_blog_ID = NULL, $cat_description = NULL, $add_to_cache = false, $cat_order = NULL, $subcat_ordering = NULL, $meta = false, $default_item_type_name = NULL )
{
	global $DB;

	load_class('chapters/model/_chapter.class.php', 'Chapter');

	if( ! $cat_blog_ID )
	{
		if( empty( $cat_parent_ID ) )
		{
			debug_die ( 'cat_create(-) missing parameters (cat_parent_ID)!' );
		}

		$ChapterCache = & get_ChapterCache();
		$Chapter = $ChapterCache->get_by_ID($cat_parent_ID);
		$cat_blog_ID = $Chapter->blog_ID;
	}

	if( $cat_parent_ID === 'NULL' )
	{ // fix old use case
		$cat_parent_ID = NULL;
	}

	$new_Chapter = new Chapter( NULL, $cat_blog_ID );
	$new_Chapter->set( 'name', $cat_name );
	$new_Chapter->set( 'parent_ID', $cat_parent_ID );
	if( !empty( $cat_description ) )
	{ // Set decription
		$new_Chapter->set( 'description', $cat_description );
	}
	$new_Chapter->set( 'order', $cat_order );
	$new_Chapter->set( 'subcat_ordering', $subcat_ordering );
	if( $meta )
	{ // Set this category as meta
		$new_Chapter->set( 'meta', 1 );
	}

	if( $default_item_type_name !== NULL &&
	    ( $ItemTypeCache = & get_ItemTypeCache() ) &&
	    ( $ItemType = & $ItemTypeCache->get_by_name( $default_item_type_name, false, false ) ) )
	{	// Item type exists in DB by requested name, Use it:
		$new_Chapter->set( 'ityp_ID', $ItemType->ID );
	}

	if( ! $new_Chapter->dbinsert() )
	{
		return 0;
	}

	if( $add_to_cache )
	{ // add new Category into the Cache
		$ChapterCache = & get_ChapterCache();
		$ChapterCache->add( $new_Chapter );
	}

	return $new_Chapter->ID;
}


/**
 * Get category name+blog_id for specified cat ID
 *
 * fplanque: reused "R. U. Serious" optimization here
 * fplanque: added blog ID stuff
 *
 * @deprecated since 3.1.0-alpha. Use ChapterCache instead.
 * @param integer category ID
 * @param boolean die() if category does not exist? (default: true)
 *
 */
function get_the_category_by_ID( $cat_ID, $die = true )
{
	// TODO: asimo> Old content was changed, but still the whole function should be removed.
	$ChapterCache = & get_ChapterCache();
	return $ChapterCache->get_by_ID( $cat_ID, $die );
}


/**
 * Get blog ID for a given cat.
 * This halts on error.
 * @return integer
 */
function get_catblog( $cat_ID )
{
	$ChapterCache = & get_ChapterCache();
	$Chapter = $ChapterCache->get_by_ID($cat_ID);

	return $Chapter->blog_ID;
}


/**
 * Get category permanent url by category ID
 *
 * @param integer category ID
 */
function get_caturl( $cat_ID )
{
	$ChapterCache = & get_ChapterCache();
	$Chapter = $ChapterCache->get_by_ID($cat_ID);

	return $Chapter->get_permanent_url();
}


/**
 * Get # of posts for each category in a blog
 *
 * @param integer Category ID
 * @param integer Blog ID
 */
function get_postcount_in_category( $cat_ID, $blog_ID = NULL )
{
	if( is_null( $blog_ID ) )
	{
		global $blog;
		$blog_ID = $blog;
	}

	global $DB, $number_of_posts_in_cat;

	if( !isset( $number_of_posts_in_cat[ (string) $blog_ID ] ) )
	{
		$SQL = new SQL( 'Get # of posts for each category in a blog' );
		$SQL->SELECT( 'cat_ID, count( postcat_post_ID ) c' );
		$SQL->FROM( 'T_categories' );
		$SQL->FROM_add( 'INNER JOIN T_postcats ON postcat_cat_ID = cat_id' );
		$SQL->FROM_add( 'INNER JOIN T_items__item ON postcat_post_ID = post_id' );
		$SQL->FROM_add( 'LEFT JOIN T_items__type ON post_ityp_ID = ityp_ID' );
		$SQL->WHERE( 'cat_blog_ID = '.$DB->quote( $blog_ID ) );
		$SQL->WHERE_and( 'post_ityp_ID IS NULL OR ityp_usage = "post"' );
		$SQL->WHERE_and( statuses_where_clause( get_inskin_statuses( $blog_ID, 'post' ), 'post_', $blog_ID, 'blog_post!', true ) );
		$SQL->GROUP_BY( 'cat_ID' );
		$number_of_posts_in_cat[ (string) $blog_ID ] = $DB->get_assoc( $SQL );
	}

	return isset( $number_of_posts_in_cat[(string) $blog_ID][$cat_ID] ) ? (int) $number_of_posts_in_cat[(string) $blog_ID][$cat_ID] : 0;
}


/**
 * Get # of comments for each category in a blog
 *
 * @param integer Category ID
 * @param integer Blog ID
 */
function get_commentcount_in_category( $cat_ID, $blog_ID = NULL )
{
	if( is_null( $blog_ID ) )
	{
		global $blog;
		$blog_ID = $blog;
	}

	global $DB, $number_of_comments_in_cat;

	if( !isset( $number_of_comments_in_cat[(string) $blog_ID] ) )
	{
		$SQL = new SQL();
		$SQL->SELECT( 'cat_ID, COUNT( comment_ID ) c' );
		$SQL->FROM( 'T_comments' );
		$SQL->FROM_add( 'LEFT JOIN T_postcats ON comment_item_ID = postcat_post_ID' );
		$SQL->FROM_add( 'LEFT JOIN T_categories ON postcat_cat_ID = cat_id' );
		$SQL->FROM_add( 'LEFT JOIN T_items__item ON comment_item_ID = post_id' );
		$SQL->FROM_add( 'LEFT JOIN T_items__type ON post_ityp_ID = ityp_ID' );
		$SQL->WHERE( 'cat_blog_ID = '.$DB->quote( $blog_ID ) );
		$SQL->WHERE_and( 'comment_type IN ( "comment", "trackback", "pingback", "webmention" )' );
		$SQL->WHERE_and( statuses_where_clause( get_inskin_statuses( $blog_ID, 'comment' ), 'comment_', $blog_ID, 'blog_comment!', true ) );
		// add where condition to show only those posts commetns which are visible for the current User
		$SQL->WHERE_and( statuses_where_clause( get_inskin_statuses( $blog_ID, 'post' ), 'post_', $blog_ID, 'blog_post!', true ) );
		// Get content post types, Exclide pages, intros, sidebar links and ads
		$SQL->WHERE_and( 'post_ityp_ID IS NULL OR ityp_usage = "post"' );
		$SQL->GROUP_BY( 'cat_ID' );

		$number_of_comments_in_cat[(string) $blog_ID] = $DB->get_assoc( $SQL->get() );
	}

	return isset( $number_of_comments_in_cat[(string) $blog_ID][$cat_ID] ) ? (int) $number_of_comments_in_cat[(string) $blog_ID][$cat_ID] : 0;
}


/**
 * Load category associations for requested items
 *
 * @param array Item IDs
 * @return array Item categories IDs
 */
function postcats_get_by_IDs( $item_IDs )
{
	global $DB, $cache_postcats;

	if( ! is_array( $cache_postcats ) )
	{	// Initialize cache array first time:
		$cache_postcats = array();
	}

	$not_cached_item_IDs = array_diff( $item_IDs, array_keys( $cache_postcats ) );

	if( empty( $not_cached_item_IDs ) )
	{	// The category associations are loaded for all requested items:
		return;
	}

	// Load the category associations from DB and cache into global cache array:
	$SQL = new SQL( 'Load the category associations for items' );
	$SQL->SELECT( 'postcat_post_ID AS post_ID, postcat_cat_ID AS cat_ID' );
	$SQL->FROM( 'T_postcats' );
	$SQL->WHERE( 'postcat_post_ID IN ( '.$DB->quote( $not_cached_item_IDs ).' )' );
	$SQL->ORDER_BY( 'postcat_post_ID, postcat_cat_ID' );

	$items_postcats = $DB->get_results( $SQL );

	foreach( $items_postcats as $item_postcats )
	{
		if( ! isset( $cache_postcats[ $item_postcats->post_ID ] ) )
		{
			$cache_postcats[ $item_postcats->post_ID ] = array();
		}

		$cache_postcats[ $item_postcats->post_ID ][] = $item_postcats->cat_ID;
	}

	// Set all unexiting associations for requested items in order to don't repeat SQL queries later:
	foreach( $not_cached_item_IDs as $not_cached_item_ID )
	{
		if( ! isset( $cache_postcats[ $not_cached_item_ID ] ) )
		{
			$cache_postcats[ $not_cached_item_ID ] = array();
		}
	}
}


/**
 * Get category associations with given item
 *
 * sam2kb> TODO: Cache item cat IDs into Item::categories property instead of global $cache_postcats
 *
 * @param integer Item ID
 * @return array Item categories IDs
 */
function postcats_get_byID( $post_ID )
{
	global $DB, $cache_postcats;

	if( empty( $post_ID ) )
	{
		return array();
	}

	if( ! is_array( $cache_postcats ) )
	{	// Initialize cache array first time:
		$cache_postcats = array();
	}

	if( ! isset( $cache_postcats[ $post_ID ] ) )
	{	// Get data from DB if it is not still in cache:
		$SQL = new SQL( 'Get category associations with given item #'.$post_ID );
		$SQL->SELECT( 'postcat_cat_ID' );
		$SQL->FROM( 'T_postcats' );
		$SQL->WHERE( 'postcat_post_ID = '.$DB->quote( $post_ID ) );
		$SQL->ORDER_BY( 'postcat_cat_ID' );

		$cache_postcats[ $post_ID ] = $DB->get_col( $SQL );
	}

	return $cache_postcats[ $post_ID ];
}


/**
 * Does a given blog have categories?
 *
 * @param integer Blog ID
 * @return boolean
 */
function blog_has_cats( $blog_ID )
{
	$ChapterCache = & get_ChapterCache();
	return $ChapterCache->has_chapters_in_subset( $blog_ID );
}


/**
 * Compiles the cat array from $cat (recursive + optional modifiers) and $catsel[] (non recursive)
 *
 * @param string
 * @param array
 * @param array by ref, will be modified
 * @param string by ref, will be modified
 * @param integer blog number to restrict to
 */
function compile_cat_array( $cat, $catsel, & $cat_array, & $cat_modifier, $restrict_to_blog = 0  )
{
	// echo '$cat='.$cat;
	// pre_dump( $catsel );
	// echo '$restrict_to_blog'.$restrict_to_blog;

	$cat_array = array();
	$cat_modifier = '';

	// Check for cat string (which will be handled recursively)
	if( $cat != 'all' && !empty($cat) )
	{ // specified a category string:
		$cat_modifier = substr($cat, 0, 1 );
		// echo 'cats['.$first_char.']';
		if( ( $cat_modifier == '*' ) ||
		    ( $cat_modifier == '-' ) ||
		    ( $cat_modifier == '|' ) )
		{
			$cat = substr( $cat, 1 );
		}
		else
		{
			$cat_modifier = '';
		}

		if( strlen( $cat ) )
		{	// There are some values to explode...
			$req_cat_array = explode(',', $cat);

			// Getting required sub-categories:
			// and add everything to cat array
			// ----------------- START RECURSIVE CAT LIST ----------------
			$ChapterCache = & get_ChapterCache();
			if( $restrict_to_blog > 0 )
			{ // Load all Chapters from the given blog
				$ChapterCache->reveal_children( $restrict_to_blog, true );
			}
			else
			{ // Load all chapters
				$ChapterCache->reveal_children( NULL, true );
			}

			foreach( $req_cat_array as $cat_ID )
			{ // run recursively through the cats
				$current_Chapter = $ChapterCache->get_by_ID( $cat_ID, false );
				if( empty( $current_Chapter ) )
				{ // The requested Chapter doesn't exists in the given context
					continue;
				}
				if( ! in_array( $cat_ID, $cat_array ) )
				{ // Not already in list
					$cat_array[] = $cat_ID;
					$ChapterCache->iterate_through_category_children( $current_Chapter, array( 'line' => 'cat_req' ), true, array( 'sorted' => true ) );
				}
			}
			// ----------------- END RECURSIVE CAT LIST ----------------
		}
	}

	// Add explicit selections:
	if( ! empty( $catsel ))
	{
		// echo "Explicit selections!<br />";
		$cat_array = array_merge( $cat_array, $catsel );
		$cat_array = array_unique( $cat_array );
	}

	// echo '$cat_modifier='.$cat_modifier;
	// pre_dump( $cat_array );

}


/**
 * Callback used in compile_cat_array()
 */
function cat_req( $Chapter, $level )
{
	global $cat_array;

	if( ! in_array( $Chapter->ID, $cat_array ) )
	{ // Not already visited
		$cat_array[] = $Chapter->ID;
	}
}


/**
 * Get global cross posting settings -- (cross posting = 1 post in multiple blogs)
 *
 * @return int
 * 		0 - cross posting disabled
 * 		1 - cross posting enabled for extra categories
 * 		2 - cross posting enabled for main categories
 * 		3 - cross posting enabled for main and extracats
 */
function get_allow_cross_posting()
{
	global $Settings;
	return $Settings->get( 'cross_posting' ) + ( 2 *  $Settings->get( 'cross_posting_blogs' ) );
}
?>