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

/**
 * Standard code module initialisation function.
 */
function init__themewizard()
{
	global $THEME_WIZARD_IMAGES_CACHE,$THEME_SEED_CACHE,$THEME_DARK_CACHE;
	$THEME_WIZARD_IMAGES_CACHE=array();
	$THEME_SEED_CACHE=array();
	$THEME_DARK_CACHE=array();

	global $THEME_WIZARD_IMAGES,$THEME_WIZARD_IMAGES_NO_WILD;
	$THEME_WIZARD_IMAGES=array();
	$THEME_WIZARD_IMAGES_NO_WILD=array();
	if (function_exists('imagecreatefromgif'))
	{
		$THEME_WIZARD_IMAGES[]='';
	}

	$hooks=find_all_hooks('modules','admin_themewizard');
	foreach (array_keys($hooks) as $hook)
	{
		require_code('hooks/modules/admin_themewizard/'.filter_naughty_harsh($hook));
		$ob=object_factory('Hook_admin_themewizard_'.filter_naughty_harsh($hook),true);
		if (is_null($ob)) continue;
		$results=$ob->run();
		if (is_null($results)) continue;
		list($a,$b)=$results;
		$THEME_WIZARD_IMAGES=array_merge($THEME_WIZARD_IMAGES,$a);
		$THEME_WIZARD_IMAGES_NO_WILD=array_merge($THEME_WIZARD_IMAGES_NO_WILD,$b);
	}
}

/**
 * Given a source theme name, configure the theme wizard for theme generation from it.
 *
 * @param  ID_TEXT			The theme name
 * @param  boolean			Whether we suspect the theme might not be well defined
 */
function load_themewizard_params_from_theme($theme,$guess_images_if_needed=false)
{
	global $THEME_WIZARD_IMAGES_CACHE;
	if (isset($THEME_WIZARD_IMAGES_CACHE[$theme])) return;

	$map=array();
	if ($theme!='default')
	{
		$ini_path=get_custom_file_base().'/themes/'.filter_naughty($theme).'/theme.ini';
		if (file_exists($ini_path))
			$map+=better_parse_ini_file($ini_path);
	}
	$ini_path=get_file_base().'/themes/default/theme.ini';
	$autodetect_background_images=$guess_images_if_needed && (!isset($map['theme_wizard_images']));
	$map+=better_parse_ini_file($ini_path); // NB: Does not take precedence

	if ($autodetect_background_images)
	{
		$dh=opendir(get_file_base().'/themes/'.filter_naughty($theme).(($theme=='default')?'/css/':'/css_custom/'));
		while (($sheet=readdir($dh))!==false)
		{
			if (substr($sheet,-4)=='.css')
			{
				$css_path=get_custom_file_base().'/themes/'.filter_naughty($theme).'/css_custom/'.$sheet;
				if (!file_exists($css_path))
					$css_path=get_custom_file_base().'/themes/default/css_custom/'.$sheet;
				if (!file_exists($css_path))
					$css_path=get_custom_file_base().'/themes/default/css/'.$sheet;
				$css_file=file_get_contents($css_path);
				$matches=array();
				$num_matches=preg_match_all('#\{\$IMG[;\#]?,([\w\_\-\d]+)\}#',$css_file,$matches);
				for ($i=0;$i<$num_matches;$i++)
				{
					if ((preg_match('#'.preg_quote($matches[0][$i]).'[\'"]?\)[^\n]*no-repeat#',$css_file)==0) || (preg_match('#'.preg_quote($matches[0][$i]).'[\'"]?\)[^\n]*width:\s*\d\d\d+px#',$css_file)!=0) || (preg_match('#width:\s*\d\d\d+px;[^\n]*'.preg_quote($matches[0][$i]).'[\'"]?\)#',$css_file)!=0))
						$map['theme_wizard_images'].=','.$matches[1][$i];
				}
			}
		}

		if ($theme!='default')
		{
			$myfile=fopen(get_custom_file_base().'/themes/'.filter_naughty($theme).'/theme.ini','at');
			fwrite($myfile,'theme_wizard_images='.$map['theme_wizard_images']."\n");
			fclose($myfile);
		}
	}

	global $THEME_WIZARD_IMAGES,$THEME_WIZARD_IMAGES_NO_WILD;
	$THEME_WIZARD_IMAGES=explode(',',$map['theme_wizard_images']);
	$THEME_WIZARD_IMAGES_NO_WILD=explode(',',$map['theme_wizard_images_no_wild']);
	if (!function_exists('imagecreatefromgif'))
	{
		global $IMG_CODES;

		$temporary_default=isset($_GET['keep_theme_seed']);
		if ($temporary_default) // To stop an infinite loop, with find_theme_image trying to load up the theme wizard subsystem
		{
			$tseed=$_GET['keep_theme_seed'];
			unset($_GET['keep_theme_seed']);
			$img_codes_bak=$IMG_CODES;
		}
		$new=array();
		foreach ($THEME_WIZARD_IMAGES as $theme_image)
		{
			if (substr(find_theme_image($theme_image,true,false,$theme),-4)!='.gif')
			{
				$new[]=$theme_image;
			}
		}
		$THEME_WIZARD_IMAGES=$new;
		if ($temporary_default)
		{
			$_GET['keep_theme_seed']=$tseed;
			$IMG_CODES=$img_codes_bak;
		}
	}

	$THEME_WIZARD_IMAGES_CACHE[$theme]=$THEME_WIZARD_IMAGES;
}

/**
 * Find the seed of a theme.
 *
 * @param  ID_TEXT			The theme name
 * @param  boolean			Whether we can't assume the theme has any ocPortal default colour information defined, if not in theme.ini
 * @return ID_TEXT			The seed colour
 */
function find_theme_seed($theme,$no_easy_anchor=false)
{
	global $THEME_SEED_CACHE;
	if (isset($THEME_SEED_CACHE[$theme])) return $THEME_SEED_CACHE[$theme];

	$ini_path=(($theme=='default')?get_file_base():get_custom_file_base()).'/themes/'.filter_naughty($theme).'/theme.ini';
	if (is_file($ini_path))
	{
		$map=better_parse_ini_file($ini_path);
	} else
	{
		$map=array();
	}

	if (!array_key_exists('seed',$map))
	{
		$css_path=get_custom_file_base().'/themes/'.$theme.'/css_custom/global.css';
		if (!is_file($css_path))
			$css_path=get_file_base().'/themes/default/css/global.css';
		if (!is_file($css_path)) return '426aa9'; // Not ideal, but default theme is this
		$css_file_contents=file_get_contents($css_path);
		$matches=array();
		if (preg_match('#\{\$THEME\_WIZARD\_COLOR,\#(.{6}),seed,.*\}#',$css_file_contents,$matches)!=0)
		{
			$THEME_SEED_CACHE[$theme]=$matches[1];
		} else
		{
			/*if ($no_easy_anchor)
			{
				Ideally we would put some auto-detection code here
			} else*/
			{
				$THEME_SEED_CACHE[$theme]='426aa9'; // Not ideal, but default theme is this
			}
		}
	} else
	{
		$THEME_SEED_CACHE[$theme]=$map['seed'];
	}

	return $THEME_SEED_CACHE[$theme];
}

/**
 * Find whether a theme is dark.
 *
 * @param  ID_TEXT			The theme name
 * @return boolean			Whether the theme is dark
 */
function find_theme_dark($theme)
{
	global $THEME_DARK_CACHE;
	if (isset($THEME_DARK_CACHE[$theme])) return $THEME_DARK_CACHE[$theme];

	$css_path=get_custom_file_base().'/themes/'.$theme.'/css_custom/global.css';
	if (!is_file($css_path))
		$css_path=get_file_base().'/themes/default/css/global.css';
	if (!is_file($css_path)) return false;
	$css_file_contents=file_get_contents($css_path);
	$matches=array();
	if (preg_match('#\{\$THEME\_WIZARD\_COLOR,\#(.{6}),WB,.*\}#',$css_file_contents,$matches)!=0)
	{
		$THEME_DARK_CACHE[$theme]=(strtoupper($matches[1])!='FFFFFF');
	} else
	{
		$THEME_DARK_CACHE[$theme]=false;
	}

	return $THEME_DARK_CACHE[$theme];
}

/**
 * Called by find_theme_image to allow on-the-fly previewing of what theme wizard output would look like.
 *
 * @param  ID_TEXT			The theme image ID
 * @return ?URLPATH			URL to image (NULL: use standard one, this one is not theme wizard influenced).
 */
function find_theme_image_themewizard_preview($id)
{
	load_themewizard_params_from_theme(get_param('keep_theme_source','default'),get_param('keep_theme_algorithm','equations')=='hsv');

	$seed=get_param('keep_theme_seed');
	if ($seed=='random')
	{
		$_GET['keep_theme_seed']=str_pad(dechex(mt_rand(0,255)),2,'0',STR_PAD_LEFT).str_pad(dechex(mt_rand(0,255)),2,'0',STR_PAD_LEFT).str_pad(dechex(mt_rand(0,255)),2,'0',STR_PAD_LEFT);
		require_lang('themes');
		attach_message(do_lang_tempcode('SEED_IS',escape_html($_GET['keep_theme_seed'])),'inform');
	}

	global $THEME_WIZARD_IMAGES,$THEME_WIZARD_IMAGES_NO_WILD;
	if (!in_array($id,$THEME_WIZARD_IMAGES_NO_WILD))
	{
		foreach ($THEME_WIZARD_IMAGES as $expression)
		{
			if (($expression==$id) || ((substr($expression,-1)=='*') && (substr($id,0,strlen($expression)-1).'*'==$expression)))
			{
				$keep=keep_symbol(array());
				return find_script('themewizard').'?type=image&show='.rawurlencode($id).$keep;
			}
		}
	}

	return NULL;
}

