<?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		stats
 */


/**
 * Module page class.
 */
class Module_admin_stats
{

	/**
	 * Standard modular info function.
	 *
	 * @return ?array	Map of module info (NULL: module is disabled).
	 */
	function info()
	{
		$info=array();
		$info['author']='Philip Withnall';
		$info['organisation']='ocProducts';
		$info['hacked_by']=NULL;
		$info['hack_version']=NULL;
		$info['version']=8;
		$info['locked']=true;
		$info['update_require_upgrade']=1;
		return $info;
	}

	/**
	 * Standard modular uninstall function.
	 */
	function uninstall()
	{
		$GLOBALS['SITE_DB']->drop_if_exists('stats');
		$GLOBALS['SITE_DB']->drop_if_exists('usersonline_track');
		$GLOBALS['SITE_DB']->drop_if_exists('ip_country');

		delete_config_option('super_logging');
		delete_config_option('stats_store_time');
	}

	/**
	 * Standard modular install function.
	 *
	 * @param  ?integer	What version we're upgrading from (NULL: new install)
	 * @param  ?integer	What hack version we're upgrading from (NULL: new-install/not-upgrading-from-a-hacked-version)
	 */
	function install($upgrade_from=NULL,$upgrade_from_hack=NULL)
	{
		if (is_null($upgrade_from))
		{
			// Note: We have chosen not to create many indices because we want insertion to be very fast
			$GLOBALS['SITE_DB']->create_table('stats',array(
				'id'=>'*AUTO',
				'the_page'=>'SHORT_TEXT',
				'ip'=>'IP',
				'the_user'=>'USER',
				'date_and_time'=>'TIME',
				'referer'=>'URLPATH',
				's_get'=>'URLPATH',
				'post'=>'LONG_TEXT',
				'browser'=>'SHORT_TEXT',
				'milliseconds'=>'INTEGER',
				'operating_system'=>'SHORT_TEXT',
				'access_denied_counter'=>'INTEGER'
			));

			$GLOBALS['SITE_DB']->create_index('stats','member_track_1',array('the_user'));
			$GLOBALS['SITE_DB']->create_index('stats','member_track_2',array('ip'));
			$GLOBALS['SITE_DB']->create_index('stats','member_track_3',array('the_user','date_and_time'));
			$GLOBALS['SITE_DB']->create_index('stats','pages',array('the_page'));
			$GLOBALS['SITE_DB']->create_index('stats','date_and_time',array('date_and_time'));
			$GLOBALS['SITE_DB']->create_index('stats','milliseconds',array('milliseconds'));
			$GLOBALS['SITE_DB']->create_index('stats','referer',array('referer'));
			$GLOBALS['SITE_DB']->create_index('stats','browser',array('browser'));
			$GLOBALS['SITE_DB']->create_index('stats','operating_system',array('operating_system'));

			$GLOBALS['SITE_DB']->create_table('usersonline_track',array(
				'date_and_time'=>'*TIME',
				'peak'=>'INTEGER'
			));

			$GLOBALS['SITE_DB']->create_index('usersonline_track','peak_track',array('peak'));

			add_config_option('SUPER_LOGGING','super_logging','tick','return \'1\';','SITE','LOGGING',1);
		}

		if ((is_null($upgrade_from)) || ($upgrade_from<5))
		{
			add_config_option('STORE_TIME','stats_store_time','integer','return \'124\';','SITE','LOGGING',1);

			$GLOBALS['SITE_DB']->create_table('ip_country',array(
				'id'=>'*AUTO',
				'begin_num'=>'UINTEGER',
				'end_num'=>'UINTEGER',
				'country'=>'SHORT_TEXT'
			));
		}

		if (!is_null($upgrade_from))
		{
			if ($upgrade_from<3) // These are new in 3 of this module, and thus are for upgrades
			{
				$GLOBALS['SITE_DB']->add_table_field('stats','milliseconds','INTEGER');
			}

			if ($upgrade_from<4)
			{
				$GLOBALS['SITE_DB']->create_index('stats','pages',array('the_page'));
			}

			if ($upgrade_from<5)
			{
				$GLOBALS['SITE_DB']->add_table_field('stats','browser','SHORT_TEXT');
				$GLOBALS['SITE_DB']->add_table_field('stats','operating_system','SHORT_TEXT');
				$GLOBALS['SITE_DB']->add_table_field('zones','access_denied_counter','INTEGER');
			}

			if ($upgrade_from<6)
			{
				$GLOBALS['SITE_DB']->alter_table_field('stats','the_page','SHORT_TEXT');
			}

			if ($upgrade_from<7)
			{
				$GLOBALS['SITE_DB']->delete_table_field('stats','regionality');
			}

			if ($upgrade_from<8)
			{
				$GLOBALS['SITE_DB']->alter_table_field('stats','get','URLPATH','s_get');
			}
		}
	}

	/**
	 * Standard modular entry-point finder function.
	 *
	 * @return ?array	A map of entry points (type-code=>language-code) (NULL: disabled).
	 */
	function get_entry_points()
	{
		require_lang('stats');

		$ret=array('misc'=>'SITE_STATISTICS','overview'=>'OVERVIEW_STATISTICS','users_online'=>'USERS_ONLINE_STATISTICS','submission_rates'=>'SUBMISSION_STATISTICS','referrers'=>'TOP_REFERRERS','keywords'=>'TOP_SEARCH_KEYWORDS','page'=>'PAGES_STATISTICS','load_times'=>'LOAD_TIMES','clear'=>'CLEAR_STATISTICS','install_data'=>'INSTALL_GEOLOCATION_DATA');
		$hooks=find_all_hooks('modules','admin_stats');
		foreach (array_keys($hooks) as $hook)
		{
			require_code('hooks/modules/admin_stats/'.filter_naughty_harsh($hook));
			$ob=object_factory('Hook_admin_stats_'.filter_naughty_harsh($hook),true);
			if (is_null($ob)) continue;
			$info=$ob->info();
			if (!is_null($info)) $ret+=$info[0];
		}
		return $ret;
	}

	/**
	 * Standard modular run function.
	 *
	 * @return tempcode	The result of execution.
	 */
	function run()
	{
		$GLOBALS['SITE_DB']->query('DELETE FROM '.get_table_prefix().'stats WHERE date_and_time<'.strval(time()-60*60*24*intval(get_option('stats_store_time'))));

		require_lang('stats');
		require_code('svg');
		require_css('stats');

		if (get_param_integer('csv',0)==1)
			require_code('files2');

		$type=get_param('type','misc');

		if ($type=='misc') return $this->misc();
		elseif ($type=='overview') return $this->overview();
		elseif ($type=='users_online') return $this->users_online();
		elseif ($type=='submission_rates') return $this->submission_rates();
		elseif ($type=='referrers') return $this->referrers();
		elseif ($type=='keywords') return $this->keywords();
		elseif ($type=='_page') return $this->show_page();
		elseif ($type=='load_times') return $this->load_times();
		elseif ($type=='clear') return $this->clear();
		elseif ($type=='_clear') return $this->_clear();
		elseif ($type=='install_data') return $this->install_geolocation_data();
		elseif ($type=='page') return $this->page_stats();
		else
		{
			$hooks=find_all_hooks('modules','admin_stats');
			foreach (array_keys($hooks) as $hook)
			{
				require_code('hooks/modules/admin_stats/'.filter_naughty_harsh($hook));
				$ob=object_factory('Hook_admin_stats_'.filter_naughty_harsh($hook));
				if (method_exists($ob,$type)) return call_user_func_array(array(&$ob,$type),array(&$this,$type));
			}
		}
		return new ocp_tempcode();
	}

	/**
	 * The do-next manager for before content management.
	 *
	 * @return tempcode		The UI
	 */
	function misc()
	{
		$GLOBALS['HELPER_PANEL_PIC']='pagepics/statistics';
		$GLOBALS['HELPER_PANEL_TUTORIAL']='tut_statistics';

		require_code('templates_donext');
		$test=$GLOBALS['SITE_DB']->query_value('ip_country','COUNT(*)');
		$actions=array(
			/*	 type							  page	 params													 zone	  */
			array('statistics',array('_SELF',array('type'=>'overview'),'_SELF'),do_lang('OVERVIEW_STATISTICS'),('DESCRIPTION_OVERVIEW_STATISTICS')),
			array('page_views',array('_SELF',array('type'=>'page'),'_SELF'),do_lang('PAGES_STATISTICS'),('DOC_PAGE_STATISTICS')),
			array('users_online',array('_SELF',array('type'=>'users_online'),'_SELF'),do_lang('USERS_ONLINE_STATISTICS'),('DOC_USERS_ONLINE_STATISTICS')),
			array('submits',array('_SELF',array('type'=>'submission_rates'),'_SELF'),do_lang('SUBMISSION_STATISTICS'),('DOC_SUBMISSION_STATISTICS')),
			array('load_times',array('_SELF',array('type'=>'load_times'),'_SELF'),do_lang('LOAD_TIMES'),('DOC_LOAD_TIMES')),
			array('top_referrers',array('_SELF',array('type'=>'referrers'),'_SELF'),do_lang('TOP_REFERRERS'),('DOC_TOP_REFERRERS')),
			array('top_keywords',array('_SELF',array('type'=>'keywords'),'_SELF'),do_lang('TOP_SEARCH_KEYWORDS'),('DOC_TOP_SEARCH_KEYWORDS')),
		);
		$hooks=find_all_hooks('modules','admin_stats');
		foreach (array_keys($hooks) as $hook)
		{
			require_code('hooks/modules/admin_stats/'.filter_naughty_harsh($hook));
			$ob=object_factory('Hook_admin_stats_'.filter_naughty_harsh($hook),true);
			if (is_null($ob)) continue;
			$info=$ob->info();
			if (!is_null($info)) $actions=array_merge($actions,array($info[1]));
		}
		if ($test==0) $actions[]=array('geolocate',array('_SELF',array('type'=>'install_data'),'_SELF'),do_lang('INSTALL_GEOLOCATION_DATA'),('DOC_INSTALL_GEOLOCATION_DATA'));
		$actions[]=array('clear_stats',array('_SELF',array('type'=>'clear'),'_SELF'),do_lang('CLEAR_STATISTICS'),do_lang_tempcode('DESCRIPTION_CLEAR_STATISTICS'));
		return do_next_manager(get_screen_title('SITE_STATISTICS'),comcode_lang_string('DOC_STATISTICS'),
					$actions,
					do_lang('SITE_STATISTICS')
		);
	}

