<?php
#CMS - CMS Made Simple
#This project's homepage is: http://dev.cmsmadesimple.org
#
#This program is free software; you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation; either version 2 of the License, or
#(at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY.

function smarty_function_favicon($params, &$template)
{
	$smarty = $template->smarty;
	$gCms = cmsms();
	
	$srcfile = $params['src'];
	
	if($srcfile{0} == '/') {
		$srcfile = substr($srcfile, 1);
	}
	
	$srcfilepath = realpath($gCms->config['image_uploads_path'].'/'.$srcfile);

	if(!file_exists($srcfilepath) or !is_readable($srcfilepath)) {
		return "<!-- Error from favicon-plugin: sourcefile ".htmlentities($srcfile)." does not exists -->";
	}
	
	$icon_sizes = _function_favicon_set_icon_size($params['icon_size'], array(16, 32, 96, 160, 196));
	$apple_icon_sizes = _function_favicon_set_icon_size($params['apple_icon_size'], array(57, 60, 72, 76, 114, 120, 144, 152));
	
	$do_ico = true;
	$do_png = true;
	$do_apple = true;
		
	if(isset($params['only']) and strlen($params['only']) > 0) {
		$m = array();
		if(preg_match_all('/[a-z]+/x', $params['only'], $m, PREG_PATTERN_ORDER)) {
			$do_ico = false;
			$do_png = false;
			$do_apple = false;
			foreach($m[0] as $mm) {
				if(strcmp($mm, 'ico') == 0) { $do_ico = true; }
				elseif(strcmp($mm, 'png') == 0) { $do_png = true; }
				elseif(strcmp($mm, 'apple') == 0) { $do_apple = true; }				
			}			
		}
	}
	

	$srcfileinfo = pathinfo($srcfilepath);
	$srcfilebasename = $srcfileinfo['filename'];
	
	$icondir = $gCms->config['image_uploads_path'].'/favicons/'.$srcfilebasename;
	$iconurl = $gCms->config['image_uploads_url'].'/favicons/'.$srcfilebasename;
	
	// check that favicon subfolder exists
	if(!file_exists($icondir)) {
		mkdir($icondir, $smarty->_dir_perms === null ? 0777 : $smarty->_dir_perms, true);
		if(!file_exists($icondir)) {
			return "<!-- Error from favicon-plugin: can not generate subfolder ".htmlentities($icondir)." -->";
		}
	}
	
	$output = "";
	
	// the basic ico favicon
	$faviconpath = $icondir.'/favicon.ico';
	$faviconurl = $iconurl.'/favicon.ico';
	
	if($do_ico) {
		if(!file_exists($faviconpath)) {
			$ico_lib = new PHP_ICO($srcfilepath, array(array(32, 32)));
			$ico_lib->save_ico($faviconpath);
			unset($ico_lib);
		}
		
		if(file_exists($faviconpath)) {
			$output .= '<link rel="shortcut icon" type="image/vnd.microsoft.icon" href="'.$faviconurl.'" />'."\n".'<link rel="icon" type="image/vnd.microsoft.icon" href="'.$faviconurl.'" />';
		}
	}
	
	if($do_png) {
		// png favicons with different sizes
		$output .= _function_favicon_create_bunch('favicon', 'icon', 'image/png', $icon_sizes, $icondir, $iconurl, $srcfilepath);
	}
	
	if($do_apple) {
		// apple touch favicons
		$output .= _function_favicon_create_bunch('apple-touch-icon', 'apple-touch-icon', 'image/png', $apple_icon_sizes, $icondir, $iconurl, $srcfilepath);
	}
	
	if(isset($params['assign'])){
		$smarty->assign(trim($params['assign']), $output);
		return;
	}
	
	return $output;
}

