<?php /*

 ocPortal
 Copyright (c) ocProducts, 2004-2012

 See text/EN/licence.txt for full licencing information.


 NOTE TO PROGRAMMERS:
   Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder
   **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes ****

*/

/**
 * @license		http://opensource.org/licenses/cpal_1.0 Common Public Attribution License
 * @copyright	ocProducts Ltd
 * @package		catalogues
 */

/**
 * Standard code module initialisation function.
 */
function init__catalogues()
{
	global $PT_PAIR_CACHE;
	$PT_PAIR_CACHE=array();

	global $CAT_FIELDS_CACHE;
	$CAT_FIELDS_CACHE=array();

	// We do not actually necessarily use these constants in the code (they're based on an extensive of an old BINARY field): but they're here for reference so as to understand the codes
	if (!defined('C_DT_MAPS'))
	{
		define('C_DT_MAPS',0);
		define('C_DT_LIST',1);
		define('C_DT_MATRIX',2);
	}
}

/**
 * Get tempcode for a catalogue category 'feature box' for the given row
 *
 * @param  array			The database field row of it
 * @param  ID_TEXT		The zone to use
 * @param  boolean		Whether to put it in a box
 * @return tempcode		A box for it, linking to the full page
 */
function get_catalogue_category_html($row,$zone='_SEARCH',$put_in_box=true)
{
	$content=paragraph(get_translated_tempcode($row['cc_description']),'yghjgfjftgerr');
	$url=build_url(array('page'=>'catalogues','type'=>'category','id'=>$row['id']),$zone);

	$tree=catalogue_category_breadcrumbs($row['id']);
	if (!$tree->is_empty()) $content->attach(paragraph(do_lang_tempcode('LOCATED_IN',$tree)));

	$preview=do_template('SIMPLE_PREVIEW_BOX',array('SUMMARY'=>$content,'URL'=>$url));

	if (!$put_in_box) return $preview;

	return put_in_standard_box($preview,get_translated_text($row['cc_title']));
}

/**
 * Grant all usergroups access to the specified catalogue category.
 *
 * @param  AUTO_LINK		The ID of the category that access is being given to
 */
function grant_catalogue_full_access($category_id)
{
	$groups=$GLOBALS['FORUM_DRIVER']->get_usergroup_list(false,true);
	foreach (array_keys($groups) as $group_id)
	{
		$GLOBALS['SITE_DB']->query_insert('group_category_access',array('module_the_name'=>'catalogues_category','category_name'=>strval($category_id),'group_id'=>$group_id));
	}
}

/**
 * Count the entries and subcategories underneath the specified category, recursively.
 *
 * @param  AUTO_LINK		The ID of the category for which count details are collected
 * @return array			The number of entries is returned in $output['num_entries'], and the number of subcategories is returned in $output['num_children'], the (possibly recursive) number of subcategories in $output['num_children_children'], and the (possibly recursive) number of entries is returned in $output['num_entries_children'].
 */
function count_catalogue_category_children($category_id)
{
	static $total_categories=NULL;
	if (is_null($total_categories)) $total_categories=$GLOBALS['SITE_DB']->query_value('catalogue_categories','COUNT(*)');

	$out=array();

	$out['num_children']=$GLOBALS['SITE_DB']->query_value('catalogue_categories','COUNT(*)',array('cc_parent_id'=>$category_id));
	$out['num_entries']=$GLOBALS['SITE_DB']->query_value('catalogue_entries','COUNT(*)',array('cc_id'=>$category_id,'ce_validated'=>1));

	$rec_record=$GLOBALS['SITE_DB']->query_select('catalogue_childcountcache',array('c_num_rec_children','c_num_rec_entries'),array('cc_id'=>$category_id),'',1);
	if (!array_key_exists(0,$rec_record)) $rec_record[0]=array('c_num_rec_children'=>0,'c_num_rec_entries'=>0);

	$out['num_children_children']=$rec_record[0]['c_num_rec_children'];
	$out['num_entries_children']=$rec_record[0]['c_num_rec_entries'];

	return $out;
}

/**
 * Get an ordered array of all the entries in the specified catalogue.
 *
 * @param  ?AUTO_LINK		The ID of the category for which the entries are being collected (NULL: entries are [and must be] passed instead)
 * @param  ID_TEXT			The name of the catalogue
 * @param  ?array				A database row of the catalogue we are working with (NULL: read it in)
 * @param  ID_TEXT			The view type we're doing
 * @set    PAGE SEARCH CATEGORY
 * @param  ID_TEXT			The template set we are rendering this category using
 * @param  ?integer			The maximum number of entries to show on a single page of this this category (NULL: all)
 * @param  ?integer			The entry number to start at (NULL: all)
 * @param  ?mixed				The entries to show, may be from other categories. Can either be SQL fragment, or array (NULL: use $start and $max)
 * @param  ?AUTO_LINK		The virtual root for display of this category (NULL: default)
 * @param  ?SHORT_INTEGER	The display type to use (NULL: lookup from $catalogue)
 * @param  boolean			Whether to perform sorting
 * @param  ?array				A list of entry rows (NULL: select them normally)
 * @param  string				Search filter (blank: no filter)
 * @param  ?ID_TEXT			Orderer (NULL: read from environment)
 * @return array				An array containing our built up entries (renderable tempcode), our sorting interface, and our entries (entry records from database, with an additional 'map' field), and the max rows
 */