/**
 * Generate a logo from the template.
 *
 * @param  string		The site name.
 * @param  string		The site slogan.
 * @param  boolean	Whether to output the logo to the browser, destroy then image, and exit the script (i.e. never returns)
 * @param  ?string	The theme to use the logo template from (NULL: default root zone theme).
 * @param  string		The logo img file to base upon.
 * @return resource  The image resource.
 */
function generate_logo($name,$slogan,$raw=false,$theme=NULL,$use='logo_template')
{
	require_code('character_sets');

	if (is_null($theme))
	{
		$theme=$GLOBALS['SITE_DB']->query_value('zones','zone_theme',array('zone_name'=>''));
		if (($theme=='') || ($theme=='-1')) $theme='default';
	}

	$logo_wizard_details=array();
	if ($theme!='default')
	{
		$ini_path=get_custom_file_base().'/themes/'.filter_naughty($theme).'/theme.ini';
		if (file_exists($ini_path))
			$logo_wizard_details+=better_parse_ini_file($ini_path);
	}
	$ini_path=get_file_base().'/themes/default/theme.ini';
	$logo_wizard_details+=better_parse_ini_file($ini_path);

	$logo_url=$GLOBALS['SITE_DB']->query_value_null_ok('theme_images','path',array('theme'=>$theme,'id'=>$use));
	if (!is_null($logo_url)) $file_path_stub=convert_url_to_path($logo_url);
	if ((is_null($logo_url)) || (!file_exists($file_path_stub)))
	{
		$logo_url=find_theme_image($use,false,false,$theme);
	} else
	{
		if (url_is_local($logo_url)) $logo_url=((strpos($logo_url,'themes/default/')!==false)?get_base_url():get_custom_base_url()).'/'.$logo_url;
	}
	$file_path_stub=convert_url_to_path($logo_url);
	if (!is_null($file_path_stub))
	{
		if (!file_exists($file_path_stub)) $file_path_stub=get_file_base().'/themes/default/images/trimmed_logo_template.png'; // Exceptional situation. Maybe theme got corrupted?
		$data=file_get_contents($file_path_stub);
	} else
	{
		$data=http_download_file($logo_url);
	}
	$img=imagecreatefromstring($data);
	if ($img===false)
	{
		warn_exit(do_lang_tempcode('CORRUPT_FILE',escape_html($logo_url)));
	}

	$ttf_font=get_file_base().'/data/fonts/Vera.ttf';

	if ((!function_exists('imagettftext')) || (!array_key_exists('FreeType Support',gd_info())) || (!file_exists($ttf_font)) || (@imagettfbbox(26.0,0.0,get_file_base().'/data/fonts/Vera.ttf','test')===false))
	{
		$font=intval($logo_wizard_details['site_name_font_size_small_non_ttf']);
		$font_width=imagefontwidth($font)*strlen($name);
		$font_height=imagefontheight($font);
	} else
	{
		list(,,$font_width,,,,,$font_height)=imagettfbbox(26.0,0.0,$ttf_font,foxy_utf8_to_nce($name));
		$font_height=max($font_height,-$font_height);
	}
	$white=imagecolorallocate($img,hexdec(substr($logo_wizard_details['site_name_colour'],0,2)),hexdec(substr($logo_wizard_details['site_name_colour'],2,2)),hexdec(substr($logo_wizard_details['site_name_colour'],4,2)));
	$black=imagecolorallocate($img,0,0,0);
	$matches=array();
	if (file_exists(get_custom_file_base().'/themes/'.$theme.'/css_custom/global.css'))
	{
		$css_file=file_get_contents(get_custom_file_base().'/themes/'.$theme.'/css_custom/global.css');
	} else
	{
		$css_file=file_get_contents(get_file_base().'/themes/default/css/global.css');
	}
	if (preg_match('/#\{\$THEME\_WIZARD\_COLOR,\#(\w\w)(\w\w)(\w\w),standard_border,.*\}/',$css_file,$matches)!=0)
	{
		$blue=imagecolorallocate($img,hexdec($matches[1]),hexdec($matches[2]),hexdec($matches[3]));
	} else
	{
		$blue=imagecolorallocate($img,hexdec(substr($logo_wizard_details['slogan_colour'],0,2)),hexdec(substr($logo_wizard_details['slogan_colour'],2,2)),hexdec(substr($logo_wizard_details['slogan_colour'],4,2)));
	}
	$do=array(array($slogan,intval($logo_wizard_details['slogan_x_offset']),intval($logo_wizard_details['slogan_y_offset'])/*73 on some servers?? Weird*/,intval($logo_wizard_details['slogan_font_size']),$ttf_font,$white));
	if (($font_width>intval($logo_wizard_details['site_name_split'])) && (strpos($name,' ')!==false)) // Split in two
	{
		if ((function_exists('imagettftext')) && (array_key_exists('FreeType Support',gd_info())) && (file_exists($ttf_font)) && (@imagettfbbox(26.0,0.0,get_file_base().'/data/fonts/Vera.ttf','test')!==false))
		{
			list(,,$font_width,,,,,$font_height)=imagettfbbox(floatval($logo_wizard_details['site_name_font_size_small']),0.0,$ttf_font,foxy_utf8_to_nce($name));
			$font_height=max($font_height,-$font_height);
		}
		$bits=explode(' ',$name);
		$a='';
		$b='';
		foreach ($bits as $bit)
		{
			if (strlen($a)<intval(round(floatval(strlen($name))/2.0))) $a.=$bit.' '; else $b.=$bit.' ';
		}
		$do[]=array($a,intval($logo_wizard_details['site_name_x_offset']),intval($logo_wizard_details['site_name_y_offset_small'])+$font_height,intval($logo_wizard_details['site_name_font_size_small']),$ttf_font,$blue);
		$do[]=array($b,intval($logo_wizard_details['site_name_x_offset']),intval($logo_wizard_details['site_name_y_offset_small'])+$font_height*2+intval($logo_wizard_details['site_name_split_gap']),intval($logo_wizard_details['site_name_font_size_small']),$ttf_font,$blue);
	} elseif ($font_width>intval($logo_wizard_details['site_name_split'])) // Smaller font
	{
		if ((function_exists('imagettftext')) && (array_key_exists('FreeType Support',gd_info())) && (file_exists($ttf_font)) && (@imagettfbbox(26.0,0.0,get_file_base().'/data/fonts/Vera.ttf','test')!==false))
		{
			list(,,$font_width,,,,,$font_height)=imagettfbbox(floatval($logo_wizard_details['site_name_font_size_small']),0.0,$ttf_font,foxy_utf8_to_nce($name));
			$font_height=max($font_height,-$font_height);
		}
		$do[]=array($name,intval($logo_wizard_details['site_name_x_offset']),intval($logo_wizard_details['site_name_y_offset'])+$font_height,intval($logo_wizard_details['site_name_font_size_small']),$ttf_font,$blue);
	} else // Show normally
	{
		$do[]=array($name,intval($logo_wizard_details['site_name_x_offset']),intval($logo_wizard_details['site_name_y_offset'])+$font_height,floatval($logo_wizard_details['site_name_font_size']),$ttf_font,$blue);
	}
	foreach ($do as $i=>$doing)
	{
		//if ($i==1) continue;
		if ((function_exists('imagettftext')) && (array_key_exists('FreeType Support',gd_info())) && (file_exists($doing[4])) && (@imagettfbbox(26.0,0.0,get_file_base().'/data/fonts/Vera.ttf','test')!==false))
		{
			imagettftext($img,(float)($doing[3]),0.0,$doing[1],$doing[2],$doing[5],$doing[4],foxy_utf8_to_nce($doing[0]));
		} else
		{
			imagestring($img,($doing[3]==intval($logo_wizard_details['site_name_font_size_small']))?intval($logo_wizard_details['site_name_font_size_nonttf']):$font,$doing[1],$doing[2]-11,$doing[0],$doing[5]);
		}
	}

	if ($raw)
	{
		header('Content-type: image/png');
		/*if (strstr(ocp_srv('HTTP_USER_AGENT'),'MSIE')!==false)
			header('Content-Disposition: filename="-logo.png"');
		else
			header('Content-Disposition: attachment; filename="-logo.png"');*/
		imagepng($img);
		imagedestroy($img);
		exit();
	}

	return $img;
}

/**
 * Make a theme. Note that this will trigger the AFM.
 *
 * @param  string		Name of the theme.
 * @param  ID_TEXT	The theme it's being generated from
 * @param  ID_TEXT	The algorithm to use
 * @set equations hsv
 * @param  string		Seed colour to use.
 * @param  boolean	Whether to use the theme immediately.
 * @param  ?boolean  Whether it will be a dark theme (NULL: autodetect).
 * @param  boolean	Whether to inherit the CSS, for easier theme upgrading.
 */