function smarty_cms_help_function_favicon()
{
	$gCms = cmsms();
	$config = $gCms->GetConfig();
	
	?>
	<h3>What does this do?</h3>
	<p>This plugin generates a bunch of different favicon files from a given source file and returns html code embedding these images as favicons links and apple touch icons links.</p>
	<p>The generates icons from <tt>myfaviconsrc.jpg</tt> are put in a subfolder <tt>/favicons/myfaviconsrc</tt> in your images folder. If you update myfaviconsrc.jpg, you need to delete the folder <tt>myfaviconsrc</tt> or at least the containing images in order to force the icons to be regenerated.</p>
	<h3>How do I use it?</h3>
	<p>Just insert this tag into the header of your page like: <code>{favicon src='myfaviconsrc.jpg'}</code></p>
	<p>You can place it either in the <code><?php echo htmlentities('<head></head>'); ?></code> section of your template or in global meta data (Site preferences)</p>
	<h3>What parameters does it take?</h3>
	<p>
			<ul>
				<li><em>(required)</em> src - image file within you images folder which should the source to generate icons</li>
				<li>icon_size - a list of width in pixels for the different png icon sizes to be linked. Default is "16, 32, 96, 160, 196", which generates and links png icons width 16x16, 32x32, 96x96, 160x160, 196x196.</li>
				<li>apple_icon_size - same as icon_size but for the apple touch icons, which have differenz width by default: 57, 60, 72, 76, 114, 120, 144, 152 pixels.</li>
				<li>only - generates and links only listet in this parameter (multiple values seperated by space):
					<ul>
						<li>ico - only .ico-file</li>
						<li>png - only .png-files, which are linked with rel="icon"</li>
						<li>apple - only .png-files, which are linked with rel="apple-touch-icon"</li>
					</ul>
					Use this parameter to use different source images for apple and normal icons or generated sizes.</li>
				<li>assign - smarty variable name to assign the returned content of this plugin</li>
			</ul>
	</p>
	<h3>Additional Infos</h3>
	<ul>
	<li>Your source file should be square width at least 196px width and height in order to generate the biggest apple icon</li> 
	<li>You should only use A-Za-z0-9_- within the filename. (No spaces or non-ASCII-characters!). Remember that path and filenames are case-sensitive!</li>
	<li>Valid src image formats are:  JPEG, PNG, GIF, WBMP, and GD2 if your build of PHP supports them.</li>
	</ul>
	
	<h3>Thanks to</h3>
	<p>Thanks to chrisbliss18 and his php-ico class found at <a href="https://github.com/chrisbliss18/php-ico">https://github.com/chrisbliss18/php-ico</a></p>
	<?php
}

function smarty_cms_about_function_favicon()
{
	?>
	<p>Author: Georg Wallisch (mail@phpco.de);</p>
	<p>Version: 1.1</p>
	<p>Change History:</p>
	<ul>
	<li>1.0 - Initial release (03.04.2015)</li>
	<li>1.1 - Refactored. Added more parameters (04.04.2015)</li>
	<?php
}

function _function_favicon_set_icon_size($str, $default) {
	
	if(!isset($str) or strlen($str) < 1) {
		return $default;
	}
	
	$m = array();
	
	if(preg_match_all('/\d+/x', $str, $m, PREG_PATTERN_ORDER)) {
		sort($m[0]);
		return $m[0];		
	}
	
	return $default;
}

function _function_favicon_create_bunch($iconname, $rel, $type, $icon_sizes, $icondir, $iconurl, $srcfilepath) {
	
	$output = '';
	$t = (strlen($type) > 0) ? 'type="'.$type.'" ' : '';
	
	if(!is_array($icon_sizes)) return false;
	
	foreach($icon_sizes as $icon_size) {
		$icon_size_txt = "{$icon_size}x{$icon_size}";
		$faviconfile = "{$iconname}-{$icon_size_txt}.png";
		$faviconpath = $icondir.'/'.$faviconfile;
		$faviconurl = $iconurl.'/'.$faviconfile;
		
		if(file_exists($faviconpath) or _function_favicon_create_png_icon($srcfilepath, $faviconpath, $icon_size)) {
			$output .= "\n".'<link rel="'.$rel.'" '.$t.'href="'.$faviconurl.'" sizes="'.$icon_size_txt.'" />';
		} else {
			$output .= "\n<!-- Couldn't generate {$faviconpath} out of {$srcfilepath} with a size of {$icon_size} -->";
		}			
	}
	
	return $output;
}