	/**
	 * An interface for choosing between dates.
	 *
	 * @param  tempcode		The title to display.
	 * @param  boolean		Whether display is dependent on what we kept in our stats table.
	 * @param  ?tempcode		Extra fields to request (NULL: none).
	 * @param  ?tempcode		The message to show for date selection (NULL: default).
	 * @return tempcode		The result of execution.
	 */
	function get_between($title,$stats_table=false,$extra_fields=NULL,$message=NULL)
	{
		require_code('form_templates');

		$fields=new ocp_tempcode();
		$month_start=utctime_to_usertime(mktime(0,0,0,intval(date('m')),1,intval(date('Y'))));
		$prior_month=intval(date('m'))-1;
		$prior_year=intval(date('Y'));
		if ($prior_month==0)
		{
			$prior_month=12;
			$prior_year--;
		}
		$prior_month_start=utctime_to_usertime(mktime(0,0,0,$prior_month,1,$prior_year));
		$first_stat=$stats_table?$GLOBALS['SITE_DB']->query_value_null_ok('stats','MIN(date_and_time)'):NULL;
		if (is_null($first_stat))
		{
			$year_start=intval(date('Y'))-5;
			$years_ahead=5;
			$first_stat=time();
		} else
		{
			$year_start=intval(date('Y',$first_stat));
			$years_ahead=intval(date('Y'))-$year_start;
		}
		if ($stats_table)
		{
			if (utctime_to_usertime($first_stat)>$month_start)
			{
				$prior_month_start=$first_stat;
				$month_start=time();
			}
		}
		$fields->attach(form_input_date(do_lang_tempcode('FROM'),do_lang_tempcode('TIME_RANGE_START'),'time_start',true,false,false,$prior_month_start,$years_ahead,$year_start));
		$fields->attach(form_input_date(do_lang_tempcode('TO'),do_lang_tempcode('TIME_RANGE_END'),'time_end',true,false,false,$month_start,$years_ahead,$year_start));
		if (!is_null($extra_fields)) $fields->attach($extra_fields);

		$post_url=get_self_url(false,false,array('dated'=>1),false,true);

		if (is_null($message))
		{
			$message=do_lang_tempcode($stats_table?'SELECT_STATS_RANGE':'_SELECT_STATS_RANGE',escape_html(get_timezoned_date($first_stat,false)));
		}

		breadcrumb_set_parents(array(array('_SELF:_SELF:misc',do_lang_tempcode('SITE_STATISTICS'))));

		return do_template('FORM_SCREEN',array('SKIP_VALIDATION'=>true,'GET'=>true,'TITLE'=>$title,'FIELDS'=>$fields,'TEXT'=>$message,'HIDDEN'=>'','URL'=>$post_url,'SUBMIT_NAME'=>do_lang_tempcode('CHOOSE')));
	}

	/**
	 * The UI to show user online statistics.
	 *
	 * @return tempcode		The UI
	 */
	function users_online()
	{
		//This needs to show a big scatter graph with the users online every day
		$title=get_screen_title('USERS_ONLINE_STATISTICS');

		$start=get_param_integer('start',0);
		$max=get_param_integer('max',50); // Intentionally the browse is disabled, as the graph will show all - we fudge $max_rows to $i
		$csv=get_param_integer('csv',0)==1;
		if ($csv)
		{
			if (function_exists('set_time_limit')) @set_time_limit(0);
			$start=0; $max=10000;
		}
		$sortables=array('date_and_time'=>do_lang_tempcode('DATE_TIME'),'peak'=>do_lang_tempcode('PEAK'));
		$test=explode(' ',get_param('sort','date_and_time DESC'),2);
		if (count($test)==1) $test[1]='DESC';
		list($sortable,$sort_order)=$test;
		if (((strtoupper($sort_order)!='ASC') && (strtoupper($sort_order)!='DESC')) || (!array_key_exists($sortable,$sortables)))
			log_hack_attack_and_exit('ORDERBY_HACK');
		global $NON_CANONICAL_PARAMS;
		$NON_CANONICAL_PARAMS[]='sort';

		$rows=$GLOBALS['SITE_DB']->query_select('usersonline_track',array('date_and_time','peak'),NULL,'ORDER BY '.$sortable.' '.$sort_order);
		if (count($rows)<1) return warn_screen($title,do_lang_tempcode('NO_DATA'));

		$base=$rows[0]['date_and_time'];
		foreach ($rows as $value)
		{
			if ($value['date_and_time']<$base) $base=$value['date_and_time'];
		}

		$data=array();

		foreach ($rows as $value)
		{
			$date=get_timezoned_date($value['date_and_time'],false);
			// If there's no data, or if this isn't the same as the last record and is more than an hour later than it
			if ((count($data)==0) || ($data[count($data)-1]['key']!=$date)) $data[]=array('t'=>$value['date_and_time']-$base,'key'=>$date,'value'=>$value['peak']);
			else $data[count($data)-1]['value']=max($value['peak'],$data[count($data)-1]['value']);
		}

		require_code('templates_results_table');
		$fields_title=results_field_title(array(do_lang_tempcode('DATE_TIME'),do_lang_tempcode('PEAK')),$sortables,'sort',$sortable.' '.$sort_order);
		$fields=new ocp_tempcode();
		$real_data=array();
		for ($i=0;$i<$max;$i++)
		{
			if (!array_key_exists($i,$data)) continue;

			$real_data[]=array(
				do_lang('DATE_TIME')=>get_timezoned_date($data[$i]['t']+$base),
				do_lang('COUNT_TOTAL')=>$data[$i]['value'],
			);

			$fields->attach(results_entry(array(get_timezoned_date($data[$i]['t']+$base,false),integer_format($data[$i]['value'])),true));
		}
		if ($csv) make_csv($real_data,'users_online.csv');
		$list=results_table(do_lang_tempcode('USERS_ONLINE_STATISTICS'),$start,'start',$max,'max',$i,$fields_title,$fields,$sortables,$sortable,$sort_order,'sort',new ocp_tempcode());

		$output=create_scatter_graph($data,do_lang('DATE'),do_lang('USERS_ONLINE'),'','');
		$this->save_graph('Global-Users-online',$output);

		$graph=do_template('STATS_GRAPH',array('_GUID'=>'9688722e526a814f3b90ca93a21333ad','GRAPH'=>get_custom_base_url().'/data_custom/modules/admin_stats/Global-Users-online.xml','TITLE'=>do_lang_tempcode('USERS_ONLINE_STATISTICS'),'TEXT'=>do_lang_tempcode('DESCRIPTION_USERS_ONLINE_STATISTICS')));

		breadcrumb_set_parents(array(array('_SELF:_SELF:misc',do_lang_tempcode('SITE_STATISTICS'))));

		return do_template('STATS_SCREEN',array('_GUID'=>'2e5a6a2f7317c80464c518996728d839','TITLE'=>$title,'GRAPH'=>$graph,'STATS'=>$list));
	}

	/**
	 * The UI to show submission rates.
	 *
	 * @return tempcode		The UI
	 */
	function submission_rates()
	{
		//Like the users online above, we need to use a nice scatter graph
		$title=get_screen_title('SUBMISSION_STATISTICS');

		$start=get_param_integer('start',0);
		$max=get_param_integer('max',50); // Intentionally the browse is disabled, as the graph will show all - we fudge $max_rows to $i
		$csv=get_param_integer('csv',0)==1;
		if ($csv)
		{
			if (function_exists('set_time_limit')) @set_time_limit(0);
			$start=0; $max=10000;
		}
		$sortables=array('date_and_time'=>do_lang_tempcode('DATE_TIME'));
		$test=explode(' ',either_param('sort','date_and_time DESC'));
		if (count($test)==1) $test[1]='DESC';
		list($sortable,$sort_order)=$test;
		if (((strtoupper($sort_order)!='ASC') && (strtoupper($sort_order)!='DESC')) || (!array_key_exists($sortable,$sortables)))
			log_hack_attack_and_exit('ORDERBY_HACK');
		global $NON_CANONICAL_PARAMS;
		$NON_CANONICAL_PARAMS[]='sort';

		$rows=$GLOBALS['SITE_DB']->query_select('adminlogs',array('date_and_time','COUNT(*) AS cnt'),NULL,'GROUP BY date_and_time ORDER BY '.$sortable.' '.$sort_order,3000/*reasonable limit*/);
		if (count($rows)<1) return warn_screen($title,do_lang_tempcode('NO_DATA'));
		//$max_rows=$GLOBALS['SITE_DB']->query_value('adminlogs','COUNT(DISTINCT date_and_time)');	Cannot do this as the DB does not do all the processing

		$data=array();
		$base=$rows[0]['date_and_time'];
		foreach ($rows as $value)
		{
			$date=get_timezoned_date($value['date_and_time'],false);
			$t=$value['date_and_time']-$base;
			if ($t<0) $t=0-$t;
			if ((count($data)==0) || ($data[count($data)-1]['key']!=$date)) $data[]=array('t'=>$t,'key'=>$date,'value'=>$value['cnt']);
			else $data[count($data)-1]['value']+=$value['cnt'];
		}

		require_code('templates_results_table');
		$fields_title=results_field_title(array(do_lang_tempcode('DATE_TIME'),do_lang_tempcode('PEAK')),$sortables,'sort',$sortable.' '.$sort_order);
		$fields=new ocp_tempcode();
		$real_data=array();
		for ($i=0;$i<$max;$i++)
		{
			if (!array_key_exists($i,$data)) continue;

			$real_data[]=array(
				do_lang('DATE_TIME')=>$data[$i]['key'],
				do_lang('COUNT_TOTAL')=>$data[$i]['value'],
			);

			$fields->attach(results_entry(array($data[$i]['key'],integer_format($data[$i]['value'])),true));
		}
		$list=results_table(do_lang_tempcode('SUBMISSION_STATISTICS'),$start,'start',$max,'max',$i,$fields_title,$fields,$sortables,$sortable,$sort_order,'sort',new ocp_tempcode());
		if ($csv) make_csv($real_data,'submission_rates.csv');

		$output=create_scatter_graph($data,do_lang('DATE'),do_lang('SUBMISSION_STATISTICS'),'','');
		$this->save_graph('Global-Submissions',$output);

		$graph=do_template('STATS_GRAPH',array('_GUID'=>'f6d5a58eae148a555e0f868eda245304','GRAPH'=>get_custom_base_url().'/data_custom/modules/admin_stats/Global-Submissions.xml','TITLE'=>do_lang_tempcode('SUBMISSION_STATISTICS'),'TEXT'=>do_lang_tempcode('DESCRIPTION_SUBMISSION_STATISTICS')));

		breadcrumb_set_parents(array(array('_SELF:_SELF:misc',do_lang_tempcode('SITE_STATISTICS'))));

		return do_template('STATS_SCREEN',array('_GUID'=>'66e8534ef342c1d0197f4ddb8f767025','TITLE'=>$title,'GRAPH'=>$graph,'STATS'=>$list));
	}