function get_catalogue_category_entry_buildup($category_id,$catalogue_name,$catalogue,$view_type,$tpl_set,$max,$start,$select,$root,$display_type=NULL,$do_sorting=true,$entries=NULL,$search='',$_order_by=NULL)
{
	if (addon_installed('ecommerce'))
	{
		require_code('ecommerce');
	}

	$is_ecomm=is_ecommerce_catalogue($catalogue_name);

	if (is_null($catalogue))
	{
		$_catalogues=$GLOBALS['SITE_DB']->query_select('catalogues',array('*'),array('c_name'=>$catalogue_name),'',1);
		$catalogue=$_catalogues[0];
	}

	$or_list=mixed();
	if ((is_null($entries)) && (!is_null($select)))
	{
		if (((is_array($select)) && (count($select)==0)) || ((is_string($select)) && ($select=='')))
		{
			$entries=array();
		} else
		{
			if (!is_array($select))
			{
				$or_list=$select;
			} else
			{
				$or_list='';
				foreach ($select as $s)
				{
					if ($or_list!='') $or_list.=' OR ';
					$or_list.='e.id='.strval($s);
				}
			}
		}
	}

	if (is_null($display_type))
	{
		$display_type=get_param_integer('keep_cat_display_type',$catalogue['c_display_type']);
	}

	// Find order field
	global $CAT_FIELDS_CACHE;
	if (isset($CAT_FIELDS_CACHE[$catalogue_name]))
	{
		$fields=$CAT_FIELDS_CACHE[$catalogue_name];
	} else
	{
		$fields=$GLOBALS['SITE_DB']->query_select('catalogue_fields',array('*'),array('c_name'=>$catalogue_name),'ORDER BY cf_order');
	}
	$CAT_FIELDS_CACHE[$catalogue_name]=$fields;
	if ($do_sorting)
	{
		global $NON_CANONICAL_PARAMS;
		$NON_CANONICAL_PARAMS[]='order';

		if (is_null($_order_by))
			$_order_by=get_param('order','');
		if (($_order_by=='') || (strpos($_order_by,' ')===false/*probably some bot probing URLs -- sorting always has a space between sorter and direction*/))
		{	
			$order_by='0';
			$direction='ASC';
			foreach ($fields as $i=>$field)
			{
				if ($field['cf_defines_order']!=0)
				{
					$order_by=strval($i);
					$direction=($field['cf_defines_order']==1)?'ASC':'DESC';
					$_order_by=strval($field['id']).' '.$direction;
					break;
				}
			}
		} else
		{	
			list($order_by,$direction)=explode(' ',$_order_by);
			if (($order_by!='rating') && ($order_by!='add_date'))
			{
				$found=false;
				foreach ($fields as $i=>$field)
				{
					if ($order_by==strval($field['id']))
					{
						$order_by=strval($i);
						$found=true;
						break;
					}
				}
				if (!$found) $order_by='0'; // Could not find
			}
		}
	} else
	{
		$order_by=mixed();
		$direction='ASC';
	}

	// Get entries in this category
	$map='1=1';
	if (!is_null($category_id)) $map.=' AND cc_id='.strval($category_id);
	if (!has_specific_permission(get_member(),'see_unvalidated')) $map.=' AND ce_validated=1';
	if (!is_null($or_list)) $map.=' AND ('.$or_list.')';
	$in_db_sorting=!is_null($order_by) && $do_sorting;
	require_code('fields');
	if (is_null($entries))
	{
		if ($in_db_sorting)
		{
			$num_entries=$GLOBALS['SITE_DB']->query_value_null_ok_full('SELECT COUNT(*) FROM '.get_table_prefix().'catalogue_entries e WHERE '.$map);
			if ($order_by=='add_date')
			{
				$entries=($max==0)?array():$GLOBALS['SITE_DB']->query('SELECT e.* FROM '.get_table_prefix().'catalogue_entries e WHERE '.$map.' ORDER BY ce_add_date '.$direction,$max,$start);
			}
			elseif ($order_by=='rating')
			{
				$select_rating='(SELECT AVG(rating) FROM '.get_table_prefix().'rating WHERE '.db_string_equal_to('rating_for_type','catalogues').' AND rating_for_id=e.id) AS compound_rating';
				$entries=($max==0)?array():$GLOBALS['SITE_DB']->query('SELECT e.*,'.$select_rating.' FROM '.get_table_prefix().'catalogue_entries e  WHERE '.$map.' ORDER BY compound_rating '.$direction,$max,$start);
			} else
			{
				$ob=get_fields_hook($fields[intval($order_by)]['cf_type']);
				list(,,$table)=$ob->get_field_value_row_bits($fields[$order_by]);

				if (strpos($table,'_trans')!==false)
				{
					$join=get_table_prefix().'catalogue_entries e LEFT JOIN '.get_table_prefix().'catalogue_efv_'.$table.' f ON f.ce_id=e.id AND f.cf_id='.strval($fields[$order_by]['id']).' LEFT JOIN '.get_table_prefix().'translate t ON f.cv_value=t.id';
					$entries=($max==0)?array():$GLOBALS['SITE_DB']->query('SELECT e.* FROM '.$join.' WHERE '.$map.' '.(($num_entries>300)?'':'ORDER BY t.text_original '.$direction) /* For large data sets too slow as after two MySQL joins it can't then use index for ordering */,$max,$start);
				} else
				{
					$join=get_table_prefix().'catalogue_entries e LEFT JOIN '.get_table_prefix().'catalogue_efv_'.$table.' f ON f.ce_id=e.id AND f.cf_id='.strval($fields[$order_by]['id']);
					$entries=($max==0)?array():$GLOBALS['SITE_DB']->query('SELECT e.* FROM '.$join.' WHERE '.$map.' ORDER BY f.cv_value '.$direction,$max,$start);
				}
			}
			$start=0; // To stop it skipping itself
		} else
		{
			if ((is_null($order_by) || !$do_sorting) && (!is_null($max)))
			{
				$entries=($max==0)?array():$GLOBALS['SITE_DB']->query('SELECT e.* FROM '.get_table_prefix().'catalogue_entries e WHERE '.$map,$max,$start);
				$num_entries=$GLOBALS['SITE_DB']->query_value_null_ok_full('SELECT COUNT(*) FROM '.get_table_prefix().'catalogue_entries e WHERE '.$map);
				$start=0; // To stop it skipping itself
			} else
			{
				$entries=($max==0)?array():$GLOBALS['SITE_DB']->query('SELECT e.* FROM '.get_table_prefix().'catalogue_entries e WHERE '.$map);
				$num_entries=count($entries);
			}
		}
	} else
	{
		$num_entries=count($entries);
	}
	if (($num_entries>300) && (!$in_db_sorting)) $in_db_sorting=true; // Needed to stop huge slow down

	foreach ($entries as $i=>$entry)
	{
		$entries[$i]['map']=get_catalogue_entry_map($entry,$catalogue,$view_type,$tpl_set,$root,$fields,(($display_type==1) && (!$is_ecomm) && (!is_null($order_by)))?array(0,intval($order_by)):NULL,false,false,intval($order_by));
	}

	// Implement search filter
	if ($search!='')
	{
		$new_entries=array();
		for ($i=0;$i<$num_entries;$i++)
		{
			$two_d_list=$entries[$i]['map']['FIELDS_2D'];
			$all_output='';
			foreach ($two_d_list as $index=>$l)
			{
				$all_output.=(is_object($l['VALUE'])?$l['VALUE']->evaluate():$l['VALUE']).' ';
			}

			if (strpos(strtolower($all_output),strtolower($search))!==false)
			{
				$new_entries[]=$entries[$i];
			}
		}
		$entries=$new_entries;
	}

	disable_php_memory_limit();

	if ($do_sorting)
	{
		// Sort entries
		$selectors=new ocp_tempcode();
		foreach ($fields as $i=>$field)
		{
			if ($field['cf_searchable']==1)
			{
				$potential_sorter_name=get_translated_text($field['cf_name']);
				foreach (array('ASC'=>'_ASCENDING','DESC'=>'_DESCENDING') as $dir_code=>$dir_lang)
				{
					$sort_sel=(($order_by==strval($i)) && ($direction==$dir_code));
					$_potential_sorter_name=new ocp_tempcode();
					$_potential_sorter_name->attach(escape_html($potential_sorter_name));
					$_potential_sorter_name->attach(do_lang_tempcode($dir_lang));
					$selectors->attach(do_template('RESULTS_BROWSER_SORTER',array('_GUID'=>'dfdsfdsusd0fsd0dsf','SELECTED'=>$sort_sel,'NAME'=>protect_from_escaping($_potential_sorter_name),'VALUE'=>strval($field['id']).' '.$dir_code)));
				}
			}
		}
		$extra_sorts=array();
		$extra_sorts['add_date']='_ADDED';
		if (get_option('is_on_rating')=='0')
		{
			$has_ratings=false;
		} else
		{
			if (is_null($entries))
			{
				$has_ratings=false;
				foreach ($entries as $entry)
				{
					if ($entry['allow_rating']==1)
					{
						$has_ratings=true;
					}
				}
				if ($has_ratings)
				{
					$extra_sorts['rating']='RATING';
				}
			} else
			{
				$has_ratings=true;
			}
		}
		foreach ($extra_sorts as $extra_sort_code=>$extra_sort_lang)
		{
			foreach (array('ASC'=>'_ASCENDING','DESC'=>'_DESCENDING') as $dir_code=>$dir_lang)
			{
				$sort_sel=(($order_by==$extra_sort_code) && ($direction==$dir_code));
				$_potential_sorter_name=new ocp_tempcode();
				$_potential_sorter_name->attach(do_lang_tempcode($extra_sort_lang));
				$_potential_sorter_name->attach(do_lang_tempcode($dir_lang));
				$selectors->attach(do_template('RESULTS_BROWSER_SORTER',array('_GUID'=>'xfdsfdsusd0fsd0dsf','SELECTED'=>$sort_sel,'NAME'=>protect_from_escaping($_potential_sorter_name),'VALUE'=>$extra_sort_code.' '.$dir_code)));
			}
		}
		$sort_url=get_self_url(false,false,array('order'=>NULL),false,true);
		$sorting=do_template('RESULTS_BROWSER_SORT',array('_GUID'=>'9fgjfdklgjdfgkjlfdjgd90','SORT'=>'order','RAND'=>uniqid(''),'URL'=>$sort_url,'SELECTORS'=>$selectors));
		if (!$in_db_sorting)
		{
			for ($i=0;$i<$num_entries;$i++)
			{
				if (!array_key_exists($i,$entries)) continue;

				for ($j=$i+1;$j<$num_entries;$j++)
				{
					if (!array_key_exists($j,$entries)) continue;

					$a=@$entries[$j]['map']['FIELD_'.$order_by];
					if (array_key_exists('FIELD_'.$order_by.'_PLAIN',@$entries[$j]['map']))
					{
						$a=@$entries[$j]['map']['FIELD_'.$order_by.'_PLAIN'];
					}
					$b=@$entries[$i]['map']['FIELD_'.$order_by];
					if (array_key_exists('FIELD_'.$order_by.'_PLAIN',@$entries[$i]['map']))
					{
						$b=@$entries[$i]['map']['FIELD_'.$order_by.'_PLAIN'];
					}
					if (is_object($a)) $a=$a->evaluate();
					if (is_object($b)) $b=$b->evaluate();

					if ($fields[$order_by]['cf_type']=='date')
					{
						$bits=explode(' ',$a,2);
						$date_bits=explode((strpos($bits[0],'-')!==false)?'-':'/',$bits[0],3);
						if (!array_key_exists(1,$date_bits)) $date_bits[1]=date('m');
						if (!array_key_exists(2,$date_bits)) $date_bits[2]=date('Y');
						$time_bits=explode(':',$bits[1],3);
						if (!array_key_exists(1,$time_bits)) $time_bits[1]='00';
						if (!array_key_exists(2,$time_bits)) $time_bits[2]='00';
						$time_a=mktime(intval($time_bits[0]),intval($time_bits[1]),intval($time_bits[2]),intval($date_bits[1]),intval($date_bits[2]),intval($date_bits[0]));
						$bits=explode(' ',$b,2);
						$date_bits=explode((strpos($bits[0],'-')!==false)?'-':'/',$bits[0],3);
						if (!array_key_exists(1,$date_bits)) $date_bits[1]=date('m');
						if (!array_key_exists(2,$date_bits)) $date_bits[2]=date('Y');
						$time_bits=explode(':',$bits[1],3);
						if (!array_key_exists(1,$time_bits)) $time_bits[1]='00';
						if (!array_key_exists(2,$time_bits)) $time_bits[2]='00';
						$time_b=mktime(intval($time_bits[0]),intval($time_bits[1]),intval($time_bits[2]),intval($date_bits[1]),intval($date_bits[2]),intval($date_bits[0]));

						$r=($time_a<$time_b)?-1:(($time_a==$time_b)?0:1);
					} else
					{
						$r=strnatcmp(strtolower($a),strtolower($b));
					}
					if ((($r<0) && ($direction=='ASC')) || (($r>0) && ($direction=='DESC')))
					{
						$temp=$entries[$i];
						$entries[$i]=$entries[$j];
						$entries[$j]=$temp;
					}
				}
			}
		}
	} else $sorting=new ocp_tempcode();

	// Build up entries
	$entry_buildup=new ocp_tempcode();

	$extra_map=array();
	if($is_ecomm)
	{	
		require_lang('shopping');
		$i=0;
		for ($i=0;$i<$num_entries;$i++)
		{
			if (!array_key_exists($i,$entries)) break;

			$entry=$entries[$i];
			$extra_map[$i]['ADD_TO_CART']=build_url(array('page'=>'shopping','type'=>'add_item','product_id'=>$entry['id'],'hook'=>'catalogue_items'),get_module_zone('shopping'));
		}
	}


	if ($display_type==2)
	{
		for ($i=0;$i<$num_entries;$i++)
		{
			if (!array_key_exists($i,$entries)) break;
			$entry=$entries[$i];
			if (((is_null($start)) || ($in_db_sorting) || ($i>=$start) && ($i<$start+$max)))
			{
				$tab_entry_map=$entry['map']+(array_key_exists($i,$extra_map)?$extra_map[$i]:array());
				if ((get_option('is_on_comments')=='1') && ($entry['allow_comments']>=1) || (get_option('is_on_rating')=='1') && ($entry['allow_rating']==1) || (get_option('is_on_trackbacks')=='1') && ($entry['allow_trackbacks']==1))
				{
					$tab_entry_map['VIEW_URL']=build_url(array('page'=>'catalogues','type'=>'entry','id'=>$entry['id'],'root'=>($root==-1)?NULL:$root),get_module_zone('catalogues'));
				} else
				{
					$tab_entry_map['VIEW_URL']='';
				}

				$entry_buildup->attach(/*Preserve memory*/static_evaluate_tempcode(do_template('CATALOGUE_'.$tpl_set.'_TAB_ENTRY',$tab_entry_map,NULL,false,'CATALOGUE_DEFAULT_TAB_ENTRY')));
			}
			if ((!is_null($start)) && ($i>=$start+$max)) break;
		}

		if (!$entry_buildup->is_empty())
		{
			$head=new ocp_tempcode();
			$field_count=0;
			foreach ($fields as $i=>$field)
			{
				if (((($field['cf_put_in_category']==1) && ($view_type=='CATEGORY')) || (($field['cf_put_in_search']==1) && ($view_type=='SEARCH'))) && ($field['cf_visible']==1))
				{
					if ($field['cf_searchable']==1)
					{
						$sort_url_asc=get_self_url(false,false,array('order'=>strval($field['id']).' ASC'),true);
						$sort_url_desc=get_self_url(false,false,array('order'=>strval($field['id']).' DESC'),true);
						$sort_asc_selected=(($order_by==strval($field['id'])) && ($direction=='ASC'));
						$sort_desc_selected=(($order_by==strval($field['id'])) && ($direction=='DESC'));
					} else
					{
						$sort_url_asc='';
						$sort_url_desc='';
						$sort_asc_selected=false;
						$sort_desc_selected=false;
					}
					$head->attach(do_template('CATALOGUE_'.$tpl_set.'_TAB_FIELD_HEAD',array('SORT_ASC_SELECTED'=>$sort_asc_selected,'SORT_DESC_SELECTED'=>$sort_desc_selected,'SORT_URL_ASC'=>$sort_url_asc,'SORT_URL_DESC'=>$sort_url_desc,'CATALOGUE'=>$catalogue_name,'FIELDID'=>strval($i),'_FIELDID'=>strval($field['id']),'FIELD'=>get_translated_text($field['cf_name']),'FIELDTYPE'=>$field['cf_type']),NULL,false,'CATALOGUE_DEFAULT_TAB_FIELD_HEAD'));
					$field_count++;
				}
			}
			$entry_buildup=do_template('CATALOGUE_'.$tpl_set.'_TAB_WRAP',array('CATALOGUE'=>$catalogue_name,'HEAD'=>$head,'CONTENT'=>$entry_buildup,'FIELD_COUNT'=>strval($field_count)),NULL,false,'CATALOGUE_DEFAULT_TAB_WRAP');
		}
	}
	elseif ($display_type==0)
	{		
		for ($i=0;$i<$num_entries;$i++)
		{
			if (!array_key_exists($i,$entries)) break;

			$entry=$entries[$i];

			if ((is_null($max)) || ($in_db_sorting) || ((is_null($start)) || ($i>=$start) && ($i<$start+$max)))
				$entry_buildup->attach(do_template('CATALOGUE_'.$tpl_set.'_ENTRY_EMBED',$entry['map']+(array_key_exists($i,$extra_map)?$extra_map[$i]:array()),NULL,false,'CATALOGUE_DEFAULT_ENTRY_EMBED'));
		}
	}
	else
	{
		for ($i=0;$i<$num_entries;$i++)
		{
			if (!array_key_exists($i,$entries)) break;

			$entry=$entries[$i];

			if (((is_null($start)) || ($in_db_sorting) || ($i>=$start) && ($i<$start+$max)))
				$entry_buildup->attach(do_template('CATALOGUE_'.$tpl_set.'_LINE',$entry['map']+(array_key_exists($i,$extra_map)?$extra_map[$i]:array()),NULL,false,'CATALOGUE_DEFAULT_LINE'));
		}
		if (!$entry_buildup->is_empty()) $entry_buildup=do_template('CATALOGUE_'.$tpl_set.'_LINE_WRAP',$entry['map']+array('CATALOGUE'=>$catalogue_name,'CONTENT'=>$entry_buildup),NULL,false,'CATALOGUE_DEFAULT_LINE_WRAP');
	}

	return array($entry_buildup,$sorting,$entries,$num_entries);
}