function make_theme($themename,$source_theme,$algorithm,$seed,$use,$dark=false,$inherit_css=false)
{
	$GLOBALS['NO_QUERY_LIMIT']=true;

	load_themewizard_params_from_theme($source_theme,$algorithm=='hsv');

	if (file_exists(get_custom_file_base().'/themes/'.$themename))
	{
		require_code('abstract_file_manager');
		force_have_afm_details();
		$extending_existing=true;
	} else
	{
		if ($source_theme=='default')
		{
			actual_add_theme($themename);
		} else
		{
			require_code('themes3');
			actual_copy_theme($source_theme,$themename);
		}
		$extending_existing=false;
	}

	if (($seed!=find_theme_seed($source_theme)) || ($dark!=find_theme_dark($source_theme)))
	{
		list($colours,$landscape)=calculate_theme($seed,$source_theme,$algorithm,'colours',$dark);

		// Make images
		global $THEME_WIZARD_IMAGES,$THEME_WIZARD_IMAGES_NO_WILD,$IMG_CODES;
		if (function_exists('imagecolorallocatealpha'))
		{
			require_code('themes2');
			$full_img_set=array();
			foreach ($THEME_WIZARD_IMAGES as $expression)
			{
				if (substr($expression,-1)=='*')
				{
					$expression=substr($expression,0,strlen($expression)-2); // remove "/*"
					$full_img_set=array_merge($full_img_set,array_keys(get_all_image_codes(get_file_base().'/'.filter_naughty($source_theme).'/default/images',$expression)));
					$full_img_set=array_merge($full_img_set,array_keys(get_all_image_codes(get_file_base().'/themes/'.filter_naughty($source_theme).'/images/'.fallback_lang(),$expression)));
				} else
				{
					$full_img_set[]=$expression;
				}
			}

			if ($extending_existing)
			{
				$temp_all_ids=collapse_2d_complexity('id','path',$GLOBALS['SITE_DB']->query_select('theme_images',array('id','path'),array('theme'=>$themename)));
			} else $temp_all_ids=array();

			$_langs=find_all_langs(true);

			foreach ($full_img_set as $image_code)
			{
				if (!in_array($image_code,$THEME_WIZARD_IMAGES_NO_WILD))
				{
					if (($extending_existing) && (array_key_exists($image_code,$temp_all_ids)) && (strpos($temp_all_ids[$image_code],$themename.'/images_custom/')!==false) && ((!url_is_local($temp_all_ids[$image_code])) || (file_exists(get_custom_file_base().'/'.$temp_all_ids[$image_code])))) continue;

					foreach (array_keys($_langs) as $lang)
					{
						$orig_path=find_theme_image($image_code,true,true,$source_theme,$lang);
						if ($orig_path=='') continue; // Theme has specified non-existent image as themewizard-compatible

						if ((strpos($orig_path,'/'.$lang.'/')===false) && ($lang!=fallback_lang())) continue;

						if (strpos($orig_path,'/'.fallback_lang().'/')!==false)
						{
							$composite='themes/'.filter_naughty($themename).'/images/'.$lang.'/';
						} else
						{
							$composite='themes/'.filter_naughty($themename).'/images/';
						}
						$saveat=get_custom_file_base().'/'.$composite.$image_code.'.png';
						$saveat_url=$composite.$image_code.'.png';

						// Wipe out ones that might have been copied from source theme
						if (($source_theme!='default') && (strpos($orig_path,'images_custom')!==false))
						{
							@unlink(str_replace('/images/','/images_custom/',basename($saveat,'.png')).'.png');
							@unlink(str_replace('/images/','/images_custom/',basename($saveat,'.png')).'.jpg');
							@unlink(str_replace('/images/','/images_custom/',basename($saveat,'.png')).'.gif');
							@unlink(str_replace('/images/','/images_custom/',basename($saveat,'.png')).'.jpeg');
						}

						if ((!file_exists($saveat)) || ($source_theme!='default') || ($algorithm=='hsv'))
						{
							$image=calculate_theme($seed,$source_theme,$algorithm,$image_code,$dark,$colours,$landscape,$lang);
							if (!is_null($image))
							{
								$pos=strpos($image_code,'/');
								if (($pos!==false) || (strpos($orig_path,'/'.fallback_lang().'/')!==false))
								{
									afm_make_directory($composite.substr($image_code,0,$pos),true,true);
								}
								@imagepng($image,$saveat) OR intelligent_write_error($saveat);
								imagedestroy($image);
								fix_permissions($saveat);
								sync_file($saveat);
								actual_edit_theme_image($image_code,$themename,$lang,$image_code,$saveat_url,true);
								//if ($lang==fallback_lang()) $IMG_CODES['site'][$image_code]=$saveat_url;
							}
						} else // Still need to do the edit, as currently it'll have been mapped to the default theme when this theme was added
						{
							actual_edit_theme_image($image_code,$themename,$lang,$image_code,$saveat_url,true);
						}
					}
				}
			}
		}

		// Make sheets
		$dh=opendir(get_file_base().'/themes/'.filter_naughty($source_theme).(($source_theme=='default')?'/css/':'/css_custom/'));
		while (($sheet=readdir($dh))!==false)
		{
			if (substr($sheet,-4)=='.css')
			{
				$saveat=get_custom_file_base().'/themes/'.filter_naughty($themename).'/css_custom/'.$sheet;
				if ((!file_exists($saveat)) || ($source_theme!='default') || ($algorithm=='hsv'))
				{
					if ($inherit_css)
					{
						$output='{+START,CSS_INHERIT,'.basename($sheet,'.css').','.filter_naughty($source_theme).','.$seed.','.($dark?'1':'0').','.$algorithm.'}{+END}';
					} else
					{
						$output=theme_wizard_colours_to_sheet($sheet,$landscape,$source_theme,$algorithm,$seed);
					}
					$default_version_path=get_file_base().'/themes/default/css/'.$sheet;
					if (is_file($default_version_path))
					{
						$default_version=file_get_contents($default_version_path);
						$changed_from_default_theme=file_get_contents(unixify_line_format($default_version_path))!=$output;
					} else
					{
						$changed_from_default_theme=true;
					}
					if ($changed_from_default_theme)
					{
						$fp=@fopen($saveat,'wt') OR intelligent_write_error(get_custom_file_base().'/themes/'.filter_naughty($themename).'/css_custom/'.$sheet);
						if (fwrite($fp,$output)<strlen($output)) warn_exit(do_lang_tempcode('COULD_NOT_SAVE_FILE'));
						fclose($fp);
						fix_permissions($saveat);
						sync_file($saveat);
						if (!$inherit_css)
						{
							$c_success=@copy(get_file_base().'/themes/'.filter_naughty($source_theme).'/css/'.$sheet,$saveat.'.editfrom');
							if ($c_success!==false)
							{
								fix_permissions($saveat.'.editfrom');
								sync_file($saveat.'.editfrom');
							}
						} else
						{
							@unlink($saveat.'.editfrom');
						}
					}
				}
			}
		}
	}

	// Use it, if requested
	if ($use)
	{
		$GLOBALS['SITE_DB']->query('UPDATE '.get_table_prefix().'zones SET zone_theme=\''.db_escape_string($themename).'\' WHERE '.db_string_not_equal_to('zone_name','cms').' AND '.db_string_not_equal_to('zone_name','adminzone'));

		$admin_groups=$GLOBALS['FORUM_DRIVER']->get_super_admin_groups();
		$groups=$GLOBALS['FORUM_DRIVER']->get_usergroup_list(false,true);
		$GLOBALS['SITE_DB']->query_delete('group_category_access',array('module_the_name'=>'theme','category_name'=>$themename));
		foreach (array_keys($groups) as $group_id)
		{
			if (in_array($group_id,$admin_groups)) continue;

			$GLOBALS['SITE_DB']->query_insert('group_category_access',array('module_the_name'=>'theme','category_name'=>$themename,'group_id'=>$group_id));
		}

		persistent_cache_empty();
	}
}

/**
 * Output a theme component straight to the browser.
 */
function themewizard_script()
{
	$type=get_param('type');
	$source_theme=get_param('keep_theme_source','default');
	$algorithm=get_param('keep_theme_algorithm','equations');
	$show=get_param('show');
	$seed=get_param('keep_theme_seed');
	if ($seed=='kiddie')
	{
		$seed=str_pad(dechex(mt_rand(0,255)),2,'0',STR_PAD_LEFT).str_pad(dechex(mt_rand(0,255)),2,'0',STR_PAD_LEFT).str_pad(dechex(mt_rand(0,255)),2,'0',STR_PAD_LEFT);
	}
	$_dark=get_param_integer('keep_theme_dark',NULL);
	$dark=is_null($_dark)?NULL:($_dark==1);
	if ($type=='preview')
	{
		$_tpl=do_template('THEMEWIZARD_2_PREVIEW');
		$tpl=do_template('STANDALONE_HTML_WRAP',array('TITLE'=>do_lang_tempcode('PREVIEW'),'CONTENT'=>$_tpl));
		$tpl->evaluate_echo();
	}
	if ($type=='css')
	{
		@ini_set('ocproducts.xss_detect','0');
		list($colours,$landscape)=calculate_theme($seed,$source_theme,$algorithm,'colours',$dark);
		$css=theme_wizard_colours_to_sheet($show,$landscape,$source_theme,$algorithm,$seed);
		header('Content-type: text/css');
		require_code('tempcode_compiler');
		$tpl=template_to_tempcode($css);
		$tpl->evaluate_echo();
	}
	if ($type=='image')
	{
		$image=calculate_theme($seed,$source_theme,$algorithm,$show,$dark);
		if (is_null($image))
		{
			header('Location: '.find_theme_image($show));
			exit();
		}
		header('Content-type: image/png');
		imagepng($image);
		imagedestroy($image);
	}
}