	/**
	 * The UI to show page load times.
	 *
	 * @return tempcode		The UI
	 */
	function load_times()
	{
		// Handle time range
		if (get_param_integer('dated',0)==0)
		{
			$title=get_screen_title('LOAD_TIMES');

			return $this->get_between($title,true);
		}
		$time_start=get_input_date('time_start',true);
		$time_end=get_input_date('time_end',true);
		if (!is_null($time_end)) $time_end+=60*60*24-1; // So it is end of day not start
		if (is_null($time_start)) $time_start=0;
		if (is_null($time_end)) $time_end=time();
		$first_stat=$GLOBALS['SITE_DB']->query_value_null_ok('stats','MIN(date_and_time)');
		if ($time_end<$first_stat) warn_exit(do_lang_tempcode('NO_DATA_SPECIFIC'));

		$start=get_param_integer('start',0);
		$max=get_param_integer('max',30);
		$csv=get_param_integer('csv',0)==1;
		if ($csv)
		{
			if (function_exists('set_time_limit')) @set_time_limit(0);
			$start=0;
			$max=10000;
			/*$time_start=0;		Actually, this is annoying. We have legitimate reason to filter, and cannot re-filter the data in Excel retro-actively
			$time_end=time();*/
		}

		$title=get_screen_title('LOAD_TIMES_RANGE',true,array(escape_html(get_timezoned_date($time_start,false)),escape_html(get_timezoned_date($time_end,false))));

		// We calculate MIN not AVG, because data can be made very dirty by slow clients or if the server is having trouble at one specfic point. It's a shame.
		$rows=$GLOBALS['SITE_DB']->query('SELECT the_page,MIN(milliseconds) AS avg FROM '.get_table_prefix().'stats WHERE date_and_time>'.strval((integer)$time_start).' AND date_and_time<'.strval((integer)$time_end).' GROUP BY the_page');
		if (count($rows)<1) return warn_screen($title,do_lang_tempcode('NO_DATA'));

		$data=array();
		foreach ($rows as $row)
		{
			$page=$row['the_page'];
			$page2=page_path_to_pagelink($page);
			if ($page2=='') $page2=$page;

			$avg=$row['avg']/1000.0;

			$data[$page2]=array($avg,$page);
		}

		$sortables=array('AVG(milliseconds)'=>do_lang_tempcode('LOAD_TIME'));
		$test=explode(' ',get_param('sort','AVG(milliseconds) DESC'),2);
		if (count($test)==1) $test[1]='DESC';
		list($sortable,$sort_order)=$test;
		if (((strtoupper($sort_order)!='ASC') && (strtoupper($sort_order)!='DESC')) || (!array_key_exists($sortable,$sortables)))
			log_hack_attack_and_exit('ORDERBY_HACK');
		global $NON_CANONICAL_PARAMS;
		$NON_CANONICAL_PARAMS[]='sort';

		global $M_SORT_KEY;
		$M_SORT_KEY=0;
		uasort($data,'multi_sort');
		if ($sort_order=='DESC')
		{
			$data=array_reverse($data,true);
		}

		require_code('templates_results_table');
		$fields_title=results_field_title(array(do_lang_tempcode('URL'),do_lang_tempcode('LOAD_TIME')),$sortables,'sort',$sortable.' '.$sort_order);
		$fields=new ocp_tempcode();
		$real_data=array();
		$i=0;
		foreach ($data as $url=>$_value)
		{
			if ($i<$start)
			{
				$i++;
				continue;
			} elseif ($i>=$start+$max) break;
			list($value,$page)=$_value;

			$real_data[]=array(
				'URL'=>$url,
				'Milliseconds'=>$value,
			);

			$fields->attach(results_entry(array(hyperlink(build_url(array('page'=>'_SELF','type'=>'_page','iscreen'=>$page),'_SELF'),escape_html($url)),escape_html(float_format($value)))));

			$i++;
		}
		$list=results_table(do_lang_tempcode('LOAD_TIMES'),$start,'start',$max,'max',count($data),$fields_title,$fields,$sortables,$sortable,$sort_order,'sort',new ocp_tempcode());
		if ($csv) make_csv($real_data,'load_times.csv');

		$output=create_bar_chart($data,do_lang('PAGE'),do_lang('LOAD_TIME'),'',do_lang('_SECONDS'));
		$this->save_graph('Global-Load-times',$output);

		$graph=do_template('STATS_GRAPH',array('GRAPH'=>get_custom_base_url().'/data_custom/modules/admin_stats/Global-Load-times.xml','TITLE'=>do_lang_tempcode('LOAD_TIMES'),'TEXT'=>do_lang_tempcode('DESCRIPTION_LOAD_TIMES')));

		breadcrumb_set_parents(array(array('_SELF:_SELF:misc',do_lang_tempcode('SITE_STATISTICS'))));

		return do_template('STATS_SCREEN',array('_GUID'=>'8f7c585bdbc0180ed116693723108e2b','TITLE'=>$title,'GRAPH'=>$graph,'STATS'=>$list));
	}

	/**
	 * The UI to show referrers.
	 *
	 * @return tempcode		The UI
	 */
	function referrers()
	{
		// Handle time range
		if (get_param_integer('dated',0)==0)
		{
			$title=get_screen_title('TOP_REFERRERS');

			return $this->get_between($title,true);
		}
		$time_start=get_input_date('time_start',true);
		$time_end=get_input_date('time_end',true);
		if (!is_null($time_end)) $time_end+=60*60*24-1; // So it is end of day not start
		if (is_null($time_start)) $time_start=0;
		if (is_null($time_end)) $time_end=time();
		$first_stat=$GLOBALS['SITE_DB']->query_value_null_ok('stats','MIN(date_and_time)');
		if ($time_end<$first_stat) warn_exit(do_lang_tempcode('NO_DATA_SPECIFIC'));

		$start=get_param_integer('start',0);
		$max=get_param_integer('max',20);
		$csv=get_param_integer('csv',0)==1;
		if ($csv)
		{
			if (function_exists('set_time_limit')) @set_time_limit(0);
			$start=0;
			$max=10000;
			/*$time_start=0;		Actually, this is annoying. We have legitimate reason to filter, and cannot re-filter the data in Excel retro-actively
			$time_end=time();*/
		}

		$title=get_screen_title('TOP_REFERRERS_RANGE',true,array(escape_html(get_timezoned_date($time_start,false)),escape_html(get_timezoned_date($time_end,false))));

		$non_local_filter='referer NOT LIKE \''.db_encode_like(preg_replace('#^https?://#','http://',get_base_url()).'%').'\'';
		$non_local_filter.=' AND referer NOT LIKE \''.db_encode_like(preg_replace('#^https?://#','https://',get_base_url()).'%').'\'';
		if (get_param_integer('debug',0)==1) $non_local_filter='1=1';

		$where=$non_local_filter.' AND date_and_time>'.strval((integer)$time_start).' AND date_and_time<'.strval((integer)$time_end);

		$rows=$GLOBALS['SITE_DB']->query('SELECT COUNT(*) AS cnt,referer FROM '.get_table_prefix().'stats WHERE '.$where.' GROUP BY referer');
		if (count($rows)<1) return warn_screen($title,do_lang_tempcode('NO_DATA'));

		$referrers=array();
		$total=0;
		foreach ($rows as $value)
		{
			$referrers[$value['referer']]=$value['cnt'];
			$total+=$referrers[$value['referer']];
		}

		$sortables=array('referer'=>do_lang_tempcode('TOP_REFERRERS'));
		$test=explode(' ',get_param('sort','referer DESC'),2);
		if (count($test)==1) $test[1]='DESC';
		list($sortable,$sort_order)=$test;
		if (((strtoupper($sort_order)!='ASC') && (strtoupper($sort_order)!='DESC')) || (!array_key_exists($sortable,$sortables)))
			log_hack_attack_and_exit('ORDERBY_HACK');
		global $NON_CANONICAL_PARAMS;
		$NON_CANONICAL_PARAMS[]='sort';

		if ($sort_order=='ASC') asort($referrers);
		else arsort($referrers);

		require_code('templates_results_table');
		$fields_title=results_field_title(array(do_lang_tempcode('URL'),do_lang_tempcode('COUNT_VIEWS')),$sortables,'sort',$sortable.' '.$sort_order);
		$fields=new ocp_tempcode();
		$i=0;
		$degrees=360.0/$total;
		$done_total=0;
		//$done=0;
		$data=array();

		$real_data=array();
		foreach ($referrers as $referrer=>$views)
		{
			if ($i<$start)
			{
				$i++;
				continue;
			} elseif ($i>=$start+$max) break;
			if ($referrer=='') $link=do_lang('_UNKNOWN'); else $link=hyperlink($referrer,escape_html($referrer));
			$fields->attach(results_entry(array($link,integer_format($views)),true));

			$real_data[]=array(
				do_lang('DATE_TIME')=>$referrer,
				do_lang('COUNT_TOTAL')=>$views,
			);

			//if ($done<20)
			//{
				$data[$referrer]=$views*$degrees;
				//$done++;
				$done_total+=$data[$referrer];
			//}
			$i++;
		}
		if ($csv) make_csv($real_data,'referrers.csv');

		if ((360-$done_total)>0)
		{
			$data[do_lang('OTHER')]=360-$done_total;
			$fields->attach(results_entry(array(do_lang('OTHER'),float_format((360-$done_total)/$degrees)),true));
		}

		$list=results_table(do_lang_tempcode('TOP_REFERRERS'),$start,'start',$max,'max',count($referrers),$fields_title,$fields,$sortables,$sortable,$sort_order,'sort',new ocp_tempcode());

		$output=create_pie_chart($data);
		$this->save_graph('Global-Referrers',$output);

		$graph=do_template('STATS_GRAPH',array('_GUID'=>'22c565665d8a98528659bbfc25526855','GRAPH'=>get_custom_base_url().'/data_custom/modules/admin_stats/Global-Referrers.xml','TITLE'=>do_lang_tempcode('REFERRER_SHARE'),'TEXT'=>do_lang_tempcode('DESCRIPTION_REFERRER_SHARE')));

		breadcrumb_set_parents(array(array('_SELF:_SELF:misc',do_lang_tempcode('SITE_STATISTICS'))));

		return do_template('STATS_SCREEN',array('_GUID'=>'777bc5f8573a5cef54aa0bc9bdc0ee29','TITLE'=>$title,'GRAPH'=>$graph,'STATS'=>$list));
	}