/**
 * Get a map of the fields for the given entry.
 *
 * @param  array			A database row of the entry we are working with
 * @param  ?array			A database row of the catalogue we are working with (NULL: read it in here)
 * @param  ID_TEXT		The view type we're doing
 * @set    PAGE SEARCH CATEGORY
 * @param  ID_TEXT		The template set we are rendering this category using
 * @param  ?AUTO_LINK	The virtual root for display of this category (NULL: none)
 * @param  ?array			The database rows for the fields for this catalogue (NULL: find them)
 * @param  ?array			A list of fields that we are limiting ourselves to (NULL: get ALL fields)
 * @param  boolean		Whether to grab the feedback details
 * @param  boolean		Whether to grab the tree details
 * @param  ?integer		Field index to order by (NULL: none)
 * @return array			A map of information relating to the entry. The map contains 'FIELDS' (tempcode for all accumulated fields), 'FIELD_x' (for each field x applying to the entry), STAFF_DETAILS, COMMENT_DETAILS, RATING_DETAILS, VIEW_URL, TREE (tempcode category tree to this entry)
 */
function get_catalogue_entry_map($entry,$catalogue,$view_type,$tpl_set,$root=NULL,$fields=NULL,$only_fields=NULL,$feedback_details=false,$tree_details=false,$order_by=NULL)
{
	$id=$entry['id'];

	if (is_null($catalogue))
	{
		$catalogue_rows=$GLOBALS['SITE_DB']->query_select('catalogues',array('*'),array('c_name'=>$entry['c_name']),'',1);
		if (!array_key_exists(0,$catalogue_rows)) warn_exit(do_lang_tempcode('MISSING_RESOURCE'));
		$catalogue=$catalogue_rows[0];
	}

	// Views
	if (get_db_type()!='xml')
	{
		$entry['ce_views']++;
	}


	$catalogue_name=$catalogue['c_name'];
	$fields=get_catalogue_entry_field_values($catalogue_name,$entry,$only_fields,$fields,false,$view_type);
	$map=array();
	$map['FIELDS']=new ocp_tempcode();
	$map['FIELDS_TABLE']=new ocp_tempcode();
	$map['fields']=$fields;
	$fields_1d=array();
	$fields_2d=array();
	$all_visible=true;

	require_code('fields');

	foreach ($fields as $i=>$field)
	{
		if (!array_key_exists('effective_value',$field))
		{
			$all_visible=false;
			continue;
		}

		$ev=$field['effective_value'];
		$dereference_ev=is_object($field['effective_value'])?$field['effective_value']->evaluate():$field['effective_value'];
		$use_ev=$dereference_ev;
		/*if (($field['cf_visible']==1) || ($i==0) || ($order_by===$i)) // If it's visible, or if it is the first (name) field, or if it is our order field        Actually we always want the data even if it's not visible, especially for catalogues */
		{
			$ob=get_fields_hook($field['cf_type']);
			$use_ev=$ob->render_field_value($field,$ev,$i,$only_fields);
			if (($i==0) && ($catalogue['c_display_type']==1))
			{
				//$use_ev=hyperlink(build_url(array('page'=>'catalogues','type'=>'entry','id'=>$id,'root'=>$root),get_module_zone('catalogues')),$ev,false,!is_object($ev));
				$use_ev=$ev;
			}

			//Set image thumbnail in to the map array
			if($field['cf_type']=='picture')
			{
				if((!is_null($ev)) && ($dereference_ev!=''))
				{
					require_code('images');
					$map['FIELD_'.strval($i).'_THUMB']=do_image_thumb($dereference_ev,($i==0)?'':(is_object($map['FIELD_0'])?$map['FIELD_0']->evaluate():$map['FIELD_0']),false,false,100,100);
				} else
				{
					$map['FIELD_'.strval($i).'_THUMB']= new ocp_tempcode();
				}
				$map['_FIELD_'.strval($field['id']).'_THUMB']=$map['FIELD_'.strval($i).'_THUMB'];
			}

			$map['FIELD_'.strval($i)]=$use_ev;
			$map['_FIELD_'.strval($field['id'])]=$use_ev;
			$map['FIELD_'.strval($i).'_PLAIN']=$ev;
			$map['_FIELD_'.strval($field['id']).'_PLAIN']=$ev;
			if (array_key_exists('effective_value_pure',$field))
			{
				$map['FIELD_'.strval($i).'_PURE']=$field['effective_value_pure'];
				$map['_FIELD_'.strval($field['id']).'_PURE']=$field['effective_value_pure'];
			}
			$field_name=get_translated_text($field['cf_name']);
			$map['FIELDNAME_'.strval($i)]=$field_name;
			$fields_2d[]=array('NAME'=>$field_name,'VALUE'=>$use_ev);
			$field_type=$field['cf_type'];
			$map['FIELDTYPE_'.strval($i)]=$field_type;

			if (($view_type=='PAGE') || (($field['cf_put_in_category']==1) && ($view_type=='CATEGORY')) || (($field['cf_put_in_search']==1) && ($view_type=='SEARCH')))
			{
				$use_ev_enhanced=$use_ev;
				/*if (($view_type!='PAGE') && ($i==0) && (($field['cf_type']=='short_trans') || ($field['cf_type']=='short_text')))
				{
					foreach ($fields as $field_temp)
					{
						if ((array_key_exists('effective_value',$field_temp)) && ($field_temp['cf_type']=='url') && (!((($field_temp['cf_put_in_category']==1) && ($view_type=='CATEGORY')) || (($field_temp['cf_put_in_search']==1) && ($view_type=='SEARCH')))) && ($field_temp['cf_visible']==1))
						{
							if ($field_temp['effective_value']!='')
							{
								$use_ev_enhanced=hyperlink($field_temp['effective_value'],$use_ev,true);
								break;
							}
						}
					}
				}*/

				if (($field['cf_visible']==1) || ($i==0))
				{
					$_field=do_template('CATALOGUE_'.$tpl_set.'_ENTRY_FIELD',array('ENTRYID'=>strval($id),'CATALOGUE'=>$catalogue_name,'TYPE'=>$field['cf_type'],'FIELD'=>$field_name,'FIELDID'=>strval($i),'_FIELDID'=>strval($field['id']),'FIELDTYPE'=>$field_type,'VALUE_PLAIN'=>$ev,'VALUE'=>$use_ev_enhanced),NULL,false,'CATALOGUE_DEFAULT_ENTRY_FIELD');
					if ((!is_string($ev)) || ($ev!='')) $map['FIELDS']->attach($_field);
					$_field=do_template('CATALOGUE_'.$tpl_set.'_TAB_FIELD',array('ENTRYID'=>strval($id),'CATALOGUE'=>$catalogue_name,'TYPE'=>$field['cf_type'],'FIELD'=>$field_name,'FIELDID'=>strval($i),'_FIELDID'=>strval($field['id']),'FIELDTYPE'=>$field_type,'VALUE_PLAIN'=>$ev,'VALUE'=>$use_ev_enhanced),NULL,false,'CATALOGUE_DEFAULT_TAB_FIELD');
					$map['FIELDS_TABLE']->attach($_field);
				}
			} else $all_visible=false;

			$fields_1d[]=$field;
		}
		if (!(($field['cf_visible']==1) || ($i==0) || ($order_by===$i))) $all_visible=false;
	}
	$map['FIELDS_1D']=$fields_1d;
	$map['FIELDS_2D']=$fields_2d;

	// Admin functions
	if ((has_actual_page_access(NULL,'cms_catalogues',NULL,NULL)) && (has_edit_permission('mid',get_member(),$entry['ce_submitter'],'cms_catalogues',array('catalogues_catalogue',$catalogue_name,'catalogues_category',$entry['cc_id']))))
	{
		$map['EDIT_URL']=build_url(array('page'=>'cms_catalogues','type'=>'_edit_entry','catalogue_name'=>$catalogue_name,'id'=>$id),get_module_zone('cms_catalogues'));
	} else $map['EDIT_URL']='';

	$map['SUBMITTER']=strval($entry['ce_submitter']);
	$map['VIEWS']=strval($entry['ce_views']);
	$map['ADD_DATE_RAW']=strval($entry['ce_add_date']);
	$map['EDIT_DATE_RAW']=is_null($entry['ce_edit_date'])?'':strval($entry['ce_edit_date']);
	$map['ADD_DATE']=get_timezoned_date($entry['ce_add_date']);
	$map['EDIT_DATE']=get_timezoned_date($entry['ce_edit_date']);
	$map['ID']=strval($id);
	if ((get_option('is_on_comments')=='1') && (!has_no_forum()) && ($entry['allow_comments']>=1)) $map['COMMENT_COUNT']='1';

	// Feedback
	//Set basic rating display of item to the map array
	require_code('feedback');
	$c_value=array_key_exists('FIELD_0_PLAIN_PURE',$map)?$map['FIELD_0_PLAIN_PURE']:$map['FIELD_0_PLAIN'];
	if (is_object($c_value)) $c_value=$c_value->evaluate();
	$self_url=build_url(array('page'=>'catalogues','type'=>'entry','id'=>$id),get_module_zone('catalogues'),NULL,false,false,true);
	if (($feedback_details) || ($only_fields!==array(0)))
	{
		$map['RATING']=($entry['allow_rating']==1)?display_rating($self_url,$c_value,'catalogues__'.$catalogue_name,strval($id),'RATING_INLINE_STATIC',$entry['ce_submitter']):new ocp_tempcode();
	}
	if ($feedback_details)
	{
		list($map['RATING_DETAILS'],$map['COMMENT_DETAILS'],$map['TRACKBACK_DETAILS'])=embed_feedback_systems(
			'catalogues__'.$catalogue_name,
			strval($id),
			$entry['allow_rating'],
			$entry['allow_comments'],
			$entry['allow_trackbacks'],
			$entry['ce_validated'],
			$entry['ce_submitter'],
			$self_url,
			$c_value,
			get_value('comment_forum__catalogues__'.$catalogue_name)
		);
	}

	if ((get_option('is_on_comments')=='1') && ($entry['allow_comments']>=1) || (get_option('is_on_rating')=='1') && ($entry['allow_rating']==1) || (get_option('is_on_trackbacks')=='1') && ($entry['allow_trackbacks']==1) || (!$all_visible))
	{
		$map['VIEW_URL']=($tpl_set=='WML')?make_string_tempcode('wap.php?page=catalogues&type=entry&id='.strval($id)):build_url(array('page'=>'catalogues','type'=>'entry','id'=>$id,'root'=>($root==-1)?NULL:$root),get_module_zone('catalogues'));
	} else
	{
		$map['VIEW_URL']='';
	}
	if ($tree_details)
	{
		$map['TREE']='';
		if (($catalogue['c_is_tree']==1) && (is_null($only_fields)))
		{
			$tree=catalogue_category_breadcrumbs($entry['cc_id'],$root,false);
			$map['TREE']=$tree;
		}
	}
	$map['CATALOGUE']=$catalogue_name;
	$map['CAT']=strval($entry['cc_id']);

	return $map;
}