/**
 * Calculate some component relating to a theme from a colour seed.
 *
 * @param  string				Colour seed.
 * @param  ID_TEXT			The theme it's being generated from
 * @param  ID_TEXT			The algorithm to use
 * @set equations hsv
 * @param  ID_TEXT			What to generate ('colours', or the name of a theme image).
 * @param  ?boolean			Whether it will be a dark theme (NULL: autodetect).
 * @param  ?array				The colour map to use (NULL: compute).
 * @param  ?array				The computed colour landscape to use (NULL: compute).
 * @param  ?LANGUAGE_NAME	The language to work in (NULL: default).
 * @return mixed				Image resource OR A pair: extended map of colours, colour expression landscape
 */
function calculate_theme($seed,$source_theme,$algorithm,$show='colours',$dark=NULL,$colours=NULL,$landscape=NULL,$lang=NULL)
{
	if ($seed[0]=='#') $seed=substr($seed,1);

	$white=255;
	$black=0;

	// Strip and decimalize the three colors
	$red=hexdec(substr($seed,0,2));
	$green=hexdec(substr($seed,2,2));
	$blue=hexdec(substr($seed,4,2));

	// Decide the dominant color

	if (($red==$blue) && ($red!=$green)) $dominant='purple';
	if (($red==$green) && ($red!=$blue)) $dominant='yellow';
	if (($blue==$green) && ($blue!=$red)) $dominant='cyan';

	if (($red==$green) && ($red==$blue))
	{
		if ($red>=185) $dominant='white';
		if (($red<185) && ($red>=100)) $dominant='gray';
		if ($red<100) $dominant='black';
	}

	if (($red>$green) && ($red>$blue)) $dominant='red';
	if (($green>$red) && ($green>$blue)) $dominant='green';
	if (($blue>$red) && ($blue>$green)) $dominant='blue';

	// Decide if this is a "light" skin or a "dark" theme
	if (((intval(round(floatval($red+$green+$blue)/3.0))>=127) || ($dark===false)) && ($dark!==true))
	{
		$light_dark='light';
		$anti_light_dark='dark';
		$wb='FFFFFF';
		$awb='000000';
	} else
	{
		$light_dark='dark';
		$anti_light_dark='light';
		$wb='000000';
		$awb='FFFFFF';
	}

	if ((is_null($landscape)) || (is_null($colours)))
	{
		$colours=array(
			// Hints for computation
			'dark'=>($light_dark=='dark')?'1':'0',
			'red'=>strval($red),
			'green'=>strval($green),
			'blue'=>strval($blue),
			'dominant'=>$dominant,
			'LD'=>$light_dark,
			'DL'=>$anti_light_dark,

			// Actual colours
			'seed'=>$seed,
			'WB'=>$wb,
			'BW'=>$awb,
		);
		if ($algorithm=='equations')
		{
			list($colours,$landscape)=calculate_dynamic_css_colours($colours,$source_theme);
		} else
		{
			$landscape=array();
		}
	}

	if ($show!='colours')
	{
		unset($_GET['keep_theme_seed']);
		$ti=find_theme_image($show,false,true,$source_theme,$lang);
		if ($ti=='') return NULL;
		$path=get_file_base().'/'.$ti;
		if (!file_exists($path)) // File since deleted, we'll revert
		{
			$ti=find_theme_image($show,false,true,$source_theme,$lang,NULL,true);
			if ($ti=='') return NULL;
			$path=get_file_base().'/'.$ti;
		}

		$img=NULL;
		if (function_exists('imagecolorallocatealpha'))
		{
			if ($algorithm=='hsv')
			{
				$img=re_hue_image($path,$seed,$source_theme,true);
			} else
			{
				if ($source_theme=='default')
				{
					$needed=array('washed_out','area_background','lgrad','dgrad','dark_border','comcode_quote_left','comcode_quote_right','a.link','a.hover','a.link__dark','a.hover__dark','special_borderer','navigation_top','navigation_bot','navigation_peak','ocfredirectindicator','ocfpostindicator','header_background','slightly_seeded_text','special_middle',);
					foreach ($needed as $colour_needed)
					{
						if (!array_key_exists($colour_needed,$colours))
							fatal_exit(do_lang_tempcode('UNRESOLVABLE_COLOURS',escape_html($colour_needed)));
					}

					if ($show=='gradient')
					{
						$img=generate_gradient($colours['lgrad'],$colours['dgrad']);
					}
					elseif (($show=='background_image'))
					{
						$img=generate_recoloured_image($path,'#FFFFFF',$colours['WB'],'#DDE5F7',$colours['washed_out']);
					}
					elseif (($show=='header') || ($show=='outer_background') || ($show=='inner_background') || ($show=='block_background') || ($show=='big_tabs_controller_button_active') || ($show=='big_tabs_controller_button_top_active') || ($show=='big_tabs_controller_button_top') || ($show=='big_tabs_controller_button'))
					{
						$img=re_hue_image($path,$seed,$source_theme,false,$light_dark=='dark');
					}
					elseif ($show=='quote_gradient')
					{
						$img=generate_recoloured_image($path,'#072A66',$colours['dark_border'],'#C7D5EC',$colours['comcode_quote_left'],'#8CA7D2',$colours['comcode_quote_right'],'horizontal');
					}
					elseif ($show=='menu_bullet')
					{
						$img=generate_recoloured_image($path,'#000000',$colours['a.link'],'#190406',$colours['a.link']);
					}
					elseif ($show=='menu_bullet_current')
					{
						$img=generate_recoloured_image($path,'#00A55A',$colours['a.hover'],'#00A55A',$colours['a.hover']);
					}
					elseif ($show=='menu_bullet_hover')
					{
						$img=generate_recoloured_image($path,'#9C202F',$colours['a.hover'],'#BA1621',$colours['a.hover']);
					}
					elseif (substr($show,0,10)=='checklist/')
					{
						$img=generate_recoloured_image($path,'#335082',$colours['special_borderer'],'#091C3D',$colours['special_middle']);
					}
					elseif ($show=='arrow_box')
					{
						$img=generate_recoloured_image($path,'#12467A',$colours['a.link'],'#0A223D',$colours['a.link__dark']);
					}
					elseif ($show=='arrow_box_hover')
					{
						$img=generate_recoloured_image($path,'#12467A',$colours['a.hover'],'#0A223D',$colours['a.hover__dark']);
					}
					elseif ($show=='help_panel_hide')
					{
						$img=generate_recoloured_image($path,'#82A3D8',$colours['navigation_top'],'#82A3D8',$colours['navigation_top']);
					}
					elseif ($show=='zone_gradient')
					{
						$img=generate_recoloured_image($path,'#FFFFFF'/*3660A6*/,'#FFFFFF'/*$colours['navigation_peak']*/,'#82A3D8',$colours['navigation_top'],'#3865A9',$colours['navigation_bot']);
					}
					elseif (($show=='trimmed_logo_template') || ($show=='logo_template') || (substr($show,0,5)=='logo/'))
					{
						$pixel_x_start_array=array();
						for ($y=0;$y<=59+((strpos($show,'trimmed')===false)?0:13);$y++)
						{
							$pixel_x_start_array[]=324; // i.e. the line is skipped (324 is the far right)
						}
						if (strpos($show,'trimmed')===false)
						{
							$pixel_x_start_array=array_merge($pixel_x_start_array,array(320,319,318,317,316,316,315,315,314,314,313,312,309));
						}
						$img=generate_recoloured_image($path,'#FFFFFF'/*3660A6*/,'#FFFFFF'/*$colours['navigation_peak']*/,'#82A3D8',$colours['navigation_top'],'#3865A9',$colours['navigation_bot'],'vertical',$pixel_x_start_array,60);
						for ($y=0;$y<=88;$y++)
						{
							$pixel_x_start_array[$y]=(array_key_exists($y,$pixel_x_start_array)?$pixel_x_start_array[$y]:0);
						}
						$img=generate_recoloured_image(isset($img)?$img:$path,'#8FADDC',$colours['navigation_peak'],'#FFFFFF',$colours['WB'],'#E3EAF2',$colours['header_background'],'horizontal',$pixel_x_start_array,0,true);
					}
					elseif (in_array($show,array('ocf_general/no_new_posts_redirect','ocf_general/new_posts_redirect')))
					{
						$img=generate_recoloured_image($path,'#FFFFFF','#FFFFFF','#549B8C',$colours['ocfredirectindicator']);
					}
					elseif (in_array($show,array('ocf_general/redirect','ocf_general/redirect','ocf_general/no_new_posts_redirect','ocf_general/new_posts_redirect','ocf_general/no_new_posts','ocf_general/new_posts')))
					{
						$img=generate_recoloured_image($path,'#FFFFFF','#FFFFFF','#5A84C4',$colours['ocfpostindicator']);
					} else // These are less special... we just change the hue
					{
						$img=re_hue_image($path,$seed,$source_theme);
					}
				} else
				{
					$img=re_hue_image($path,$seed,$source_theme);
				}
			}
		}

		return $img;
	}

	return array($colours,$landscape);
}