	/**
	 * The UI to show top search keywords.
	 *
	 * @return tempcode		The UI
	 */
	function keywords()
	{
		// Handle time range
		if (get_param_integer('dated',0)==0)
		{
			$title=get_screen_title('TOP_SEARCH_KEYWORDS');

			return $this->get_between($title,true);
		}
		$time_start=get_input_date('time_start',true);
		$time_end=get_input_date('time_end',true);
		if (!is_null($time_end)) $time_end+=60*60*24-1; // So it is end of day not start
		if (is_null($time_start)) $time_start=0;
		if (is_null($time_end)) $time_end=time();
		$first_stat=$GLOBALS['SITE_DB']->query_value_null_ok('stats','MIN(date_and_time)');
		if ($time_end<$first_stat) warn_exit(do_lang_tempcode('NO_DATA_SPECIFIC'));

		$start=get_param_integer('start',0);
		$max=get_param_integer('max',20);
		$csv=get_param_integer('csv',0)==1;
		if ($csv)
		{
			if (function_exists('set_time_limit')) @set_time_limit(0);
			$start=0;
			$max=10000;
			/*$time_start=0;		Actually, this is annoying. We have legitimate reason to filter, and cannot re-filter the data in Excel retro-actively
			$time_end=time();*/
		}

		$title=get_screen_title('TOP_SEARCH_KEYWORDS_RANGE',true,array(escape_html(get_timezoned_date($time_start,false)),escape_html(get_timezoned_date($time_end,false))));

		$sortables=array('referer'=>do_lang_tempcode('TOP_SEARCH_KEYWORDS'));
		$test=explode(' ',get_param('sort','referer DESC'),2);
		if (count($test)==1) $test[1]='DESC';
		list($sortable,$sort_order)=$test;
		if (((strtoupper($sort_order)!='ASC') && (strtoupper($sort_order)!='DESC')) || (!array_key_exists($sortable,$sortables)))
			log_hack_attack_and_exit('ORDERBY_HACK');
		global $NON_CANONICAL_PARAMS;
		$NON_CANONICAL_PARAMS[]='sort';

		$rows=$GLOBALS['SITE_DB']->query('SELECT referer FROM '.get_table_prefix().'stats WHERE referer LIKE \''.db_encode_like('http://www.google.%q=%').'\' AND date_and_time>'.strval((integer)$time_start).' AND date_and_time<'.strval((integer)$time_end).' ORDER BY '.$sortable.' '.$sort_order);
		if (count($rows)<1) return warn_screen($title,do_lang_tempcode('NO_DATA'));

		$keywords=array();
		$total=0;
		foreach ($rows as $value)
		{
			$matches=array();
			preg_match('#(&|\?)q=([^&]*)#',$value['referer'],$matches);
			if (!array_key_exists(1,$matches)) continue;
			$_keywords=explode('+',rawurldecode($matches[2]));
			foreach ($_keywords as $keyword)
			{
				$keyword=str_replace('"','',$keyword);
				if (trim($keyword)=='') continue;
				if (substr($keyword,0,strlen('cache:'))=='cache:') continue;

				if (!array_key_exists($keyword,$keywords)) $keywords[$keyword]=1;
				else $keywords[$keyword]++;
				$total++;
			}
		}

		if ($sort_order=='ASC') asort($keywords); else arsort($keywords);

		require_code('templates_results_table');
		$fields_title=results_field_title(array(do_lang_tempcode('KEYWORD'),do_lang_tempcode('COUNT_VIEWS')),$sortables,'sort',$sortable.' '.$sort_order);
		$fields=new ocp_tempcode();
		$degrees=($total==0)?360.0:(360/$total);
		$done_total=0;
		//$done=0;
		$data=array();
		$i=0;

		$real_data=array();
		foreach ($keywords as $keyword=>$views)
		{
			if ($i<$start)
			{
				$i++;
				continue;
			} elseif ($i>=$start+$max) break;
			if ($keyword=='') $link=do_lang('_UNKNOWN'); else $link=escape_html($keyword);
			$fields->attach(results_entry(array($link,escape_html(integer_format($views)))));

			$real_data[]=array(
				do_lang('KEYWORD')=>$keyword,
				do_lang('COUNT_TOTAL')=>$views,
			);

			//if ($done<20)
			//{
				$data[$keyword]=$keywords[$keyword]*$degrees;
				//$done++;
				$done_total+=$data[$keyword];
			//}
			$i++;
		}
		if ((360-$done_total)>0)
		{
			$data[do_lang('OTHER')]=360-$done_total;
			$fields->attach(results_entry(array(do_lang('OTHER'),float_format((360-$done_total)/$degrees)),true));
		}
		$list=results_table(do_lang_tempcode('TOP_SEARCH_KEYWORDS'),$start,'start',$max,'max',count($keywords),$fields_title,$fields,$sortables,$sortable,$sort_order,'sort',new ocp_tempcode());
		if ($csv) make_csv($real_data,'search_keywords.csv');

		$output=create_pie_chart($data);
		$this->save_graph('Global-Keywords',$output);

		$graph=do_template('STATS_GRAPH',array('_GUID'=>'a199b095199a0e337d38c78564909e52','GRAPH'=>get_custom_base_url().'/data_custom/modules/admin_stats/Global-Keywords.xml','TITLE'=>do_lang_tempcode('KEYWORDS_SHARE'),'TEXT'=>do_lang_tempcode('DESCRIPTION_KEYWORDS_SHARE')));

		breadcrumb_set_parents(array(array('_SELF:_SELF:misc',do_lang_tempcode('SITE_STATISTICS'))));

		return do_template('STATS_SCREEN',array('_GUID'=>'ab791072361184a05c7e60a3127ee439','TITLE'=>$title,'GRAPH'=>$graph,'STATS'=>$list));
	}