/**
 * Get a nice, formatted, XHTML list of all the catalogues.
 *
 * @param  ?ID_TEXT		The name of the currently selected catalogue (NULL: none selected)
 * @param  boolean		If there are too many to list prefer to get ones with entries rather than just the newest
 * @param  boolean		Whether to only show catalogues that can be submitted to
 * @return tempcode		Catalogue selection list
 */
function nice_get_catalogues($it=NULL,$prefer_ones_with_entries=false,$only_submittable=false)
{
	$query='SELECT c.* FROM '.get_table_prefix().'catalogues c';
	if ($prefer_ones_with_entries)
	{
		if (can_arbitrary_groupby())
			$query.=' JOIN '.get_table_prefix().'catalogue_entries e ON e.c_name=c.c_name GROUP BY c.c_name';
	}
	$query.=' ORDER BY c_add_date DESC';
	$rows=$GLOBALS['SITE_DB']->query($query,100/*reasonable limit*/);
	if (count($rows)==100) attach_message(do_lang_tempcode('TOO_MUCH_CHOOSE__ALPHABETICAL',escape_html(integer_format(100))),'warn');
	$out=new ocp_tempcode();
	foreach ($rows as $row)
	{
		if (substr($row['c_name'],0,1)=='_') continue;

		if (!has_category_access(get_member(),'catalogues_catalogue',$row['c_name'])) continue;

		if (($only_submittable) && (!has_specific_permission(get_member(),'submit_midrange_content','cms_catalogues',array('catalogues_catalogue',$row['c_name'])))) continue;

		if (($row['c_ecommerce']==0) || (addon_installed('shopping')))
		{
			$selected=($row['c_name']==$it);
			$out->attach(form_input_list_entry($row['c_name'],$selected,get_translated_text($row['c_title'])));
		}
	}

	return $out;
}