function _function_favicon_create_png_icon($srcfile, $dstfile, $dstwidth) {
		
	$srcsize = @getimagesize($srcfile);
	
	if(!is_array($srcsize)) {
		//$errors[] = 'Cannot read sourcefile';
		return false;
	}
	
	$srcwidth = $srcsize[0];
	$srcheight = $srcsize[1];
	$dstheight = $dstwidth;
		
	if($srcwidth <= $dstwidth and $srcheight <= $dstheight) {
		//$errors[] = 'Source image smaller than destinated image';
		return false;
	}
		
	$src_image = @imagecreatefromstring(@file_get_contents($srcfile));
	$dst_image = @imagecreatetruecolor($dstwidth, $dstheight);
	
	if($src_image == false or $dst_image == false) {
		return false;
	}
	
	// Initialise the destination image to transparent instead of
	// the default solid black, to support PNG and GIF transparency nicely
	$background = imagecolorallocate($dst_image, 0, 0, 0);
	imagecolortransparent($dst_image, $background);
	imagealphablending($dst_image, false);
	
	if($colorStyle == 'palette') {
		// Don't resample for paletted GIF images.
		// It may just uglify them, and completely breaks transparency.
		imagecopyresized($dst_image, $src_image,
			0,0,0,0,
			$dstwidth, $dstheight, imagesx($src_image), imagesy($src_image));
	} else {
		imagecopyresampled($dst_image, $src_image,
			0,0,0,0,
			$dstwidth, $dstheight, imagesx($src_image), imagesy($src_image));
	}
	
	imagesavealpha($dst_image, true);
	
	imagepng($dst_image, $dstfile);
	imagedestroy($dst_image);
	imagedestroy($src_image);
	
	//chmod($dstfile,0777);
	
	if(file_exists($dstfile) and is_readable($dstfile)) {
		return true;
	}
	
	//$errors[] = "{$dstfile} does not exists or is not readable.";
	return false;
}

/*
Copyright 2011-2013 Chris Jean & iThemes
Licensed under GPLv2 or above

Version 1.0.2

src: https://raw.githubusercontent.com/chrisbliss18/php-ico/
*/

class PHP_ICO {
	/**
	 * Images in the BMP format.
	 *
	 * @var array
	 * @access private
	 */
	var $_images = array();
	
	/**
	 * Flag to tell if the required functions exist.
	 *
	 * @var boolean
	 * @access private
	 */
	var $_has_requirements = false;
	
	
	/**
	 * Constructor - Create a new ICO generator.
	 *
	 * If the constructor is not passed a file, a file will need to be supplied using the {@link PHP_ICO::add_image}
	 * function in order to generate an ICO file.
	 *
	 * @param string $file Optional. Path to the source image file.
	 * @param array $sizes Optional. An array of sizes (each size is an array with a width and height) that the source image should be rendered at in the generated ICO file. If sizes are not supplied, the size of the source image will be used.
	 */
	function PHP_ICO( $file = false, $sizes = array() ) {
		$required_functions = array(
			'getimagesize',
			'imagecreatefromstring',
			'imagecreatetruecolor',
			'imagecolortransparent',
			'imagecolorallocatealpha',
			'imagealphablending',
			'imagesavealpha',
			'imagesx',
			'imagesy',
			'imagecopyresampled',
		);
		
		foreach ( $required_functions as $function ) {
			if ( ! function_exists( $function ) ) {
				trigger_error( "The PHP_ICO class was unable to find the $function function, which is part of the GD library. Ensure that the system has the GD library installed and that PHP has access to it through a PHP interface, such as PHP's GD module. Since this function was not found, the library will be unable to create ICO files." );
				return;
			}
		}
		
		$this->_has_requirements = true;
		
		
		if ( false != $file )
			$this->add_image( $file, $sizes );
	}
	
	/**
	 * Add an image to the generator.
	 *
	 * This function adds a source image to the generator. It serves two main purposes: add a source image if one was
	 * not supplied to the constructor and to add additional source images so that different images can be supplied for
	 * different sized images in the resulting ICO file. For instance, a small source image can be used for the small
	 * resolutions while a larger source image can be used for large resolutions.
	 *
	 * @param string $file Path to the source image file.
	 * @param array $sizes Optional. An array of sizes (each size is an array with a width and height) that the source image should be rendered at in the generated ICO file. If sizes are not supplied, the size of the source image will be used.
	 * @return boolean true on success and false on failure.
	 */
	function add_image( $file, $sizes = array() ) {
		if ( ! $this->_has_requirements )
			return false;
		
		if ( false === ( $im = $this->_load_image_file( $file ) ) )
			return false;
		
		
		if ( empty( $sizes ) )
			$sizes = array( imagesx( $im ), imagesy( $im ) );
		
		// If just a single size was passed, put it in array.
		if ( ! is_array( $sizes[0] ) )
			$sizes = array( $sizes );
		
		foreach ( (array) $sizes as $size ) {
			list( $width, $height ) = $size;
			
			$new_im = imagecreatetruecolor( $width, $height );
			
			imagecolortransparent( $new_im, imagecolorallocatealpha( $new_im, 0, 0, 0, 127 ) );
			imagealphablending( $new_im, false );
			imagesavealpha( $new_im, true );
			
			$source_width = imagesx( $im );
			$source_height = imagesy( $im );
			
			if ( false === imagecopyresampled( $new_im, $im, 0, 0, 0, 0, $width, $height, $source_width, $source_height ) )
				continue;
			
			$this->_add_image_data( $new_im );
		}
		
		return true;
	}
	