	/**
	 * The UI to show page view statistics.
	 *
	 * @return tempcode		The UI
	 */
	function page_stats()
	{
		//This will show a plain bar chart with all the pages listed

		// Handle time range
		if (get_param_integer('dated',0)==0)
		{
			$title=get_screen_title('PAGES_STATISTICS');

			return $this->get_between($title,true);
		}
		$time_start=get_input_date('time_start',true);
		$time_end=get_input_date('time_end',true);
		if (!is_null($time_end)) $time_end+=60*60*24-1; // So it is end of day not start
		if (is_null($time_start)) $time_start=0;
		if (is_null($time_end)) $time_end=time();
		$first_stat=$GLOBALS['SITE_DB']->query_value_null_ok('stats','MIN(date_and_time)');
		if ($time_end<$first_stat) warn_exit(do_lang_tempcode('NO_DATA_SPECIFIC'));

		$start=get_param_integer('start',0);
		$max=get_param_integer('max',30);
		$csv=get_param_integer('csv',0)==1;
		if ($csv)
		{
			if (function_exists('set_time_limit')) @set_time_limit(0);
			$start=0;
			$max=10000;
			/*$time_start=0;		Actually, this is annoying. We have legitimate reason to filter, and cannot re-filter the data in Excel retro-actively
			$time_end=time();*/
		}

		$title=get_screen_title('PAGES_STATISTICS_RANGE',true,array(escape_html(get_timezoned_date($time_start,false)),escape_html(get_timezoned_date($time_end,false))));

		$rows=$GLOBALS['SITE_DB']->query_select('stats',array('the_page'),NULL,'GROUP BY the_page ORDER BY COUNT(*) DESC',3000);
		if (count($rows)<1) return warn_screen($title,do_lang_tempcode('NO_DATA'));

		$views=array(do_lang('_ALL')=>0);
		$total=0;
		foreach ($rows as $row)
		{
			$page=$row['the_page'];
			$matches=array();
			if ((preg_match('#^/?([^/]+)/pages/([^/]+)/(\w\w/)?([^/\.]+)\.(php|txt|htm)$#',$page,$matches)==1) && ($matches[4]=='catalogues') && (addon_installed('catalogues')) && ($GLOBALS['SITE_DB']->query_value('catalogue_categories','COUNT(*)',NULL,'',true)<300))
			{
				require_lang('catalogues');
				$categories=$GLOBALS['SITE_DB']->query_select('catalogue_categories',array('id','cc_title'),NULL,'',NULL,NULL,true);
				foreach ($categories as $cat)
				{
					$where=db_string_equal_to('the_page',$page);
					if (substr($page,0,6)=='pages/') $where.=' OR '.db_string_equal_to('the_page','/'.$page); // Legacy compatibility
					$count=$GLOBALS['SITE_DB']->query_value_null_ok_full('SELECT COUNT(*) FROM '.$GLOBALS['SITE_DB']->get_table_prefix().'stats WHERE ('.$where.') AND s_get LIKE \''.db_encode_like('<param>page=catalogues</param>\n<param>type=category</param>\n<param>id='.strval($cat['id']).'</param>%').'\' AND date_and_time>'.strval((integer)$time_start).' AND date_and_time<'.strval((integer)$time_end));
					$views[do_lang('CATALOGUE_CATEGORY').': '.get_translated_text($cat['cc_title'])]=array($count,$page);
					$total+=$count;
				}
				continue;
			} else
			{
				$page2=page_path_to_pagelink($page);
				if ($page2=='') $page2=$page;
			}
			$where=db_string_equal_to('the_page',$page);
			if (substr($page,0,6)=='pages/') $where.=' OR '.db_string_equal_to('the_page','/'.$page); // Legacy compatibility
			$views[$page2]=array($GLOBALS['SITE_DB']->query_value_null_ok_full('SELECT COUNT(*) FROM '.$GLOBALS['SITE_DB']->get_table_prefix().'stats WHERE ('.$where.') AND date_and_time>'.strval((integer)$time_start).' AND date_and_time<'.strval((integer)$time_end)),$page);
			$total+=$views[$page2][0];
		}
		$views[do_lang('_ALL')]=array($total,NULL);

		$sortables=array('views'=>do_lang_tempcode('COUNT_VIEWS'));
		$test=explode(' ',get_param('sort','views DESC'),2);
		if (count($test)==1) $test[1]='DESC';
		list($sortable,$sort_order)=$test;
		if (((strtoupper($sort_order)!='ASC') && (strtoupper($sort_order)!='DESC')) || (!array_key_exists($sortable,$sortables)))
			log_hack_attack_and_exit('ORDERBY_HACK');
		global $NON_CANONICAL_PARAMS;
		$NON_CANONICAL_PARAMS[]='sort';

		global $M_SORT_KEY;
		$M_SORT_KEY=0;
		uasort($views,'multi_sort');
		if ($sort_order=='DESC')
		{
			$views=array_reverse($views,true);
		}

		require_code('templates_results_table');
		$fields_title=results_field_title(array(do_lang_tempcode('URL'),do_lang_tempcode('COUNT_VIEWS')),$sortables,'sort',$sortable.' '.$sort_order);
		$fields=new ocp_tempcode();
		$i=0;
		$real_data=array();
		foreach ($views as $url=>$_value)
		{
			if ($i<$start)
			{
				$i++;
				continue;
			} elseif ($i>=$start+$max) break;
			list($value,$page)=$_value;

			$real_data[]=array(
				do_lang('PAGE_OR_URL')=>is_null($page)?$url:$page,
				do_lang('COUNT_TOTAL')=>$value,
			);

			$fields->attach(results_entry(array(is_null($page)?make_string_tempcode(escape_html($url)):hyperlink(build_url(array('page'=>'_SELF','type'=>'_page','iscreen'=>$page),'_SELF'),escape_html($url)),escape_html(integer_format($value)))));

			$i++;
		}
		unset($views['('.do_lang('ALL').')']);
		$list=results_table(do_lang_tempcode('PAGES_STATISTICS'),$start,'start',$max,'max',count($views),$fields_title,$fields,$sortables,$sortable,$sort_order,'sort',new ocp_tempcode());
		if ($csv) make_csv($real_data,'page_stats.csv');

		$output=create_bar_chart(array_slice($views,$start,$max),do_lang('PAGE'),do_lang('COUNT_VIEWS'),'','');
		$this->save_graph('Global-Views',$output);

		$graph=do_template('STATS_GRAPH',array('_GUID'=>'ea79fdc013046ef94992daeab961f2da','GRAPH'=>get_custom_base_url().'/data_custom/modules/admin_stats/Global-Views.xml','TITLE'=>do_lang_tempcode('PAGES_STATISTICS'),'TEXT'=>do_lang_tempcode('DESCRIPTION_PAGES_STATISTICS')));

		breadcrumb_set_parents(array(array('_SELF:_SELF:misc',do_lang_tempcode('SITE_STATISTICS'))));

		return do_template('STATS_SCREEN',array('_GUID'=>'cfe7d5aee8aa3c0d3a54bd3bf2d09e7f','TITLE'=>$title,'GRAPH'=>$graph,'STATS'=>$list));
	}

	/**
	 * The UI to show page view statistics for the front page.
	 *
	 * @return tempcode		The UI
	 */
	function overview()
	{
		$page_request=_request_page(get_zone_default_page(''),'');
		$page=$page_request[count($page_request)-1];

		$page='pages/comcode_custom/'.get_site_default_lang().'/start.txt';
		if (!file_exists(get_custom_file_base().'/'.$page))
			$page='pages/comcode/'.get_site_default_lang().'/start.txt';
		if (!file_exists(get_file_base().'/'.$page))
			$page='pages/comcode/'.fallback_lang().'/start.txt';

		$title=get_screen_title('OVERVIEW_STATISTICS');

		list($graph_views_monthly,$list_views_monthly)=array_values($this->views_per_x($page,'views_hourly','VIEWS_PER_MONTH','DESCRIPTION_VIEWS_PER_MONTH',730,8766));

		//************************************************************************************************
		// Views
		//************************************************************************************************
		$start=get_param_integer('start_views',0);
		$max=get_param_integer('max_views',30);
		$sortables=array('date_and_time'=>do_lang_tempcode('DATE_TIME'));
		list($sortable,$sort_order)=explode(' ',get_param('sort_views','date_and_time DESC'));
		if (((strtoupper($sort_order)!='ASC') && (strtoupper($sort_order)!='DESC')) || (!array_key_exists($sortable,$sortables)))
			log_hack_attack_and_exit('ORDERBY_HACK');
		global $NON_CANONICAL_PARAMS;
		$NON_CANONICAL_PARAMS[]='sort_views';

		// NB: not used in default templates
		$where=db_string_equal_to('the_page',$page);
		if (substr($page,0,6)=='pages/') $where.=' OR '.db_string_equal_to('the_page','/'.$page); // Legacy compatibility
		$reasonable_max=10000;
		$rows=$GLOBALS['SITE_DB']->query('SELECT date_and_time FROM '.get_table_prefix().'stats WHERE ('.$where.') ORDER BY '.$sortable.' '.$sort_order,$reasonable_max);
		if (count($rows)<1)
		{
			$list_views=new ocp_tempcode();
		}
		else
		{
			require_code('templates_results_table');
			$fields_title=results_field_title(array(do_lang_tempcode('DATE_TIME'),do_lang_tempcode('COUNT_VIEWS')),$sortables,'sort',$sortable.' '.$sort_order);
			$fields=new ocp_tempcode();
			$i=0;
			while (array_key_exists($i,$rows))
			{
				$row=$rows[$i];
				$week=intval(round($row['date_and_time']/(60*60*24*7)));
				$date=get_timezoned_date(($week-1)*(60*60*24*7),false,false,false,true).' - '.get_timezoned_date(($week)*(60*60*24*7),false,false,false,true);
				$views=0;
				while (array_key_exists($i+$views,$rows))
				{
					$_week=intval(round($row['date_and_time']/(60*60*24*7)));

					if ($_week!=$week) break;

					$views++;
				}
				$i+=$views;
				if ($i<$start) continue;
				$fields->attach(results_entry(array($date,($i<$reasonable_max)?integer_format($views):('>'.integer_format($views))),true));
				if ($i>=$start+$max) break;
			}
			$list_views=results_table(do_lang_tempcode('PAGES_STATISTICS',escape_html($page)),$start,'start_views',$max,'max_views',$i,$fields_title,$fields,$sortables,$sortable,$sort_order,'sort_views');
		}

		breadcrumb_set_parents(array(array('_SELF:_SELF:misc',do_lang_tempcode('SITE_STATISTICS'))));

		return do_template('STATS_OVERVIEW_SCREEN',array('_GUID'=>'71be91ba0d83368e1e1ceaf39e506610','TITLE'=>$title,'STATS_VIEWS'=>$list_views,'GRAPH_VIEWS_MONTHLY'=>$graph_views_monthly,'STATS_VIEWS_MONTHLY'=>$list_views_monthly,));
	}