/**
 * Get the values for the specified fields, for the stated catalogue entry.
 *
 * @param  ?ID_TEXT		The catalogue name we are getting an entry in (NULL: lookup)
 * @param  mixed			The ID of the entry we are getting OR the row
 * @param  ?array			A list of fields that we are limiting ourselves to (NULL: get ALL fields)
 * @param  ?array			The database rows for the fields for this catalogue (NULL: find them)
 * @param  boolean		Whether to order the fields in their natural database order
 * @param  ID_TEXT		The view type we're doing
 * @set    PAGE SEARCH CATEGORY
 * @return array			A list of maps (each field for the entry gets a map), where each map contains 'effective_value' (the value for the field). Some maps get additional fields (effective_value_nontrans, effective_value_pure), depending on the field type
 */
function get_catalogue_entry_field_values($catalogue_name,$entry_id,$only_fields=NULL,$fields=NULL,$natural_order=false,$view_type='PAGE')
{
	global $CAT_FIELDS_CACHE;

	if (is_null($fields))
	{
		if ((isset($CAT_FIELDS_CACHE[$catalogue_name])) && (!$natural_order))
		{
			$fields=$CAT_FIELDS_CACHE[$catalogue_name];
		} else
		{
			if (is_null($catalogue_name)) $catalogue_name=$GLOBALS['SITE_DB']->query_value('catalogue_entries','c_name',array('id'=>$entry_id));
			$fields=$GLOBALS['SITE_DB']->query_select('catalogue_fields',array('*'),array('c_name'=>$catalogue_name),'ORDER BY '.($natural_order?'id':'cf_order'));
		}
	}
	if (!$natural_order)
		$CAT_FIELDS_CACHE[$catalogue_name]=$fields;

	require_code('fields');

	// Work out an ID filter for what fields to show
	$only_field_ids=mixed();
	if (get_value('catalogue_limit_cat_field_load__'.$catalogue_name)==='1')
	{
		$only_field_ids=array();
		foreach ($fields as $i=>$field)
		{
			$field_id=$field['id'];

			if ((!is_null($only_fields)) && (!in_array($i,$only_fields))) continue;
			if ($field['cf_defines_order']==0)
			{
				if (($view_type=='CATEGORY') && ($field['cf_put_in_category']==0)) continue;
				if (($view_type=='SEARCH') && ($field['cf_put_in_search']==0)) continue;
			}

			$only_field_ids[]=$field_id;
		}
	}

	foreach ($fields as $i=>$field)
	{
		$field_id=$field['id'];

		if ((!is_null($only_fields)) && (!in_array($i,$only_fields))) continue;

		$ob=get_fields_hook($field['cf_type']);
		list($raw_type,,$type)=$ob->get_field_value_row_bits($field);
		if (is_null($raw_type)) $raw_type=$field['cf_type'];

		switch ($raw_type)
		{
			case 'short_trans':
				$fields[$i]['effective_value_nontrans']=_get_catalogue_entry_field($field_id,$entry_id,'short_trans',$only_field_ids);
				if (is_null($fields[$i]['effective_value_nontrans']))
				{
					$fields[$i]['effective_value']=do_lang_tempcode('INTERNAL_ERROR');
					$fields[$i]['effective_value_pure']=do_lang('INTERNAL_ERROR');
					break;
				}
				$fields[$i]['effective_value']=get_translated_tempcode(intval($fields[$i]['effective_value_nontrans']));
				$fields[$i]['effective_value_pure']=get_translated_text(intval($fields[$i]['effective_value_nontrans']));
				break;
			case 'long_trans':
				$fields[$i]['effective_value_nontrans']=_get_catalogue_entry_field($field_id,$entry_id,'long_trans',$only_field_ids);
				if (is_null($fields[$i]['effective_value_nontrans']))
				{
					$fields[$i]['effective_value']=do_lang_tempcode('INTERNAL_ERROR');
					$fields[$i]['effective_value_pure']=do_lang('INTERNAL_ERROR');
					break;
				}
				$fields[$i]['effective_value']=get_translated_tempcode(intval($fields[$i]['effective_value_nontrans']));
				$fields[$i]['effective_value_pure']=get_translated_text(intval($fields[$i]['effective_value_nontrans']));
				break;
			case 'long_text':
				$fields[$i]['effective_value_pure']=_get_catalogue_entry_field($field_id,$entry_id,'long',$only_field_ids);
				$fields[$i]['effective_value']=$fields[$i]['effective_value_pure'];
				if (is_null($fields[$i]['effective_value']))
				{
					$fields[$i]['effective_value']=do_lang_tempcode('INTERNAL_ERROR');
					$fields[$i]['effective_value_pure']=do_lang('INTERNAL_ERROR');
					break;
				}
				break;
			case 'short_text':
				$fields[$i]['effective_value_pure']=_get_catalogue_entry_field($field_id,$entry_id,$only_field_ids);
				$fields[$i]['effective_value']=$fields[$i]['effective_value_pure'];
				if (is_null($fields[$i]['effective_value']))
				{
					$fields[$i]['effective_value']=do_lang_tempcode('NA_EM');
					$fields[$i]['effective_value_pure']=do_lang('NA');
					break;
				}
				break;
			case 'long_unescaped':
				$fields[$i]['effective_value']=_get_catalogue_entry_field($field_id,$entry_id,'long',$only_field_ids);
				if (is_null($fields[$i]['effective_value']))
				{
					$fields[$i]['effective_value']=do_lang_tempcode('NA_EM');
					$fields[$i]['effective_value_pure']=do_lang('NA');
					break;
				}
				break;
			case 'short_unescaped':
			case 'float_unescaped':
			case 'integer_unescaped':
				$fields[$i]['effective_value']=_get_catalogue_entry_field($field_id,$entry_id,$type,$only_field_ids);
				if (is_null($fields[$i]['effective_value']))
				{
					$fields[$i]['effective_value']=do_lang_tempcode('NA_EM');
					$fields[$i]['effective_value_pure']=do_lang('NA');
					break;
				}
				break;
			default:
				warn_exit(do_lang_tempcode('INTERNAL_ERROR'));
		}
	}

	return $fields;
}

/**
 * Get the value for the specified field, for the stated catalogue entry.
 *
 * @param  AUTO_LINK		The ID of the field we are getting
 * @param  mixed			The ID of the entry we are getting for OR the row
 * @param  ID_TEXT		The type of field
 * @set    short long
 * @param  ?array			A list of field IDs that we are limiting ourselves to (NULL: get ALL fields)
 * @return string			The value
 */