	/**
	 * Write the ICO file data to a file path.
	 *
	 * @param string $file Path to save the ICO file data into.
	 * @return boolean true on success and false on failure.
	 */
	function save_ico( $file ) {
		if ( ! $this->_has_requirements )
			return false;
		
		if ( false === ( $data = $this->_get_ico_data() ) )
			return false;
		
		if ( false === ( $fh = fopen( $file, 'w' ) ) )
			return false;
		
		if ( false === ( fwrite( $fh, $data ) ) ) {
			fclose( $fh );
			return false;
		}
		
		fclose( $fh );
		
		return true;
	}
	
	/**
	 * Generate the final ICO data by creating a file header and adding the image data.
	 *
	 * @access private
	 */
	function _get_ico_data() {
		if ( ! is_array( $this->_images ) || empty( $this->_images ) )
			return false;
		
		
		$data = pack( 'vvv', 0, 1, count( $this->_images ) );
		$pixel_data = '';
		
		$icon_dir_entry_size = 16;
		
		$offset = 6 + ( $icon_dir_entry_size * count( $this->_images ) );
		
		foreach ( $this->_images as $image ) {
			$data .= pack( 'CCCCvvVV', $image['width'], $image['height'], $image['color_palette_colors'], 0, 1, $image['bits_per_pixel'], $image['size'], $offset );
			$pixel_data .= $image['data'];
			
			$offset += $image['size'];
		}
		
		$data .= $pixel_data;
		unset( $pixel_data );
		
		
		return $data;
	}
	
	/**
	 * Take a GD image resource and change it into a raw BMP format.
	 *
	 * @access private
	 */
	function _add_image_data( $im ) {
		$width = imagesx( $im );
		$height = imagesy( $im );
		
		
		$pixel_data = array();
		
		$opacity_data = array();
		$current_opacity_val = 0;
		
		for ( $y = $height - 1; $y >= 0; $y-- ) {
			for ( $x = 0; $x < $width; $x++ ) {
				$color = imagecolorat( $im, $x, $y );
				
				$alpha = ( $color & 0x7F000000 ) >> 24;
				$alpha = ( 1 - ( $alpha / 127 ) ) * 255;
				
				$color &= 0xFFFFFF;
				$color |= 0xFF000000 & ( $alpha << 24 );
				
				$pixel_data[] = $color;
				
				
				$opacity = ( $alpha <= 127 ) ? 1 : 0;
				
				$current_opacity_val = ( $current_opacity_val << 1 ) | $opacity;
				
				if ( ( ( $x + 1 ) % 32 ) == 0 ) {
					$opacity_data[] = $current_opacity_val;
					$current_opacity_val = 0;
				}
			}
			
			if ( ( $x % 32 ) > 0 ) {
				while ( ( $x++ % 32 ) > 0 )
					$current_opacity_val = $current_opacity_val << 1;
				
				$opacity_data[] = $current_opacity_val;
				$current_opacity_val = 0;
			}
		}
		
		$image_header_size = 40;
		$color_mask_size = $width * $height * 4;
		$opacity_mask_size = ( ceil( $width / 32 ) * 4 ) * $height;
		
		
		$data = pack( 'VVVvvVVVVVV', 40, $width, ( $height * 2 ), 1, 32, 0, 0, 0, 0, 0, 0 );
		
		foreach ( $pixel_data as $color )
			$data .= pack( 'V', $color );
		
		foreach ( $opacity_data as $opacity )
			$data .= pack( 'N', $opacity );
		
		
		$image = array(
			'width'                => $width,
			'height'               => $height,
			'color_palette_colors' => 0,
			'bits_per_pixel'       => 32,
			'size'                 => $image_header_size + $color_mask_size + $opacity_mask_size,
			'data'                 => $data,
		);
		
		$this->_images[] = $image;
	}
	
	/**
	 * Read in the source image file and convert it into a GD image resource.
	 *
	 * @access private
	 */
	function _load_image_file( $file ) {
		// Run a cheap check to verify that it is an image file.
		if ( false === ( $size = getimagesize( $file ) ) )
			return false;
		
		if ( false === ( $file_data = file_get_contents( $file ) ) )
			return false;
		
		if ( false === ( $im = imagecreatefromstring( $file_data ) ) )
			return false;
		
		unset( $file_data );
		
		
		return $im;
	}
}

?>