/**
 * Augment an array of CSS colours with colours that are derived actually inside the CSS-sheets.
 *
 * @param  array		Map of colours.
 * @param  ID_TEXT	The theme it's being generated from
 * @return array		A pair: extended map of colours, colour expression landscape
 */
function calculate_dynamic_css_colours($colours,$source_theme)
{
	$theme=filter_naughty($source_theme);
	$css_dir=(($theme=='default')?'css':'css_custom');
	$dh=opendir(get_file_base().'/themes/'.$theme.'/'.$css_dir.'/');

	require_lang('themes');

	// First we build up our landscape
	$landscape=array();
	while (($sheet=readdir($dh))!==false)
	{
		if (substr($sheet,-4)=='.css')
		{
			$path=get_file_base().'/themes/'.$theme.'/'.$css_dir.'/'.$sheet;
			$contents=unixify_line_format(file_get_contents($path));

			$matches=array();
			$num_matches=preg_match_all('#\{\$THEME_WIZARD_COLOR,(.*),(.*),(.*)\}#',$contents,$matches);

			for ($i=0;$i<$num_matches;$i++)
			{
				// Skip over our little stored hints (not intended for ocPortal)
				if (in_array($matches[2][$i],array('seed','WB','BW'))) continue;

				// A one we're really interested in
				$parsed=parse_css_colour_expression($matches[3][$i]);
				if (!is_null($parsed))
					//							Colour name			Parsed expression		Full match string	 		  Final colour
					$landscape[]=array(	$matches[2][$i],	$parsed,					$matches[0][$i],			  NULL);
			}
		}
	}

	// Then we resolve our expressions
	$resolved_landscaped=array();
	$safety_count=0;
	while (count($landscape)!=0)
	{
		foreach ($landscape as $i=>$peak)
		{
			$peak[3]=execute_css_colour_expression($peak[1],$colours);
			if (!is_null($peak[3])) // We were able to get a result
			{
				$resolved_landscaped[]=$peak;
				unset($landscape[$i]);

				// Then we add to the colours array
				if ($peak[0]!='wizard') // 'wizard' is a generic name, so we ignore it
				{
					$colours[$peak[0]]=$peak[3];
				}
			}
		}
		$safety_count++;
		if ($safety_count==100)
		{
			$_landscape='';
			foreach ($landscape as $x)
			{
				if ($_landscape!='') $_landscape.='; ';
				$_landscape.=$x[2];
			}
			fatal_exit(do_lang_tempcode('UNRESOLVABLE_COLOURS',escape_html($_landscape)));
		}
	}

	return array($colours,$resolved_landscaped);
}

/**
 * Convert a textual CSS colour expression into an expression tree.
 *
 * @param  string	Textual expression.
 * @return ?array	Expression tree (NULL: not real).
 */
function parse_css_colour_expression($textual)
{
	// '*' is inserted after a %, and then % is dropped
	$textual=preg_replace('#(^| )(\d+)%#','\\1\\2 *',$textual);

	// We're using spaces as token delimiters, so we need to do a trim to clean up, and also put spaces around brackets
	$textual=trim(str_replace(')',' )',str_replace('(','( ',$textual)));

	// Perform inner conversion
	$tokens=explode(' ',$textual);
	//if (count($tokens)<5) return NULL;

	$expression=_parse_css_colour_expression($tokens);
	return $expression;
}

/**
 * Convert CSS colour tokens into an expression tree.
 *
 * @param  array	Tokens.
 * @return ?array	Expression tree (NULL: error).
 */
function _parse_css_colour_expression($tokens)
{
	// We now scan through, structuring into an evaluation-order tree (but not an expression tree  at the level we're operating on)
	// Brackets
	$new_tokens=array();
	for ($i=0;$i<count($tokens);$i++)
	{
		if ($tokens[$i]==='(')
		{
			// Find matching closing token
			$extra_opened=0;
			$sub_tokens=array();
			for ($i=$i+1;$i<count($tokens);$i++)
			{
				if ($tokens[$i]=='(')
				{
					$extra_opened++;
				}
				elseif (($tokens[$i]==')') && ($extra_opened>0))
				{
					$extra_opened--;
				}
				elseif ($tokens[$i]==')')
				{
					$new_tokens[]=_parse_css_colour_expression($sub_tokens);
					break;
				}
				$sub_tokens[]=$tokens[$i];
			}
		} else $new_tokens[]=$tokens[$i];
	}
	$tokens=$new_tokens;
	// Additions. Each addition is a pivot.
	for ($i=0;$i<count($tokens);$i++)
	{
		if ($tokens[$i]==='+')
		{
			return array('+',_parse_css_colour_expression(array_slice($tokens,0,$i)),_parse_css_colour_expression(array_slice($tokens,$i+1)));
		}
	}

	// Either we have a sigle token
	if (count($tokens)==1)
	{
		return $tokens[0];
	}

	// Or we have a length of more than 3 tokens, in which case we pivot
	if (count($tokens)>3)
	{
		return array($tokens[1],$tokens[0],_parse_css_colour_expression(array_slice($tokens,2)));
	}

	// Or we have just 3 tokens, a single operation
	if (!array_key_exists(2,$tokens)) return NULL;
	return array($tokens[1],$tokens[0],$tokens[2]);
}

/**
 * Execute CSS colour expression.
 *
 * @param  mixed		Expression tree (array) OR leaf (string).
 * @param  array		Known colours at this point.
 * @return ?string	RRGGBB colour or possibly just a number (NULL: answer cannot be computed).
 */