function _get_catalogue_entry_field($field_id,$entry_id,$type='short',$only_field_ids=NULL)
{
	if (is_array($entry_id)) $entry_id=$entry_id['id'];

	global $SITE_INFO;
	if (((!isset($SITE_INFO['mysql_old'])) || ($SITE_INFO['mysql_old']=='0')) && ((!isset($SITE_INFO['mysql_old'])) || (!is_file(get_file_base().'/mysql_old'))))
	{
		// Pre-caching of whole entry
		static $catalogue_entry_cache=array();
		if (!isset($catalogue_entry_cache[$entry_id][$field_id]))
		{
			if (!isset($catalogue_entry_cache[$entry_id])) $catalogue_entry_cache[$entry_id]=array();
			$query='';
			foreach (array('catalogue_efv_float','catalogue_efv_integer','catalogue_efv_long','catalogue_efv_long_trans','catalogue_efv_short','catalogue_efv_short_trans',) as $table)
			{
				if ($query!='') $query.=' UNION ';
				$query.='SELECT f.id,v.cv_value,';
				if (strpos($table,'_trans')!==false)
				{
					$query.='t.text_original,t.text_parsed';
				} else
				{
					$query.='NULL AS text_original,NULL AS text_parsed';
				}
				$query.=' FROM '.get_table_prefix().'catalogue_fields f JOIN '.get_table_prefix().$table.' v ON v.cf_id=f.id';
				if (strpos($table,'_trans')!==false)
				{
					$query.=' JOIN '.get_table_prefix().'translate t ON t.id=v.cv_value';
				}
				$query.=' WHERE v.ce_id='.strval($entry_id);
				if (!is_null($only_field_ids))
				{
					$query.=' AND (';
					if ($only_field_ids!=array())
					{
						foreach ($only_field_ids as $i=>$_field_id)
						{
							if ($i!=0) $query.=' OR ';
							$query.='f.id='.strval($_field_id);
						}
					} else
					{
						$query.='1=0';
					}
					$query.=')';
				}
			}
			foreach ($GLOBALS['SITE_DB']->query($query,NULL,NULL,false,true) as $line)
			{
				$catalogue_entry_cache[$entry_id][$line['id']]=$line['cv_value'];
				if (isset($line['text_original']))
				{
					$GLOBALS['SITE_DB']->text_lookup_original_cache[$line['cv_value']]=$line['text_original'];
					$GLOBALS['SITE_DB']->text_lookup_cache[$line['cv_value']]=$line['text_parsed'];
				}
			}
		}

		$value=isset($catalogue_entry_cache[$entry_id][$field_id])?$catalogue_entry_cache[$entry_id][$field_id]:NULL;
	} else
	{
		$value=$GLOBALS['SITE_DB']->query_value_null_ok('catalogue_efv_'.$type,'cv_value',array('cf_id'=>$field_id,'ce_id'=>$entry_id));
	}

	if (is_integer($value)) $value=strval($value);
	if (is_float($value)) $value=float_to_raw_string($value);
	return $value;
}

/**
 * Get a nice, formatted XHTML list of entries, in catalogue category tree structure
 *
 * @param  ID_TEXT		The catalogue name
 * @param  ?AUTO_LINK	The currently selected entry (NULL: none selected)
 * @param  ?AUTO_LINK	Only show entries submitted by this member (NULL: no filter)
 * @param  boolean		Whether to only show for what may be edited by the current member
 * @return tempcode		The list of entries
 */
function nice_get_catalogue_entries_tree($catalogue_name,$it=NULL,$submitter=NULL,$editable_filter=false)
{
	$tree=get_catalogue_entries_tree($catalogue_name,$submitter,NULL,NULL,NULL,NULL,$editable_filter);

	$out=''; // XHTMLXHTML
	foreach ($tree as $category)
	{
		foreach ($category['entries'] as $eid=>$etitle)
		{
			$selected=($eid==$it);
			$line=do_template('CATALOGUE_ENTRIES_LIST_LINE',array('_GUID'=>'0ccffeff5b80b1840188b83aaee8d9f2','TREE'=>$category['tree'],'NAME'=>$etitle));
			$out.='<option value="'.strval($eid).'"'.($selected?'selected="selected"':'').'>'.$line->evaluate().'</option>';
		}
	}

	if ($GLOBALS['XSS_DETECT']) ocp_mark_as_escaped($out);

	return make_string_tempcode($out);
}

/**
 * Get a list of maps containing all the catalogue entries, and path information, under the specified category - and those beneath it, recursively.
 *
 * @param  ID_TEXT		The catalogue name
 * @param  ?AUTO_LINK	Only show entries submitted by this member (NULL: no filter)
 * @param  ?AUTO_LINK	The category being at the root of our recursion (NULL: true root)
 * @param  ?string		The tree up to this point in the recursion (NULL: blank, as we are starting the recursion)
 * @param  ?ID_TEXT		The name of the $category_id we are currently going through (NULL: look it up). This is here for efficiency reasons, as finding children IDs to recurse to also reveals the childs title
 * @param  ?integer		The number of recursive levels to search (NULL: all)
 * @param  boolean		Whether to only show for what may be edited by the current member
 * @return array			A list of maps for all categories. Each map entry containins the fields 'id' (category ID) and 'tree' (tree path to the category, including the categories own title), and more.
 */
function get_catalogue_entries_tree($catalogue_name,$submitter=NULL,$category_id=NULL,$tree=NULL,$title=NULL,$levels=NULL,$editable_filter=false)
{
	if (is_null($category_id))
	{
		$is_tree=$GLOBALS['SITE_DB']->query_value_null_ok('catalogues','c_is_tree',array('c_name'=>$catalogue_name),'',1);
	}

	if ((is_null($category_id)) && (is_null($levels)))
	{
		if ($GLOBALS['SITE_DB']->query_value('catalogue_categories','COUNT(*)',array('c_name'=>$catalogue_name))>1000)) return array(); // Too many!
	}

	if (is_null($category_id))
	{
		if (is_null($is_tree)) return array();
		if ($is_tree==0)
		{
			$temp_rows=$GLOBALS['SITE_DB']->query_select('catalogue_categories',array('id','cc_title'),array('c_name'=>$catalogue_name,'cc_parent_id'=>NULL),'ORDER BY id DESC',300/*reasonable limit to stop it dying*/);
			if (get_page_name()=='cms_catalogues')
			{
				if (count($temp_rows)==300) attach_message(do_lang_tempcode('TOO_MUCH_CHOOSE__RECENT_ONLY',escape_html(integer_format(300))),'warn');
			}
			$children=array();
			foreach ($temp_rows as $row)
			{
				$children=array_merge(get_catalogue_entries_tree($catalogue_name,$submitter,$row['id'],NULL,get_translated_text($row['cc_title']),1,$editable_filter),$children);
			}
			return $children;
		}

		$temp_rows=$GLOBALS['SITE_DB']->query_select('catalogue_categories',array('id','cc_title'),array('c_name'=>$catalogue_name,'cc_parent_id'=>NULL),'ORDER BY id',1);
		if (!array_key_exists(0,$temp_rows)) return array();
		$category_id=$temp_rows[0]['id'];
		$title=get_translated_text($temp_rows[0]['cc_title']);
	}
	if (is_null($tree)) $tree='';

	if (!has_category_access(get_member(),'catalogues_catalogue',$catalogue_name)) return array();
	if ((get_value('disable_cat_cat_perms')!=='1') && (!has_category_access(get_member(),'catalogues_category',strval($category_id)))) return array();

	// Put our title onto our tree
	if (is_null($title)) $title=get_translated_text($GLOBALS['SITE_DB']->query_value('catalogue_categories','cc_title',array('id'=>$category_id)));
	$tree.=$title;

	// We'll be putting all children in this entire tree into a single list
	$children=array();
	$children[0]=array();
	$children[0]['id']=$category_id;
	$children[0]['title']=$title;
	$children[0]['tree']=$tree;

	// Children of this category
	$rows=$GLOBALS['SITE_DB']->query_select('catalogue_categories',array('id','cc_title'),array('cc_parent_id'=>$category_id),'',300/*reasonable limit to stop it dying*/);
	if (get_page_name()=='cms_catalogues')
	{
		if (count($rows)==300) attach_message(do_lang_tempcode('TOO_MUCH_CHOOSE__RECENT_ONLY',escape_html(integer_format(300))),'warn');
	}
	$where=array('cc_id'=>$category_id);
	if (!is_null($submitter)) $where['ce_submitter']=$submitter;
	$erows=$GLOBALS['SITE_DB']->query_select('catalogue_entries',array('id','ce_submitter'),$where,'ORDER BY ce_add_date DESC',1000/*reasonable limit*/);
	if (get_page_name()=='cms_catalogues')
	{
		if (count($erows)==300) attach_message(do_lang_tempcode('TOO_MUCH_CHOOSE__RECENT_ONLY',escape_html(integer_format(300))),'warn');
	}
	$children[0]['entries']=array();
	foreach ($erows as $row)
	{
		if (($editable_filter) && (!has_edit_permission('mid',get_member(),$row['ce_submitter'],'cms_catalogues',array('catalogues_catalogue',$catalogue_name,'catalogues_category',$category_id)))) continue;

		$entry_fields=get_catalogue_entry_field_values($catalogue_name,$row['id'],array(0));
		$name=$entry_fields[0]['effective_value']; // 'Name' is value of first field

		$children[0]['entries'][$row['id']]=$name;
	}
	$children[0]['child_entry_count']=count($children[0]['entries']);
	if ($levels===0) // We throw them away now because they're not on the desired level
	{
		$children[0]['entries']=array();
	}
	$children[0]['child_count']=count($rows);
	$tree.=' > ';
	if ($levels!==0)
	{
		foreach ($rows as $i=>$child)
		{
			$rows[$i]['_cc_title']=get_translated_text($child['cc_title']);
		}
		global $M_SORT_KEY;
		$M_SORT_KEY='_cc_title';
		usort($rows,'multi_sort');
		foreach ($rows as $child)
		{
			$child_id=$child['id'];
			$child_title=$child['_cc_title'];
			$child_tree=$tree;

			$child_children=get_catalogue_entries_tree($catalogue_name,$submitter,$child_id,$child_tree,$child_title,is_null($levels)?NULL:($levels-1),$editable_filter);

			$children=array_merge($children,$child_children);
		}
	}

	return $children;
}