	/**
	 * The UI to show page view statistics for a single page.
	 *
	 * @return tempcode		The UI
	 */
	function show_page()
	{
		$page=get_param('iscreen');

		$title=get_screen_title(do_lang_tempcode('_PAGE_STATISTICS',escape_html($page)),false);

		//************************************************************************************************
		// Views per hour/day/week/month
		//************************************************************************************************
		list($graph_views_hourly,$list_views_hourly)=array_values($this->views_per_x($page,'views_hourly','VIEWS_PER_HOUR','DESCRIPTION_VIEWS_PER_HOUR',1));
		list($graph_views_daily,$list_views_daily)=array_values($this->views_per_x($page,'views_hourly','VIEWS_PER_DAY','DESCRIPTION_VIEWS_PER_DAY',24,168));
		list($graph_views_weekly,$list_views_weekly)=array_values($this->views_per_x($page,'views_hourly','VIEWS_PER_WEEK','DESCRIPTION_VIEWS_PER_WEEK',168,730));
		list($graph_views_monthly,$list_views_monthly)=array_values($this->views_per_x($page,'views_hourly','VIEWS_PER_MONTH','DESCRIPTION_VIEWS_PER_MONTH',730,8766));

		//************************************************************************************************
		// Page browser/referrer/operating/IP system share
		//************************************************************************************************
		list($graph_share_browser,$list_share_browser)=array_values($this->page_x_share($page,'browser','BROWSER_SHARE','DESCRIPTION_BROWSER_SHARE','USER_AGENT'));
		list($graph_share_referrer,$list_share_referrer)=array_values($this->page_x_share($page,'referer','REFERRER_SHARE','DESCRIPTION_REFERRER_SHARE','REFERER'));
		list($graph_share_os,$list_share_os)=array_values($this->page_x_share($page,'operating_system','OS_SHARE','DESCRIPTION_OS_SHARE','USER_OS'));
		list($graph_ip,$list_ip)=array_values($this->page_x_share($page,'ip','IP_ADDRESS_DISTRIBUTION','DESCRIPTION_IP_ADDRESS_DISTRIBUTION','IP_ADDRESS'));

		//************************************************************************************************
		// Keywords
		//************************************************************************************************
		$start=get_param_integer('start_keywords',0);
		$max=get_param_integer('max_keywords',30);
		$sortables=array('referer'=>do_lang_tempcode('TOP_SEARCH_KEYWORDS'));
		$test=explode(' ',get_param('sort_keywords','referer DESC'));
		if (count($test)==1) $test[1]='DESC';
		list($sortable,$sort_order)=$test;
		if (((strtoupper($sort_order)!='ASC') && (strtoupper($sort_order)!='DESC')) || (!array_key_exists($sortable,$sortables)))
			log_hack_attack_and_exit('ORDERBY_HACK');
		global $NON_CANONICAL_PARAMS;
		$NON_CANONICAL_PARAMS[]='sort_keywords';

		$where=db_string_equal_to('the_page',$page);
		if (substr($page,0,6)=='pages/') $where.=' OR '.db_string_equal_to('the_page','/'.$page); // Legacy compatibility
		$ip_filter=$GLOBALS['DEV_MODE']?'':(' AND '.db_string_not_equal_to('ip',get_ip_address()));
		$rows=$GLOBALS['SITE_DB']->query('SELECT id,referer FROM '.get_table_prefix().'stats WHERE ('.$where.')'.$ip_filter.' AND referer LIKE \''.db_encode_like('http://www.google.%q=%').'\' ORDER BY '.$sortable.' '.$sort_order,2000/*reasonable limit*/);
		if (count($rows)<1)
		{
			$list_keywords=new ocp_tempcode();
			$graph_keywords=new ocp_tempcode();
		}
		else
		{
			$keywords=array();
			$num_keywords=0;
			foreach ($rows as $value)
			{
				$matches=array();
				preg_match('#(&|\?)q=([^&]*)#',$value['referer'],$matches);
				if (!array_key_exists(2,$matches)) continue;
				$_keywords=explode('+',rawurldecode($matches[2]));
				foreach ($_keywords as $keyword)
				{
					$keyword=str_replace('"','',$keyword);
					if (trim($keyword)=='') continue;
					if (substr($keyword,0,strlen('cache:'))=='cache:') continue;

					$num_keywords++;

					if (!array_key_exists($keyword,$keywords))
					{
						$keywords[$keyword]=1;
					}
					else $keywords[$keyword]++;
				}
			}
			if ($num_keywords==0)
			{
				$list_keywords=new ocp_tempcode();
				$graph_keywords=new ocp_tempcode();
			} else
			{
				$degrees=360/$num_keywords;

				require_code('templates_results_table');
				$fields_title=results_field_title(array(do_lang_tempcode('KEYWORD'),do_lang_tempcode('PEAK')),$sortables,'sort',$sortable.' '.$sort_order);
				$fields=new ocp_tempcode();
				$done_total=0;
				//$done=0;
				$data=array();
				$i=0;

				foreach ($keywords as $key=>$value)
				{
					if ($i<$start)
					{
						$i++;
						continue;
					} elseif ($i>=$start+$max) break;
					if ($key=='') $link=do_lang('_UNKNOWN'); else $link=escape_html($key);
					$fields->attach(results_entry(array($link,escape_html(integer_format($value)))));

					//if ($done<20)
					//{
						$data[$key]=$degrees*$value;
						//$done++;
						$done_total+=$data[$key];
					//}
					$i++;
				}
				if ((360-$done_total)>0)
				{
					$data[do_lang('OTHER')]=360-$done_total;
					$fields->attach(results_entry(array(do_lang('OTHER'),float_format((360-$done_total)/$degrees)),true));
				}
				$list_keywords=results_table(do_lang_tempcode('TOP_SEARCH_KEYWORDS'),$start,'start_keywords',$max,'max_keywords',$i,$fields_title,$fields,$sortables,$sortable,$sort_order,'sort_keywords');

				$output=create_pie_chart($data);
				$this->save_graph(strval($rows[0]['id']).'-Keywords',$output);

				$graph_keywords=do_template('STATS_GRAPH',array('GRAPH'=>get_custom_base_url().'/data_custom/modules/admin_stats/'.strval($rows[0]['id']).'-Keywords.xml','TITLE'=>do_lang_tempcode('KEYWORDS_SHARE'),'TEXT'=>do_lang_tempcode('DESCRIPTION_KEYWORDS_SHARE')));
			}
		}

		//************************************************************************************************
		// Regionalities
		//************************************************************************************************
		$regionalities_test=$GLOBALS['SITE_DB']->query_value('ip_country','COUNT(*)');
		if ($regionalities_test>0)
		{
			$start=get_param_integer('start_regionalities',0);
			$max=get_param_integer('max_regionalities',15);
			$sortables=array('ip'=>do_lang_tempcode('REGIONALITY'));
			list($sortable,$sort_order)=explode(' ',get_param('sort_regionalities','ip DESC'));
			if (((strtoupper($sort_order)!='ASC') && (strtoupper($sort_order)!='DESC')) || (!array_key_exists($sortable,$sortables)))
				log_hack_attack_and_exit('ORDERBY_HACK');
			global $NON_CANONICAL_PARAMS;
			$NON_CANONICAL_PARAMS[]='sort_regionalities';

			$where=db_string_equal_to('the_page',$page);
			if (substr($page,0,6)=='pages/') $where.=' OR '.db_string_equal_to('the_page','/'.$page); // Legacy compatibility
			$ip_filter=$GLOBALS['DEV_MODE']?'':(' AND '.db_string_not_equal_to('ip',get_ip_address()));
			$rows=$GLOBALS['SITE_DB']->query('SELECT DISTINCT ip FROM '.$GLOBALS['SITE_DB']->get_table_prefix().'stats WHERE ('.$where.')'.$ip_filter.' ORDER BY '.$sortable.' '.$sort_order,1000/*reasonable limit*/);
			shuffle($rows);
			if (count($rows)<1)
			{
				$list_regionality=new ocp_tempcode();
				$graph_regionality=new ocp_tempcode();
			}
			else
			{
				$regions=array();
				$data=array();
				$degrees=360/count($rows);
				foreach ($rows as $value)
				{
					$region=geolocate_ip($value['ip']);
					if ((is_null($region)) || ($region=='')) $region=do_lang('_UNKNOWN');

					if (!array_key_exists($region,$regions)) $regions[$region]=1;
					else $regions[$region]++;
				}

				require_code('templates_results_table');
				$fields_title=results_field_title(array(do_lang_tempcode('REGIONALITY'),do_lang_tempcode('COUNT_VIEWS')),$sortables,'sort',$sortable.' '.$sort_order);
				$fields=new ocp_tempcode();
				$done_total=0;
				//$done=0;
				$i=0;
				foreach ($regions as $key=>$value)
				{
					if ($i<$start)
					{
						$i++;
						continue;
					} elseif ($i>=$start+$max) break;
					if ($key=='') $link=do_lang('_UNKNOWN'); else $link=escape_html($key);
					$fields->attach(results_entry(array($link,integer_format($value)),true));

					//if ($done<20)
					//{
						$data[$key]=$degrees*$value;
						//$done++;
						$done_total+=$data[$key];
					//}
					$i++;
				}
				if ((360-$done_total)>0)
				{
					$data[do_lang('OTHER')]=360-$done_total;
					$fields->attach(results_entry(array(do_lang('OTHER'),float_format((360-$done_total)/$degrees)),true));
				}
				$list_regionality=results_table(do_lang_tempcode('PAGES_STATISTICS',escape_html($page)),$start,'start_views',$max,'max_views',$i,$fields_title,$fields,$sortables,$sortable,$sort_order,'sort_views');

				$output=create_pie_chart($data);
				$this->save_graph('Regionality',$output);

				$graph_regionality=do_template('STATS_GRAPH',array('GRAPH'=>get_custom_base_url().'/data_custom/modules/admin_stats/'.strval($rows[0]['ip']).'-Regionality.xml','TITLE'=>do_lang_tempcode('REGIONALITY_SHARE'),'TEXT'=>do_lang_tempcode('DESCRIPTION_REGIONALITY_SHARE')));
			}
		}
		else
		{
			// Geo-IP data isn't installed
			$list_regionality=new ocp_tempcode();
			$graph_regionality=new ocp_tempcode();
		}

		//************************************************************************************************
		// Views
		//************************************************************************************************
		$start=get_param_integer('start_views',0);
		$max=get_param_integer('max_views',30);
		$sortables=array('date_and_time'=>do_lang_tempcode('DATE_TIME'));
		$test=explode(' ',get_param('sort_views','date_and_time DESC'));
		if (count($test)==1) $test[1]='DESC';
		list($sortable,$sort_order)=$test;
		if (((strtoupper($sort_order)!='ASC') && (strtoupper($sort_order)!='DESC')) || (!array_key_exists($sortable,$sortables)))
			log_hack_attack_and_exit('ORDERBY_HACK');
		global $NON_CANONICAL_PARAMS;
		$NON_CANONICAL_PARAMS[]='sort_views';

		$where=db_string_equal_to('the_page',$page);
		if (substr($page,0,6)=='pages/') $where.=' OR '.db_string_equal_to('the_page','/'.$page); // Legacy compatibility
		$ip_filter=$GLOBALS['DEV_MODE']?'':(' AND '.db_string_not_equal_to('ip',get_ip_address()));
		$reasonable_max=10000;
		$rows=$GLOBALS['SITE_DB']->query('SELECT date_and_time FROM '.$GLOBALS['SITE_DB']->get_table_prefix().'stats WHERE ('.$where.')'.$ip_filter.' ORDER BY '.$sortable.' '.$sort_order,$reasonable_max/*reasonable limit*/);
		if (count($rows)<1)
		{
			$list_views=new ocp_tempcode();
		}
		else
		{
			require_code('templates_results_table');
			$fields_title=results_field_title(array(do_lang_tempcode('DATE_TIME'),do_lang_tempcode('COUNT_VIEWS')),$sortables,'sort',$sortable.' '.$sort_order);
			$fields=new ocp_tempcode();
			$i=0;
			while (array_key_exists($i,$rows))
			{
				$row=$rows[$i];
				$week=intval(round($row['date_and_time']/(60*60*24*7)));
				$date=get_timezoned_date(($week-1)*(60*60*24*7),false,false,false,true).' - '.get_timezoned_date(($week)*(60*60*24*7),false,false,false,true);
				$views=0;
				while (array_key_exists($i+$views,$rows))
				{
					$_week=intval(round($row['date_and_time']/(60*60*24*7)));

					if ($_week!=$week) break;

					$views++;
				}
				$i+=$views;
				if ($i<$start) continue;
				$fields->attach(results_entry(array($date,($i<$reasonable_max)?integer_format($views):('>'.integer_format($views))),true));
				if ($i>=$start+$max) break;
			}
			$list_views=results_table(do_lang_tempcode('PAGES_STATISTICS',escape_html($page)),$start,'start_views',$max,'max_views',$i,$fields_title,$fields,$sortables,$sortable,$sort_order,'sort_views');
		}

		breadcrumb_set_parents(array(array('_SELF:_SELF:misc',do_lang_tempcode('SITE_STATISTICS'))));

		return do_template('STATS_SCREEN_ISCREEN',array('_GUID'=>'1ac86992de9fc7095883ae23b900d84c','TITLE'=>$title,'GRAPH_REGIONALITY'=>$graph_regionality,'STATS_REGIONALITY'=>$list_regionality,'STATS_VIEWS'=>$list_views,'GRAPH_KEYWORDS'=>$graph_keywords,'STATS_KEYWORDS'=>$list_keywords,'GRAPH_VIEWS_HOURLY'=>$graph_views_hourly,'STATS_VIEWS_HOURLY'=>$list_views_hourly,'GRAPH_VIEWS_DAILY'=>$graph_views_daily,'STATS_VIEWS_DAILY'=>$list_views_daily,'GRAPH_VIEWS_WEEKLY'=>$graph_views_weekly,'STATS_VIEWS_WEEKLY'=>$list_views_weekly,'GRAPH_VIEWS_MONTHLY'=>$graph_views_monthly,'STATS_VIEWS_MONTHLY'=>$list_views_monthly,'GRAPH_IP'=>$graph_ip,'STATS_IP'=>$list_ip,'GRAPH_BROWSER'=>$graph_share_browser,'STATS_BROWSER'=>$list_share_browser,'GRAPH_REFERRER'=>$graph_share_referrer,'STATS_REFERRER'=>$list_share_referrer,'GRAPH_OS'=>$graph_share_os,'STATS_OS'=>$list_share_os));
	}