function execute_css_colour_expression($expression,$colours)
{
	if (!is_array($expression))
	{
		if (preg_match('#^[0-9A-Fa-f]{6}$#',$expression)!=0) return $expression;
		if (preg_match('#^\#[0-9A-Fa-f]{6}$#',$expression)!=0) return substr($expression,1);
		if (preg_match('#^[\+\-]?\d+$#',$expression)!=0) return $expression;

		foreach ($colours as $colour=>$actual_colour)
		{
			if ($colour==$expression) return $actual_colour;
		}

		return NULL; // Couldn't find it - we'll have to instruct it to come back to it
	}

	$operation=$expression[0];
	$operand_a=execute_css_colour_expression($expression[1],$colours);
	if (is_null($operand_a)) return NULL;
	/*if (($operation=='+') && ($expression[2][0]=='*') && (!is_array($expression[2][1])) && ((($expression[2][2]=='WB') && ($colours['LD']=='light')) || (($expression[2][2]=='BW') && ($colours['LD']=='dark'))))
	{
		$operation='-';
		$expression[2][2]='FFFFFF';
	} else
	{
//	if (($operation=='-') && ($expression[2][0]=='*') && (!is_array($expression[2][1])) && ((($expression[2][2]=='WB') && ($colours['LD']=='light')) || (($expression[2][2]=='BW') && ($colours['LD']=='dark'))))
//		$operation='+';
	}*/
	$operand_b=execute_css_colour_expression($expression[2],$colours);
	if (is_null($operand_b)) return NULL;

	if ($operation[0]=='&')
	{
		$operand_c=str_replace('%','',substr($operation,1));
		$operation='&';
	}

	switch ($operation)
	{
		/* These are percentage modifiers */

		case 'sat_to':
			list($h,$s,$v)=rgb_to_hsv($operand_a);
			$result=($s==0)?hsv_to_rgb(floatval($h),floatval($s),floatval(fix_colour(255*intval($operand_b)/(100.0)))):hsv_to_rgb(floatval($h),floatval(fix_colour(255*intval($operand_b)/(100.0))),floatval($v));
			break;

		case 'sat_add':
			list($h,$s,$v)=rgb_to_hsv($operand_a);
			$result=hsv_to_rgb(floatval($h),floatval(fix_colour($s+intval($operand_b))),floatval($v));
			break;

		case 'sat':
			list($h,$s,$v)=rgb_to_hsv($operand_a);
			$result=hsv_to_rgb(floatval($h),floatval(fix_colour($s*intval($operand_b)/(100.0))),floatval($v));
			break;

		case 'val_to':
			list($h,$s,$v)=rgb_to_hsv($operand_a);
			$result=hsv_to_rgb(floatval($h),floatval($s),floatval(fix_colour(255*intval($operand_b)/(100.0))));
			break;

		case 'val_add':
			list($h,$s,$v)=rgb_to_hsv($operand_a);
			$result=hsv_to_rgb(floatval($h),floatval($s),floatval(fix_colour($v+intval($operand_b))));
			break;

		case 'val':
			list($h,$s,$v)=rgb_to_hsv($operand_a);
			$result=hsv_to_rgb(floatval($h),floatval($s),floatval(fix_colour($v*intval($operand_b)/(100.0))));
			break;

		case 'hue_to':
			list($h,$s,$v)=rgb_to_hsv($operand_a);
			$result=hsv_to_rgb(floatval(fix_colour(255*intval($operand_b)/(100.0),true)),floatval($s),floatval($v));
			break;

		case 'hue_add':
			list($h,$s,$v)=rgb_to_hsv($operand_a);
			$result=hsv_to_rgb(floatval(fix_colour($h+intval($operand_b),true)),floatval($s),floatval($v));
			break;

		case 'hue':
			list($h,$s,$v)=rgb_to_hsv($operand_a);
			$result=hsv_to_rgb(floatval(fix_colour($h*intval($operand_b)/(100.0),true)%255),floatval($s),floatval($v));
			break;

		case '&':
			$red=hexdec(substr($operand_a,0,2));
			$green=hexdec(substr($operand_a,2,2));
			$blue=hexdec(substr($operand_a,4,2));
			$fraction=1.0-$operand_c/100.0;

			$red_b=hexdec(substr($operand_b,0,2));
			$green_b=hexdec(substr($operand_b,2,2));
			$blue_b=hexdec(substr($operand_b,4,2));

			$red=intval($fraction*$red+(1-$fraction)*$red_b);
			$green=intval($fraction*$green+(1-$fraction)*$green_b);
			$blue=intval($fraction*$blue+(1-$fraction)*$blue_b);

			$result=str_pad(dechex(fix_colour($red)),2,'0',STR_PAD_LEFT).str_pad(dechex(fix_colour($green)),2,'0',STR_PAD_LEFT).str_pad(dechex(fix_colour($blue)),2,'0',STR_PAD_LEFT);
			break;

		case '*':
			$red=intval(round(floatval(hexdec(substr($operand_b,0,2))*intval($operand_a))/(100.0)));
			$green=intval(round(floatval(hexdec(substr($operand_b,2,2))*intval($operand_a))/(100.0)));
			$blue=intval(round(floatval(hexdec(substr($operand_b,4,2))*intval($operand_a))/(100.0)));
			/*if ((($expression[2]==='WB') && ($colours['LD']=='dark')) || (($expression[2]==='BW') && ($colours['LD']=='light')))
			{
				$red=255-$red;
				$green=255-$green;
				$blue=255-$blue;
			}*/
			$result=str_pad(dechex($red),2,'0',STR_PAD_LEFT).str_pad(dechex($green),2,'0',STR_PAD_LEFT).str_pad(dechex($blue),2,'0',STR_PAD_LEFT);
			break;

		/* These are communicative combinators */

		case '+':
			$red=fix_colour(hexdec(substr($operand_a,0,2))+hexdec(substr($operand_b,0,2)));
			$green=fix_colour(hexdec(substr($operand_a,2,2))+hexdec(substr($operand_b,2,2)));
			$blue=fix_colour(hexdec(substr($operand_a,4,2))+hexdec(substr($operand_b,4,2)));
			$result=str_pad(dechex($red),2,'0',STR_PAD_LEFT).str_pad(dechex($green),2,'0',STR_PAD_LEFT).str_pad(dechex($blue),2,'0',STR_PAD_LEFT);
			break;

		case '-':
			$red=fix_colour(hexdec(substr($operand_a,0,2))-hexdec(substr($operand_b,0,2)));
			$green=fix_colour(hexdec(substr($operand_a,2,2))-hexdec(substr($operand_b,2,2)));
			$blue=fix_colour(hexdec(substr($operand_a,4,2))-hexdec(substr($operand_b,4,2)));
			$result=str_pad(dechex($red),2,'0',STR_PAD_LEFT).str_pad(dechex($green),2,'0',STR_PAD_LEFT).str_pad(dechex($blue),2,'0',STR_PAD_LEFT);
			break;

		/* These are miscellaneous */

		case 'shift':
			if (intval($operand_b)==1)
			{
				$result=substr($operand_a,4).substr($operand_a,0,4);
			} else
			{
				$result=substr($operand_a,2).substr($operand_a,0,2);
			}
			break;
	}

	return $result;
}

/**
 * Make sure a colour component fits within the necessary range (0<=x<256).
 *
 * @param  mixed		Colour component (float or integer).
 * @param  boolean	Whether this is hue (meaning it cycles around)
 * @return integer	Constrained colour component.
 */
function fix_colour($x,$hue=false)
{
	if (is_float($x)) $x=intval(round($x));

	if ($hue)
	{
		while ($x>255) $x-=255;
		while ($x<0) $x+=255;
	} else
	{
		if ($x>255) $x=255;
		if ($x<0) $x=0;
	}

	return $x;
}

/**
 * Convert an RGB colour to HSV colour components. Based on publically distributed code fragments which were themselves based on others: reasonably assumed as public domain.
 *
 * @param  string	RRGGBB colour.
 * @return array	Triplet of (0-255) components: H, S, V
 */
function rgb_to_hsv($rgb)
{
	$red=hexdec(substr($rgb,0,2));
	$green=hexdec(substr($rgb,2,2));
	$blue=hexdec(substr($rgb,4,2));

	$r=$red/255.0;
	$g=$green/255.0;
	$b=$blue/255.0;
	$h=0.0;
	$s=0.0;
	$v=0.0;
	$min=min($r,$g,$b);
	$max=max($r,$g,$b);
	$delta=$max-$min;

	$v=$max;

	if ($delta==0.0)
	{
		$h=0.0;
		$s=0.0;
	}
	else
	{
		$s=$delta/$max;

		$d_r=((($max-$r)/6)+($delta/2))/$delta;
		$d_g=((($max-$g)/6)+($delta/2))/$delta;
		$d_b=((($max-$b)/6)+($delta/2))/$delta;

		if ($r==$max)
			$h=$d_b-$d_g;
		elseif ($g==$max)
			$h=(1.0/3.0)+$d_r-$d_b;
		else
			$h=(2.0/3.0)+$d_g-$d_r;

		if ($h<0.0)
			$h++;
		elseif ($h>1.0)
			$h--;
	}

	return array(intval(round($h*255)),intval(round($s*255)),intval(round($v*255)));
}

/**
 * Convert HSV colour components to an RGB colour. Based on publically distributed code fragments which were themselves based on others: reasonably assumed as public domain.
 *
 * @param  float	H component
 * @param  float	S component
 * @param  float	V component
 * @return string	RGB colour.
 */
function hsv_to_rgb($h,$s,$v)
{
	$h=6.0*$h/255.0;
	$s=$s/255.0;
	$v=$v/255.0;

	if ($s==0.0)
	{
		$r=$v;
		$g=$v;
		$b=$v;
	} else
	{
		$hi=intval(floor($h));
		$f=$h-$hi;
		$p=($v*(1.0-$s));
		$q=($v*(1.0-($f*$s)));
		$t=($v*(1.0-((1.0-$f)*$s)));

		switch($hi)
		{
			case 0:
				$r=$v;
				$g=$t;
				$b=$p;
				break;
			case 1:
				$r=$q;
				$g=$v;
				$b=$p;
				break;
			case 2:
				$r=$p;
				$g=$v;
				$b=$t;
				break;
			case 3:
				$r=$p;
				$g=$q;
				$b=$v;
				break;
			case 4:
				$r=$t;
				$g=$p;
				$b=$v;
				break;
			default:
				$r=$v;
				$g=$p;
				$b=$q;
				break;
		}
	}

	return	str_pad(dechex(fix_colour(intval(round($r*255)))),2,'0',STR_PAD_LEFT).
				str_pad(dechex(fix_colour(intval(round($g*255)))),2,'0',STR_PAD_LEFT).
				str_pad(dechex(fix_colour(intval(round($b*255)))),2,'0',STR_PAD_LEFT);
}

/**
 * Rewrite some CSS code according to a CSS landscape.
 *
 * @param  ID_TEXT	CSS filename of source file.
 * @param  array		The colour expression landscape which we'll make substitutions using.
 * @param  ID_TEXT	The theme this is being generated from
 * @param  ID_TEXT	The algorithm to use
 * @set equations hsv
 * @param  ID_TEXT	The seed colour
 * @return string		The sheet
 */
function theme_wizard_colours_to_sheet($sheet,$landscape,$source_theme,$algorithm,$seed)
{
	$theme=filter_naughty($source_theme);

	if (file_exists(get_file_base().'/themes/'.$theme.'/css_custom/'.filter_naughty($sheet)))
	{
		$contents=unixify_line_format(file_get_contents(get_file_base().'/themes/'.$theme.'/css_custom/'.filter_naughty($sheet)));
	} elseif (file_exists(get_file_base().'/themes/'.$theme.'/css/'.filter_naughty($sheet)))
	{
		$contents=unixify_line_format(file_get_contents(get_file_base().'/themes/'.$theme.'/css/'.filter_naughty($sheet)));
	} elseif (file_exists(get_file_base().'/themes/default/css_custom/'.filter_naughty($sheet)))
	{
		$contents=unixify_line_format(file_get_contents(get_file_base().'/themes/default/css_custom/'.filter_naughty($sheet)));
	} else/*if (file_exists(get_file_base().'/themes/default/css/'.filter_naughty($sheet)))*/
	{
		$contents=unixify_line_format(file_get_contents(get_file_base().'/themes/default/css/'.filter_naughty($sheet)));
	}

	return theme_wizard_colours_to_css($contents,$landscape,$source_theme,$algorithm,$seed);
}