/**
 * Get a nice, formatted XHTML list extending from the root, and showing all subcategories, and their subcategories (ad infinitum). The tree bit is because each entry in the list is shown to include the path through the tree that gets to it
 *
 * @param  ID_TEXT		The catalogue name
 * @param  ?AUTO_LINK	The currently selected entry (NULL: none)
 * @param  boolean		Whether to only show for what may be added to by the current member
 * @param  boolean		Whether to make the list elements store comma-separated child lists instead of IDs
 * @return tempcode		The list of categories
 */
function nice_get_catalogue_category_tree($catalogue_name,$it=NULL,$addable_filter=false,$use_compound_list=false)
{
	if ($GLOBALS['SITE_DB']->query_value('catalogue_categories','COUNT(*)',array('c_name'=>$catalogue_name))>1000) return new ocp_tempcode(); // Too many!

	$tree=array();
	$temp_rows=$GLOBALS['SITE_DB']->query('SELECT id,cc_title FROM '.get_table_prefix().'catalogue_categories WHERE '.db_string_equal_to('c_name',$catalogue_name).' AND cc_parent_id IS NULL ORDER BY id DESC',300/*reasonable limit to stop it dying*/);
	if (count($temp_rows)==300) attach_message(do_lang_tempcode('TOO_MUCH_CHOOSE__RECENT_ONLY',escape_html(integer_format(300))),'warn');
	foreach ($temp_rows as $row)
	{
		$category_id=$row['id'];
		$title=get_translated_text($row['cc_title']);
		$subtree=get_catalogue_category_tree($catalogue_name,$category_id,NULL,$title,NULL,$addable_filter,$use_compound_list);
		if (($use_compound_list) && (array_key_exists(0,$subtree))) $subtree=$subtree[0];
		$tree=array_merge($tree,$subtree);
	}

	$out=new ocp_tempcode();
	foreach ($tree as $category)
	{
		if (($addable_filter) && (!$category['addable'])) continue;

		$selected=($category['id']==$it);
		$line=do_template('CATALOGUE_CATEGORIES_LIST_LINE',array('_GUID'=>'9f6bfc4f28c154c8f5d8887ce0d47c1c','TREE'=>$category['tree'],'COUNT'=>integer_format($category['count'])));
		$out->attach(form_input_list_entry(!$use_compound_list?strval($category['id']):$category['compound_list'],$selected,$line->evaluate()));
	}

	return $out;
}

/**
 * Get a list of maps containing all the subcategories, and path information, of the specified category - and those beneath it, recursively.
 *
 * @param  ID_TEXT		The catalogue name
 * @param  ?AUTO_LINK	The category being at the root of our recursion (NULL: true root category)
 * @param  ?tempcode		The tree up to this point in the recursion (NULL: blank, as we are starting the recursion)
 * @param  ?string		The category name of the $category_id we are currently going through (NULL: look it up). This is here for efficiency reasons, as finding children IDs to recurse to also reveals the childs title
 * @param  ?integer		The number of recursive levels to search (NULL: all)
 * @param  boolean		Whether to only show for what may be added to by the current member
 * @param  boolean		Whether to make the list elements store comma-separated child lists instead of IDs
 * @return array			A list of maps for all subcategories. Each map entry containins the fields 'id' (category ID) and 'tree' (tree path to the category, including the categories own title), and 'count' (the number of entries in the category).
 */
function get_catalogue_category_tree($catalogue_name,$category_id,$tree=NULL,$title=NULL,$levels=NULL,$addable_filter=false,$use_compound_list=false)
{
	if ($levels==-1) return array();

	if (!has_category_access(get_member(),'catalogues_catalogue',$catalogue_name)) return array();
	if ((!is_null($category_id)) && (get_value('disable_cat_cat_perms')!=='1') && (!has_category_access(get_member(),'catalogues_category',strval($category_id)))) return array();

	if (is_null($tree)) $tree=new ocp_tempcode();

	// Put our title onto our tree
	if (is_null($title))
	{
		if (is_null($category_id))
		{
			$_title=$GLOBALS['SITE_DB']->query_value_null_ok('catalogue_categories','cc_title',array('id'=>$category_id));
		} else
		{
			$_title=$GLOBALS['SITE_DB']->query_value('catalogue_categories','cc_title',array('id'=>$category_id));
		}
		$title=is_null($_title)?do_lang('HOME'):get_translated_text($_title);
	}
	$tree->attach($title);

	// We'll be putting all children in this entire tree into a single list
	$children=array();
	$is_tree=$GLOBALS['SITE_DB']->query_value_null_ok('catalogues','c_is_tree',array('c_name'=>$catalogue_name));
	if (is_null($is_tree)) warn_exit(do_lang_tempcode('_MISSING_RESOURCE','catalogue:'.escape_html($catalogue_name)));
	if ((!is_null($category_id))/* || ($is_tree==1)*/)
	{
		$children[0]['id']=$category_id;
		$children[0]['title']=$title;
		$children[0]['tree']=$tree;
		$children[0]['compound_list']=strval($category_id).',';
		$children[0]['count']=$GLOBALS['SITE_DB']->query_value('catalogue_entries','COUNT(*)',array('cc_id'=>$category_id));
		if ($addable_filter) $children[0]['addable']=has_submit_permission('mid',get_member(),get_ip_address(),'cms_catalogues',array('catalogues_catalogue',$catalogue_name,'catalogues_category',$category_id));
	}

	// Children of this category
	$tree2=new ocp_tempcode();
	$tree2->attach($tree);
	$tree2->attach(do_template('BREADCRUMB'));
	$rows=$GLOBALS['SITE_DB']->query_select('catalogue_categories',array('id','cc_title'),array('c_name'=>$catalogue_name,'cc_parent_id'=>$category_id),'ORDER BY id DESC',300/*reasonable limit to stop it dying*/);
	foreach ($rows as $i=>$child)
	{
		$rows[$i]['text_original']=get_translated_text($child['cc_title']);
	}
	if (get_page_name()=='cms_catalogues')
	{
		if (count($rows)==300) attach_message(do_lang_tempcode('TOO_MUCH_CHOOSE__RECENT_ONLY',escape_html(integer_format(300))),'warn');
	}
	global $M_SORT_KEY;
	$M_SORT_KEY='text_original';
	usort($rows,'multi_sort');
	$no_root=!array_key_exists(0,$children);
	if (!$no_root) $children[0]['child_count']=count($rows);
	if ($levels!==0)
	{
		foreach ($rows as $child)
		{
			$child_id=$child['id'];
			$child_title=$child['text_original'];
			$child_tree=new ocp_tempcode();
			$child_tree->attach($tree2);

			$child_children=get_catalogue_category_tree($catalogue_name,$child_id,$child_tree,$child_title,is_null($levels)?NULL:($levels-1),$addable_filter,$use_compound_list);
			if ($child_children!=array())
			{
				if ($use_compound_list)
				{
					list($child_children,$_compound_list)=$child_children;
					if (!$no_root) $children[0]['compound_list'].=$_compound_list;
				}

				$children=array_merge($children,$child_children);
			}
		}
	}

	return $use_compound_list?array($children,$no_root?'':$children[0]['compound_list']):$children;
}

/**
 * Get a formatted XHTML string of the route back to the specified root, from the specified category.
 *
 * @param  AUTO_LINK		The category we are finding for
 * @param  ?AUTO_LINK	The root of the tree (NULL: the true root)
 * @param  boolean		Whether to include category links at this level (the recursed levels will always contain links - the top level is optional, hence this parameter)
 * @return tempcode		The tree route
 */
function catalogue_category_breadcrumbs($category_id,$root=NULL,$no_link_for_me_sir=true)
{
	$map=array('page'=>'catalogues','type'=>'category','id'=>$category_id);
	if (!is_null($root)) $map['root']=$root;
	$url=build_url($map,get_module_zone('catalogues'));

	if (is_null($category_id)) return new ocp_tempcode();

	if (($category_id!=$root) || (!$no_link_for_me_sir))
	{
		global $PT_PAIR_CACHE;
		if (!array_key_exists($category_id,$PT_PAIR_CACHE))
		{
			$category_rows=$GLOBALS['SITE_DB']->query_select('catalogue_categories',array('cc_parent_id','cc_title'),array('id'=>$category_id),'',1);
			if (!array_key_exists(0,$category_rows)) fatal_exit(do_lang_tempcode('CAT_NOT_FOUND',escape_html(strval($category_id))));
			$PT_PAIR_CACHE[$category_id]=$category_rows[0];
		}

		if ($PT_PAIR_CACHE[$category_id]['cc_parent_id']==$category_id) fatal_exit(do_lang_tempcode('RECURSIVE_TREE_CHAIN',escape_html(strval($category_id))));
	}

	if ($category_id==$root)
	{
		$below=new ocp_tempcode();
	} else
	{
		$below=catalogue_category_breadcrumbs($PT_PAIR_CACHE[$category_id]['cc_parent_id'],$root,false);
	}

	if (!$no_link_for_me_sir)
	{
		$title=get_translated_text($PT_PAIR_CACHE[$category_id]['cc_title']);
   	if (!$below->is_empty()) $tpl_url=do_template('BREADCRUMB_ESCAPED'); else $tpl_url=new ocp_tempcode();
   	$tpl_url->attach(hyperlink($url,escape_html($title),false,false,do_lang_tempcode('GO_BACKWARDS_TO',$title),NULL,NULL,'up'));
	} else $tpl_url=new ocp_tempcode();

	$below->attach($tpl_url);
	return $below;
}