	/**
	 * The UI to clear statistics.
	 *
	 * @return tempcode		The UI
	 */
	function clear()
	{
		//Someone obviously wants to clear out all their statistics
		//Let's give them the option of only clearing out stored graphs, or deleting everything
		$title=get_screen_title('CLEAR_STATISTICS');

		require_code('form_templates');
		$controls=new ocp_tempcode();

		$controls->attach(form_input_radio_entry('clear','some',true,do_lang_tempcode('DESCRIPTION_CLEAR_GRAPHS')));
		$controls->attach(form_input_radio_entry('clear','all',false,do_lang_tempcode('DESCRIPTION_CLEAR_ALL')));

		$fields=form_input_radio(do_lang_tempcode('CLEAR_STATISTICS'),'','clear',$controls);

		breadcrumb_set_parents(array(array('_SELF:_SELF:misc',do_lang_tempcode('SITE_STATISTICS'))));

		return do_template('FORM_SCREEN',array('_GUID'=>'82f3410d45e4d9ea53b2c033792a3207','SKIP_VALIDATION'=>true,'TITLE'=>$title,'SUBMIT_NAME'=>do_lang_tempcode('CLEAR_STATISTICS'),'TEXT'=>paragraph(do_lang_tempcode('DESCRIPTION_CLEAR_STATISTICS')),'URL'=>build_url(array('page'=>'_SELF','type'=>'_clear'),'_SELF'),'HIDDEN'=>'','FIELDS'=>$fields));
	}

	/**
	 * The actualiser to clear statistics.
	 *
	 * @return tempcode		The UI
	 */
	function _clear()
	{
		$GLOBALS['HELPER_PANEL_PIC']='pagepics/statistics_clear';

		// Let's clear out the saved graphs
		$handle=opendir(get_custom_file_base().'/data_custom/modules/admin_stats/');
		if ($handle!==false)
		{
			while (false!==($file=readdir($handle)))
			{
				if ((!should_ignore_file(get_custom_file_base().'/data_custom/modules/admin_stats/'.$file,IGNORE_ACCESS_CONTROLLERS | IGNORE_HIDDEN_FILES)) && ($file!='IP_Country.txt') && (!is_dir($file)))
				{
					$path=get_custom_file_base().'/data_custom/modules/admin_stats/'.$file;
					@unlink($path)
						OR intelligent_write_error($path);
					sync_file('data_custom/modules/admin_stats/'.$file);
				}
			}

			if (post_param('clear')=='all')
			{
				// Clear ALL of the data stored stats in the db
				$GLOBALS['SITE_DB']->query_delete('hackattack');
				$GLOBALS['SITE_DB']->query_delete('failedlogins');
				$GLOBALS['SITE_DB']->query_delete('stats');
				$GLOBALS['SITE_DB']->query_delete('usersonline_track');
			}
		}

		breadcrumb_set_parents(array(array('_SELF:_SELF:misc',do_lang_tempcode('SITE_STATISTICS')),array('_SELF:_SELF:clear',do_lang_tempcode('CLEAR_STATISTICS'))));
		breadcrumb_set_self(do_lang_tempcode('DONE'));

		return inform_screen(get_screen_title('CLEAR_STATISTICS'),do_lang_tempcode('SUCCESS'));
	}

	/**
	 * Install geolocation data.
	 *
	 * @return tempcode		The UI, showing the result of the installation
	 */
	function install_geolocation_data()
	{
		$title=get_screen_title('INSTALL_GEOLOCATION_DATA');

		$GLOBALS['NO_QUERY_LIMIT']=true;

		$last=104295-1; // Index of the last line in the IP_Country.txt file

		$GLOBALS['HELPER_PANEL_PIC']='pagepics/installgeolocationdata';

		$test=$GLOBALS['SITE_DB']->query_value('ip_country','COUNT(*)');
		if ($test>=$last)
		{
			$url=build_url(array('page'=>'_SELF','type'=>'misc'),'_SELF');
			return redirect_screen($title,$url,do_lang_tempcode('SUCCESS'));
		}

		//We need to read in IP_Country.txt, line-by-line, for x lines
		$lines=get_param_integer('lines',2000);
		$position=get_param_integer('position',0);
		$i=0;

		if ($position==0) $GLOBALS['SITE_DB']->query_delete('ip_country');

		$path=get_file_base().'/data/modules/admin_stats/IP_Country.txt';
		$file=@fopen($path,'rt');
		if ($file===false) warn_exit(do_lang_tempcode('READ_ERROR',escape_html($path)));
		$to_insert=array('begin_num'=>array(),'end_num'=>array(),'country'=>array());
		while ((!feof($file)) && ($i<($position+$lines)))
		{
			$data=fgets($file,1024);
			if ($data===false) continue;
			if ($i>=$position)
			{
				$_data=explode(',',$data);
				if (count($_data)==3)
				{
					$to_insert['begin_num'][]=$_data[0]; // FUDGEFUDGE. Intentionally passes in as strings, to workaround problem in PHP integer sizes (can't store unsigned data type)
					$to_insert['end_num'][]=$_data[1];
					$to_insert['country'][]=substr($_data[2],0,2);

					if (count($to_insert['begin_num'])==100)
					{
						$GLOBALS['SITE_DB']->query_insert('ip_country',$to_insert);
						$to_insert=array('begin_num'=>array(),'end_num'=>array(),'country'=>array());
					}
				}
			}

			$i++;
		}
		fclose($file);
		fix_permissions($path);
		if (count($to_insert['begin_num'])!=0)
		{
			$GLOBALS['SITE_DB']->query_insert('ip_country',$to_insert);
		}

		if ($i>=$last)
		{
			$url=build_url(array('page'=>'_SELF','type'=>'misc'),'_SELF');
			return redirect_screen($title,$url,do_lang_tempcode('SUCCESS'));
		}

		breadcrumb_set_parents(array(array('_SELF:_SELF:misc',do_lang_tempcode('SITE_STATISTICS'))));

		global $FORCE_META_REFRESH;
		$FORCE_META_REFRESH=true;
		require_code('site2');
		assign_refresh(build_url(array('page'=>'_SELF','type'=>'install_data','lines'=>$lines,'position'=>$position+$lines),'adminzone'),($position==0)?1.0:0.0);
		return inform_screen($title,do_lang_tempcode('INSTALLING_GEOLOCATION_DATA'));
	}