/**
 * Rewrite some CSS code according to a CSS landscape.
 *
 * @param  string		CSS to apply to.
 * @param  array		The colour expression landscape which we'll make substitutions using.
 * @param  ID_TEXT	The theme this is being generated from
 * @param  ID_TEXT	The algorithm to use
 * @set equations hsv
 * @param  ID_TEXT	The seed colour
 * @return string		The sheet
 */
function theme_wizard_colours_to_css($contents,$landscape,$source_theme,$algorithm,$seed)
{
	if ($algorithm=='hsv')
	{
		list($ocportal_h,$ocportal_s,$ocportal_v)=rgb_to_hsv(find_theme_seed($source_theme,true));
		list($desired_h,$desired_s,$desired_v)=rgb_to_hsv($seed);
		$hue_dif=$desired_h-$ocportal_h;
		$sat_dif=0;//$desired_s-$ocportal_s;		Actually causes weirdness
		$val_dif=$desired_v-$ocportal_v;

		$matches=array();
		$num_matches=preg_match_all('#\#([A-Fa-f0-9]{3,6})([^A-Fa-f0-9])#',$contents,$matches);
		for ($i=0;$i<$num_matches;$i++)
		{
			list($h,$s,$v)=rgb_to_hsv((strlen($matches[1][$i])==3)?($matches[1][$i][0].$matches[1][$i][0].$matches[1][$i][1].$matches[1][$i][1].$matches[1][$i][2].$matches[1][$i][2]):$matches[1][$i]);
			$new_colour=hsv_to_rgb(floatval(fix_colour($h+$hue_dif,true)),floatval(fix_colour($s+$sat_dif)),floatval(fix_colour($v+$val_dif)));
			$contents=str_replace(array(strtolower($matches[0][$i]),strtoupper($matches[0][$i])),array('#'.$new_colour.$matches[2][$i],'#'.$new_colour.$matches[2][$i]),$contents);
		}

		return $contents;
	}

	foreach ($landscape as $peak)
	{
		$from=$peak[2];
		$to=preg_replace('#\{\$THEME_WIZARD_COLOR,\#[\da-fA-F]{6},#','{$THEME_WIZARD_COLOR,#'.$peak[3].',',$peak[2]);
		$contents=str_replace($from,$to,$contents);
	}

	return $contents;
}

/**
 * Generate a theme image by converting an existing one to a new colour scheme via re-hueing.
 *
 * @param  mixed		The image path OR a preloaded GD image resource
 * @param  string		The colour code of our hue
 * @param  ID_TEXT	The theme this is being generated from
 * @param  boolean	Whether to also adjust the S and V components
 * @param  boolean	Whether to invert the colours
 * @return resource  The image
 */
function re_hue_image($path,$seed,$source_theme,$also_s_and_v=false,$invert=false)
{
	list($ocportal_h,$ocportal_s,$ocportal_v)=rgb_to_hsv(find_theme_seed($source_theme));
	list($seed_h,$seed_s,$seed_v)=rgb_to_hsv($seed);
	$hue_dif=$seed_h-$ocportal_h;
	$sat_dif=$seed_s-$ocportal_s;
	$val_dif=$seed_v-$ocportal_v;

	if (is_string($path))
	{
		if ((function_exists('imagecreatefromgif')) && (substr($path,-4)=='.gif'))
		{
			$_image=@imagecreatefromgif($path);
		} elseif (substr($path,-4)=='.jpg')
		{
			$_image=@imagecreatefromjpeg($path);
		} else
		{
			$_image=@imagecreatefrompng($path);
		}
		if ($_image===false) warn_exit(do_lang_tempcode('CORRUPT_FILE',escape_html($path)));
	} else
	{
		$_image=$path;
	}
	$width=imagesx($_image);
	$height=imagesy($_image);
	if (function_exists('imageistruecolor'))
	{
		if (function_exists('imagecreatetruecolor'))
		{
			if (!imageistruecolor($_image))
			{
				$trans_colour=imagecolortransparent($_image);
				$image=imagecreatetruecolor($width,$height);
				imagecopy($image,$_image,0,0,0,0,$width,$height);
			} else
			{
				$image=$_image;
				$trans_colour=NULL;
			}
		} else
		{
			$image=$_image;
			$trans_colour=imagecolortransparent($_image);
		}
	} else
	{
		$image=$_image;
		$trans_colour=imagecolortransparent($_image);
	}
	imagealphablending($image,false);
	imagesavealpha($image,true);

	for ($y=0;$y<$height;$y++)
	{
		for ($x=0;$x<$width;$x++)
		{
			$_existing_colour=imagecolorat($image,$x,$y);
			$existing_colour=imagecolorsforindex($image,$_existing_colour);
			if (!is_null($trans_colour))
			{
				$__existing_colour=imagecolorat($_image,$x,$y);
				if ($__existing_colour==$trans_colour) $existing_colour['alpha']=127;
			}

			$r=$existing_colour['red'];
			$g=$existing_colour['green'];
			$b=$existing_colour['blue'];
			$a=$existing_colour['alpha'];

			list($h,$s,$v)=rgb_to_hsv(str_pad(dechex($r),2,'0',STR_PAD_LEFT).str_pad(dechex($g),2,'0',STR_PAD_LEFT).str_pad(dechex($b),2,'0',STR_PAD_LEFT));
			if ($invert)
			{
				$v=255-$v;
				$v=min($v*3,255); // Because it's harder to see deviations of black
			}
			if ($seed_s<10) $s=$seed_s; // To stop red colours for gray-scale images
			if ($also_s_and_v)
			{
				$sat_dif=0; // Actually causes weirdness
				$result=hsv_to_rgb(floatval(fix_colour($h+$hue_dif,true)),floatval(fix_colour($s+$sat_dif)),floatval(fix_colour($v+$val_dif)));
			} else
			{
				$result=hsv_to_rgb(floatval(fix_colour($h+$hue_dif,true)),floatval($s),floatval($v));
			}

			$new_colour_r=hexdec(substr($result,0,2));
			$new_colour_g=hexdec(substr($result,2,2));
			$new_colour_b=hexdec(substr($result,4,2));

			if (function_exists('imagecolorallocatealpha'))
			{
				$target_colour=imagecolorallocatealpha($image,$new_colour_r,$new_colour_g,$new_colour_b,$a);
			} else
			{
				$target_colour=imagecolorallocate($image,$new_colour_r,$new_colour_g,$new_colour_b);
			}
			imagesetpixel($image,$x,$y,$target_colour);
		}
	}

	return $image;
}

/**
 * Generate a gradient for a theme.
 *
 * @param  string		Colour for the top.
 * @param  string		Colour for the bottom.
 * @return resource  The image
 */
function generate_gradient($top,$bottom)
{
	$gradient=imagecreate(1,27);
	$width=27;

	$topred=intval(base_convert(substr($top,0,2),16,10));
	$topgrn=intval(base_convert(substr($top,2,2),16,10));
	$topblu=intval(base_convert(substr($top,4,2),16,10));

	$botred=intval(base_convert(substr($bottom,0,2),16,10));
	$botgrn=intval(base_convert(substr($bottom,2,2),16,10));
	$botblu=intval(base_convert(substr($bottom,4,2),16,10));

	$dr=($botred-$topred)/$width;
	$dg=($botgrn-$topgrn)/$width;
	$db=($botblu-$topblu)/$width;

	for ($i=0;$i<$width;$i++)
	{
		$color=imagecolorallocate($gradient,$topred+intval(round($dr*floatval($i))),$topgrn+intval(round($dg*floatval($i))),$topblu+intval(round($db*floatval($i))));
		imagesetpixel($gradient,0,$i,$color);
		//ImageLine($gradient, 0,$i, 1,$i+1 ,$color);
	}

	return $gradient;
}

/**
 * Generate a theme image by converting an existing one to a new colour scheme via intelligent blending correlation.
 *
 * @param  mixed		The image path OR a preloaded GD image resource
 * @param  string		The colour code of what we have as our "minor" colour (often a border colour)
 * @param  string		The colour code of what we want as our "minor" colour (often a border colour)
 * @param  string		The colour code of what we have as our first major colour (often the only major colour)
 * @param  string		The colour code of what we want as our first major colour (often the only major colour)
 * @param  ?string	The colour code of what we have as our second major colour (the gradient target, at the bottom/right of the image) (NULL: not gradiented)
 * @param  ?string	The colour code of what we want as our second major colour (the gradient target, at the bottom/right of the image) (NULL: not gradiented)
 * @param  string		The directional code for the gradient
 * @set    vertical horizontal
 * @param  ?array		An array that is used to limit where we do our conversion on. It specifies, for each y-offset, the x-offset we start from (NULL: no such limitation)
 * @param  integer	What the gradient assumed start-position will be offset by (in the gradient direction).
 * @param  boolean	Whether the pixel_x_start array is actually an end array.
 * @return resource  The image
 */