/**
 * Check the current catalogue is an ecommerce catalogue
 *
 * @param  SHORT_TEXT		Catalogue name
 * @return boolean			Status of ecommerce catalogue check
*/
function is_ecommerce_catalogue($catalogue_name)
{
	if (!addon_installed('ecommerce')) return false;
	if (!addon_installed('shopping')) return false;

	if (is_null($GLOBALS['SITE_DB']->query_value_null_ok('catalogues','c_name',array('c_name'=>$catalogue_name,'c_ecommerce'=>1))))
		return false;
	else
		return true;
}

/**
 * Check selected entry is an ecommerce catalogue entry
 *
 * @param  AUTO_LINK			Entry ID
 * @return boolean			Status of entry type check
*/
function is_ecommerce_catalogue_entry($entry_id)
{
	$catalogue_name=$GLOBALS['SITE_DB']->query_value('catalogue_entries','c_name',array('id'=>$entry_id));

	return is_ecommerce_catalogue($catalogue_name);
}

/**
 * Display a catalogue entry
 *
 * @param  AUTO_LINK		Entry ID
 * @param  boolean		Whether to skip rendering a title
 * @return tempcode		Tempcode interface to display an entry
 */
function render_catalogue_entry_screen($id,$no_title=false)
{	
	require_code('feedback');

	if (addon_installed('ecommerce'))
	{
		require_code('ecommerce');
	}

	require_code('images');	

	require_css('catalogues');	

	require_lang('catalogues');

	$entries=$GLOBALS['SITE_DB']->query_select('catalogue_entries',array('*'),array('id'=>$id),'',1);
	if (!array_key_exists(0,$entries))
	{
		return warn_screen(get_page_title('CATALOGUES'),do_lang_tempcode('MISSING_RESOURCE'));
	}
	$entry=$entries[0];

	$categories=$GLOBALS['SITE_DB']->query_select('catalogue_categories',array('*'),array('id'=>$entry['cc_id']),'',1);
	if (!array_key_exists(0,$categories)) warn_exit(do_lang_tempcode('CAT_NOT_FOUND',strval($entry['cc_id'])));
	$category=$categories[0];
	$GLOBALS['FEED_URL']=find_script('backend').'?mode=catalogues&filter='.strval($entry['cc_id']);

	$catalogue_name=$category['c_name'];
	$catalogues=$GLOBALS['SITE_DB']->query_select('catalogues',array('*'),array('c_name'=>$catalogue_name),'',1);
	if (!array_key_exists(0,$catalogues)) warn_exit(do_lang_tempcode('CATALOGUE_NOT_FOUND',$catalogue_name));
	$catalogue=$catalogues[0];

	// Permission for here?
	if (!has_category_access(get_member(),'catalogues_catalogue',$catalogue_name))
	{
		access_denied('CATALOGUE_ACCESS');
	}
	if ((get_value('disable_cat_cat_perms')!=='1') && (!has_category_access(get_member(),'catalogues_category',strval($entry['cc_id']))))
	{
		access_denied('CATEGORY_ACCESS');
	}

	$ecommerce=is_ecommerce_catalogue($catalogue_name);

	if($ecommerce)
		$tpl_set	=	'products';
	else
		$tpl_set	=	$catalogue_name;

	$root=get_param_integer('root',NULL);
	$map=get_catalogue_entry_map($entry,$catalogue,'PAGE',$tpl_set,$root,NULL,NULL,true,true);

	if (get_db_type()!='xml')
	{
		$entry['ce_views']++;
		$GLOBALS['SITE_DB']->query_update('catalogue_entries',array('ce_views'=>$entry['ce_views']),array('id'=>$id),'',1,NULL,false,true);
	}

	// Validation
	if ($entry['ce_validated']==0)
	{
		if (!has_specific_permission(get_member(),'jump_to_unvalidated'))
			access_denied('SPECIFIC_PERMISSION','jump_to_unvalidated');

		$map['WARNINGS']=do_template('WARNING_TABLE',array('_GUID'=>'bf604859a572ca53e969bec3d91f9cfb','WARNING'=>do_lang_tempcode((get_param_integer('redirected',0)==1)?'UNVALIDATED_TEXT_NON_DIRECT':'UNVALIDATED_TEXT')));
	} else $map['WARNINGS']='';

	//Finding any hook exists for this product--------------------
	if (addon_installed('ecommerce'))
	{
		$object=find_product(strval($id));
		if (is_object($object) && method_exists($object,'get_custom_product_map_fields'))
		{
			$object->get_custom_product_map_fields($id,$map);
		}
	}
	//------------------------------------------------------------


	$map['ENTRY']=do_template('CATALOGUE_'.$tpl_set.'_ENTRY',$map,NULL,false,'CATALOGUE_DEFAULT_ENTRY');
	$map['ADD_DATE']=get_timezoned_date($entry['ce_add_date']);
	$map['ADD_DATE_RAW']=strval($entry['ce_add_date']);
	$map['EDIT_DATE']=is_null($entry['ce_edit_date'])?'':get_timezoned_date($entry['ce_edit_date']);
	$map['EDIT_DATE_RAW']=is_null($entry['ce_edit_date'])?'':strval($entry['ce_edit_date']);
	$map['VIEWS']=integer_format($entry['ce_views']);
	$title_to_use=do_lang_tempcode($catalogue_name.'__CATALOGUE_ENTRY',$map['FIELD_0']);
	$title_to_use_2=do_lang($catalogue_name.'__CATALOGUE_ENTRY',$map['FIELD_0_PLAIN'],NULL,NULL,NULL,false);
	if (is_null($title_to_use_2))
	{
		$title_to_use=do_lang_tempcode('DEFAULT__CATALOGUE_ENTRY',$map['FIELD_0']);
		$title_to_use_2=do_lang('DEFAULT__CATALOGUE_ENTRY',$map['FIELD_0_PLAIN']);
	}
	if ($no_title)
	{
		$map['TITLE']=new ocp_tempcode();
	} else
	{
		if (addon_installed('awards'))
		{
			require_code('awards');
			$awards=find_awards_for('catalogue_entry',strval($id));
		} else $awards=array();
		$map['TITLE']=get_page_title($title_to_use,false,NULL,NULL,$awards);
	}
	$map['SUBMITTER']=strval($entry['ce_submitter']);

	require_code('seo2');
	if (is_object($title_to_use_2)) $title_to_use_2=$title_to_use_2->evaluate();
	seo_meta_load_for('catalogue_entry',strval($id),strip_tags($title_to_use_2));

	if ($map['TREE']==='')
	{
		$map['TREE']=new ocp_tempcode();
		$url=build_url(array('page'=>'_SELF','type'=>'index','id'=>$catalogue_name),'_SELF');
		$map['TREE']->attach(hyperlink($url,escape_html(get_translated_text($catalogue['c_title'])),false,false,do_lang('INDEX')));
		$map['TREE']->attach(do_template('BREADCRUMB_ESCAPED'));
		$url=build_url(array('page'=>'_SELF','type'=>'category','id'=>$category['id']),'_SELF');
		$map['TREE']->attach(hyperlink($url,escape_html(get_translated_text($category['cc_title'])),false,false,do_lang('GO_BACKWARDS_TO',get_translated_text($category['cc_title'])),NULL,NULL,'up'));
	}
	$map['CATEGORY_TITLE']=get_translated_text($category['cc_title']);
	$map['CAT']=strval($entry['cc_id']);

	$map['TAGS']=get_loaded_tags('catalogue_entries');

	breadcrumb_add_segment($map['TREE'],$title_to_use);
	if (is_null($root)) breadcrumb_set_parents(array(array('_SELF:_SELF:misc'.($ecommerce?':ecommerce=1':''),do_lang('CATALOGUES'))));

	$GLOBALS['META_DATA']+=array(
		'created'=>date('Y-m-d',$entry['ce_add_date']),
		'creator'=>$GLOBALS['FORUM_DRIVER']->get_username($entry['ce_submitter']),
		'publisher'=>'', // blank means same as creator
		'modified'=>is_null($entry['ce_edit_date'])?'':date('Y-m-d',$entry['ce_edit_date']),
		'type'=>get_translated_text($catalogue['c_title']).' entry',
		'title'=>$title_to_use_2,
		'identifier'=>'_SEARCH:catalogues:entry:'.strval($id),
		'description'=>'',
	);

	return do_template('CATALOGUE_'.$tpl_set.'_ENTRY_SCREEN',$map,NULL,false,'CATALOGUE_DEFAULT_ENTRY_SCREEN');
}