	/**
	 * Create a bar chart of the views the specified page has received in relation to the specified hours. The bar chart is stored in /data_custom/admin_stats/ as an SVG image, and the tempcode for display of the graph and results table is returned.
	 *
	 * @param  PATH		The page path
	 * @param  string		The statistic type (for use in sort parameters and such)
	 * @param  string		Language identifier for the graph title
	 * @param  string		Language identifier for the graph description
	 * @param  integer	The steps of hours to use
	 * @param  integer	The total hours to plot
	 * @return array		A linear array containing the graph and list tempcode objects, respectively
	 */
	function views_per_x($page,$type,$graph_title,$graph_description,$hours=1,$total=24)
	{
		//Return a graph with the views per hour for a specified page
		$start=get_param_integer('start_'.$type,0);
		$max=get_param_integer('max_'.$type,50);
		$sortables=array('date_and_time'=>do_lang_tempcode('DATE_TIME'));
		$test=explode(' ',get_param('sort','date_and_time ASC'),2);
		if (count($test)==1) $test[1]='DESC';
		list($sortable,$sort_order)=$test;
		if (((strtoupper($sort_order)!='ASC') && (strtoupper($sort_order)!='DESC')) || (!array_key_exists($sortable,$sortables)))
			log_hack_attack_and_exit('ORDERBY_HACK');
		global $NON_CANONICAL_PARAMS;
		$NON_CANONICAL_PARAMS[]='sort';

		$where=db_string_equal_to('the_page',$page);
		if (substr($page,0,6)=='pages/') $where.=' OR '.db_string_equal_to('the_page','/'.$page); // Legacy compatibility
		$ip_filter=$GLOBALS['DEV_MODE']?'':(' AND '.db_string_not_equal_to('ip',get_ip_address()));
		$query='SELECT id,date_and_time FROM '.get_table_prefix().'stats WHERE ('.$where.')'.$ip_filter.' AND date_and_time>'.strval(time()-($total*60*60)).' AND date_and_time<='.strval(time()).' ORDER BY '.$sortable.' '.$sort_order;
		$rows=$GLOBALS['SITE_DB']->query($query,10000/*reasonable limit*/);
		if ((count($rows)<1) || (count($rows)==10000))
		{
			$list=paragraph(do_lang_tempcode('TOO_MUCH_DATA'),'','red_alert');
			$graph=new ocp_tempcode();
			return array($graph,$list);
		}

		require_code('templates_results_table');
		$fields_title=results_field_title(array(do_lang_tempcode('DATE_TIME'),do_lang_tempcode('COUNT_VIEWS')),$sortables,'sort',$sortable.' '.$sort_order);
		$fields=new ocp_tempcode();
		$data=array();
		$round=60*60*$hours;
		$start_date_and_time=time()+$round-(time()+$round)%$round-$total*60*60;
		$i=0;

		for ($a=0;$a<intval($total/$hours);$a++)
		{
			if ($i<$start)
			{
				$i++;
				continue;
			} elseif ($i>=$start+$max) break;

			$date=get_timezoned_date($start_date_and_time+$a*$hours*60*60,($hours<24));
			$to_date=get_timezoned_date($start_date_and_time+($a+1)*$hours*60*60,($hours<24));
			$data[$date]=0;
			foreach ($rows as $value)
			{
				if (($value['date_and_time']>=$start_date_and_time+$a*$hours*60*60) && ($value['date_and_time']<$start_date_and_time+($a+1)*$hours*60*60)) $data[$date]++;
				elseif ($value['date_and_time']>$start_date_and_time+($a+1)*$hours*60*60) break;
			}

			$i++;
			$fields->attach(results_entry(array($date.' - '.$to_date,integer_format($data[$date])),true));
		}
		$list=results_table(do_lang_tempcode('PAGES_STATISTICS',escape_html($page)),$start,'start_'.$type,$max,'max_'.$type,$i,$fields_title,$fields,$sortables,$sortable,$sort_order,'sort_'.$type);

		$output=create_bar_chart($data,do_lang('DATE_TIME'),do_lang('COUNT_VIEWS'),'','');
		$this->save_graph(strval($rows[0]['id']).'-Views-'.strval((integer)$hours).'_'.strval((integer)$start_date_and_time),$output);

		$graph=do_template('STATS_GRAPH',array('GRAPH'=>get_custom_base_url().'/data_custom/modules/admin_stats/'.strval($rows[0]['id']).'-Views-'.strval((integer)$hours).'_'.strval((integer)$start_date_and_time).'.xml','TITLE'=>do_lang_tempcode($graph_title),'TEXT'=>do_lang_tempcode($graph_description)));

		return array($graph,$list);
	}

	/**
	 * Create a pie chart of the ratios of the specified statistic for the specified page. The chart is saved as an SVG image in /data_custom/admin_stats/, and the tempcode for display of the graph and results table is returned
	 *
	 * @param  PATH		The page path
	 * @param  string		The statistic to use
	 * @param  string		Language identifier for the graph title
	 * @param  string		Language identifier for the graph description
	 * @param  string		Language identifier for the list title
	 * @return array		A linear array containing the graph and list tempcode objects, respectively
	 */
	function page_x_share($page,$type,$graph_title,$graph_description,$list_title)
	{
		//Return a pie chart with the $type used to view this page
		$start=get_param_integer('start_'.$type,0);
		$max=get_param_integer('max_'.$type,25);
		$sortables=array('views'=>do_lang_tempcode('_VIEWS'));
		list($sortable,$sort_order)=explode(' ',get_param('sort','views DESC'),2);
		if (((strtoupper($sort_order)!='ASC') && (strtoupper($sort_order)!='DESC')) || (!array_key_exists($sortable,$sortables)))
			log_hack_attack_and_exit('ORDERBY_HACK');
		global $NON_CANONICAL_PARAMS;
		$NON_CANONICAL_PARAMS[]='sort';

		$where=db_string_equal_to('the_page',$page);
		if (substr($page,0,6)=='pages/') $where.=' OR '.db_string_equal_to('the_page','/'.$page); // Legacy compatibility
		$ip_filter=$GLOBALS['DEV_MODE']?'':(' AND '.db_string_not_equal_to('ip',get_ip_address()));
		$rows=$GLOBALS['SITE_DB']->query('SELECT id,'.$type.' FROM '.get_table_prefix().'stats WHERE ('.$where.')'.$ip_filter,10000/*reasonable limit*/);
		if (count($rows)<1)
		{
			$list=new ocp_tempcode();
			$graph=new ocp_tempcode();
			return array($graph,$list);
		}
		$data1=array();
		$degrees=360/count($rows);
		foreach ($rows as $value)
		{
			if ($type=='browser')
			{
				// Strip point versions in e.g. Chrome
				$value[$type]=preg_replace('#[/ ]\d+\.\d+(\.\d+)+#','',$value[$type]);
				$value[$type]=preg_replace('#[/ ]\d+\.\d\d+#','',$value[$type]);
				$value[$type]=preg_replace('#/\d\d\d\d\d+#','',$value[$type]);
				for ($i=0;$i<10;$i++)
					$value[$type]=preg_replace('#; \.NET .*([;\)])#U','$1',$value[$type]);
				$value[$type]=preg_replace('# \((Windows|Linux|Macintosh).*\)#U','',$value[$type]);
				$value[$type]=preg_replace('#; (Windows|Linux|Macintosh).*\)#U',')',$value[$type]);
			}

			if (!array_key_exists($value[$type],$data1)) $data1[$value[$type]]=0;
			$data1[$value[$type]]++;
		}
		if ($sortable=='views')
		{
			asort($data1);
			if ($sort_order=='DESC') $data1=array_reverse($data1);
		}

		require_code('templates_results_table');
		$fields_title=results_field_title(array(do_lang_tempcode($list_title),do_lang_tempcode('COUNT_VIEWS')),$sortables,'sort',$sortable.' '.$sort_order);
		$fields=new ocp_tempcode();
		$data=array();
		$done_total=0;
		//$done=0;
		$i=0;

		foreach ($data1 as $key=>$value)
		{
			if ($i<$start)
			{
				$i++;
				continue;
			} elseif ($i>=$start+$max) break;
			if ($key=='') $link=do_lang('_UNKNOWN'); else $link=escape_html($key);
			$fields->attach(results_entry(array($link,escape_html(integer_format($value)))));

			//if ($done<20)
			//{
				$data[$key]=$value*$degrees;
				//$done++;
				$done_total+=$value;
			//}

			$i++;
		}
		if (count($rows)>$done_total)
		{
			$data[do_lang('OTHER')]=360.0-$done_total*$degrees;
			$fields->attach(results_entry(array(do_lang('OTHER'),integer_format(count($rows)-$done_total)),true));
		}
		$list=results_table(do_lang_tempcode('PAGES_STATISTICS',escape_html($page)),$start,'start_'.$type,$max,'max_'.$type,$i,$fields_title,$fields,$sortables,$sortable,$sort_order,'sort_'.$type);

		$output=create_pie_chart($data);
		$this->save_graph(strval($rows[0]['id']).'-'.$type,$output);

		$graph=do_template('STATS_GRAPH',array('GRAPH'=>get_custom_base_url().'/data_custom/modules/admin_stats/'.strval($rows[0]['id']).'-'.$type.'.xml','TITLE'=>do_lang_tempcode($graph_title),'TEXT'=>do_lang_tempcode($graph_description)));

		return array($graph,$list);
	}

	/**
	 * Save a graph to the server so it can be viewed client-side.
	 *
	 * @param  string		Name of the graph (no path or extension)
	 * @param  string		SVG markup
	 */
	function save_graph($path,$graph)
	{
		$path=get_custom_file_base().'/data_custom/modules/admin_stats/'.filter_naughty_harsh($path).'.xml';
		$file=@fopen($path,'wt');
		if ($file===false) intelligent_write_error($path);
		if (fwrite($file,$graph)<strlen($graph)) warn_exit(do_lang_tempcode('COULD_NOT_SAVE_FILE'));
		@fclose($file);
		fix_permissions($path);
		sync_file($path);
	}

}