function generate_recoloured_image($path,$colour_a_orig,$colour_a_new,$colour_b1_orig,$colour_b1_new,$colour_b2_orig=NULL,$colour_b2_new=NULL,$gradient_direction='vertical',$pixel_x_start_array=NULL,$gradient_offset=0,$end_array=false)
{
	/*$colour_a_new=$colour_a_orig;  For testing: a null conversion
	$colour_b1_new=$colour_b1_orig;
	$colour_b2_new=$colour_b2_orig;*/

	$colour_a_orig=str_replace('#','',$colour_a_orig);
	$colour_b1_orig=str_replace('#','',$colour_b1_orig);
	if (!is_null($colour_b2_new))
	{
		$colour_b2_orig=str_replace('#','',$colour_b2_orig);
	}
	$colour_a_new=str_replace('#','',$colour_a_new);
	$colour_b1_new=str_replace('#','',$colour_b1_new);
	if (!is_null($colour_b2_new))
	{
		$colour_b2_new=str_replace('#','',$colour_b2_new);
	}
	$colour_a_orig_r=hexdec(substr($colour_a_orig,0,2));
	$colour_a_orig_g=hexdec(substr($colour_a_orig,2,2));
	$colour_a_orig_b=hexdec(substr($colour_a_orig,4,2));
	$colour_a_new_r=hexdec(substr($colour_a_new,0,2));
	$colour_a_new_g=hexdec(substr($colour_a_new,2,2));
	$colour_a_new_b=hexdec(substr($colour_a_new,4,2));
	$colour_b1_orig_r=hexdec(substr($colour_b1_orig,0,2));
	$colour_b1_orig_g=hexdec(substr($colour_b1_orig,2,2));
	$colour_b1_orig_b=hexdec(substr($colour_b1_orig,4,2));
	$colour_b1_new_r=hexdec(substr($colour_b1_new,0,2));
	$colour_b1_new_g=hexdec(substr($colour_b1_new,2,2));
	$colour_b1_new_b=hexdec(substr($colour_b1_new,4,2));
	if (!is_null($colour_b2_new))
	{
		$colour_b2_orig_r=hexdec(substr($colour_b2_orig,0,2));
		$colour_b2_orig_g=hexdec(substr($colour_b2_orig,2,2));
		$colour_b2_orig_b=hexdec(substr($colour_b2_orig,4,2));
		$colour_b2_new_r=hexdec(substr($colour_b2_new,0,2));
		$colour_b2_new_g=hexdec(substr($colour_b2_new,2,2));
		$colour_b2_new_b=hexdec(substr($colour_b2_new,4,2));
	}

	if (is_string($path))
	{
		if ((function_exists('imagecreatefromgif')) && (substr($path,-4)=='.gif'))
		{
			$_image=@imagecreatefromgif($path);
		} elseif (substr($path,-4)=='.jpg')
		{
			$_image=@imagecreatefromjpeg($path);
		} else
		{
			$_image=@imagecreatefrompng($path);
		}
		if ($_image===false) warn_exit(do_lang_tempcode('CORRUPT_FILE',escape_html($path)));
	} else
	{
		$_image=$path;
	}
	$width=imagesx($_image);
	$height=imagesy($_image);
	if (function_exists('imageistruecolor'))
	{
		if (function_exists('imagecreatetruecolor'))
		{
			if (!imageistruecolor($_image))
			{
				$image=imagecreatetruecolor($width,$height);
				imagecopy($image,$_image,0,0,0,0,$width,$height);
			} else $image=$_image;
		} else $image=$_image;
	} else $image=$_image;
	imagealphablending($image,false);
	imagesavealpha($image,true);

	if (is_null($colour_b2_new))
	{
		$colour_b_orig_r=$colour_b1_orig_r;
		$colour_b_orig_g=$colour_b1_orig_g;
		$colour_b_orig_b=$colour_b1_orig_b;
		$colour_b_orig=$colour_b1_orig;
		$colour_b_new_r=$colour_b1_new_r;
		$colour_b_new_g=$colour_b1_new_g;
		$colour_b_new_b=$colour_b1_new_b;
		$colour_b_new=$colour_b1_new;
	}

	if (function_exists('imageistruecolor'))
	{
		if (function_exists('imagecreatetruecolor'))
		{
			if (!imageistruecolor($_image))
				$trans_colour=imagecolortransparent($_image);
			else
				$trans_colour=NULL;
		} else $trans_colour=imagecolortransparent($_image);
	} else $trans_colour=imagecolortransparent($_image);

	$gh=floatval($height-$gradient_offset);
	$gw=floatval($width-$gradient_offset);

	$vertical=($gradient_direction=='vertical');
	$horizontal=($gradient_direction=='horizontal');

	for ($y=0;$y<$height;$y++)
	{
		$x=0;
		$end=$width;
		if ($end_array)
		{
			if ((!is_null($pixel_x_start_array)) && (array_key_exists($y,$pixel_x_start_array)))
				$end=min($width,$pixel_x_start_array[$y]);
			else $end=$width;
		} else
		{
			if ((!is_null($pixel_x_start_array)) && (array_key_exists($y,$pixel_x_start_array)))
				$x=$pixel_x_start_array[$y];
		}
		for (;$x<$end;$x++)
		{
			$_existing_colour=imagecolorat($image,$x,$y);
			$existing_colour=imagecolorsforindex($image,$_existing_colour);
			if (!is_null($trans_colour))
			{
				$__existing_colour=imagecolorat($_image,$x,$y);
				if ($__existing_colour==$trans_colour) $existing_colour['alpha']=127;
			}

			if (!is_null($colour_b2_new))
			{
				if ($vertical)
				{
					$ratio=floatval($y-$gradient_offset)/$gh;
				}
				elseif ($horizontal)
				{
					$ratio=floatval($x-$gradient_offset)/$gw;
				}
				$colour_b_orig_r=intval($colour_b1_orig_r+($ratio*($colour_b2_orig_r-$colour_b1_orig_r)));
				$colour_b_orig_g=intval($colour_b1_orig_g+($ratio*($colour_b2_orig_g-$colour_b1_orig_g)));
				$colour_b_orig_b=intval($colour_b1_orig_b+($ratio*($colour_b2_orig_b-$colour_b1_orig_b)));
				$colour_b_new_r=intval($colour_b1_new_r+($ratio*($colour_b2_new_r-$colour_b1_new_r)));
				$colour_b_new_g=intval($colour_b1_new_g+($ratio*($colour_b2_new_g-$colour_b1_new_g)));
				$colour_b_new_b=intval($colour_b1_new_b+($ratio*($colour_b2_new_b-$colour_b1_new_b)));
			}

			$existing_colour_r=$existing_colour['red'];
			$existing_colour_g=$existing_colour['green'];
			$existing_colour_b=$existing_colour['blue'];
			$existing_colour_a=$existing_colour['alpha'];

			$scale_r=NULL;
			$scale_g=NULL;
			$scale_b=NULL;
			$scale_count=0;
			if ($colour_a_orig_r!=$colour_b_orig_r)
			{
				$scale_r=($colour_a_orig_r-$colour_b_orig_r==0)?0.0:floatval($existing_colour_r-$colour_b_orig_r)/floatval($colour_a_orig_r-$colour_b_orig_r);
				$scale_count++;
			}
			if ($colour_a_orig_r!=$colour_b_orig_r)
			{
				$scale_g=($colour_a_orig_g-$colour_b_orig_g==0)?0.0:floatval($existing_colour_g-$colour_b_orig_g)/floatval($colour_a_orig_g-$colour_b_orig_g);
				$scale_count++;
			}
			if ($colour_a_orig_r!=$colour_b_orig_r)
			{
				$scale_b=($colour_a_orig_b-$colour_b_orig_b==0)?0.0:floatval($existing_colour_b-$colour_b_orig_b)/floatval($colour_a_orig_b-$colour_b_orig_b);
				$scale_count++;
			}
			if ($scale_count==0) // Impossible to calculate
			{
				$scale=0.5;
			} else
			{
				$scale=($scale_r+$scale_g+$scale_b)/floatval($scale_count);
			}

			$new_colour_r=fix_colour(intval(round(floatval($colour_a_new_r)*$scale+floatval($colour_b_new_r)*(1.0-$scale))));
			$new_colour_g=fix_colour(intval(round(floatval($colour_a_new_g)*$scale+floatval($colour_b_new_g)*(1.0-$scale))));
			$new_colour_b=fix_colour(intval(round(floatval($colour_a_new_b)*$scale+floatval($colour_b_new_b)*(1.0-$scale))));

			if (function_exists('imagecolorallocatealpha'))
			{
				$target_colour=imagecolorallocatealpha($image,$new_colour_r,$new_colour_g,$new_colour_b,$existing_colour_a);
			} else
			{
				$target_colour=imagecolorallocate($image,$new_colour_r,$new_colour_g,$new_colour_b);
			}
			imagesetpixel($image,$x,$y,$target_colour);
		}
	}

	return $image;
}

