<?php
/*
	Copyright (C) 2008 Giovanni Vallesi
	Copyright (C) 2008, 2009 CAST MediaTel <http://www.castmediatel.com/>.
	All rights reserved.

	part of a package for AskoziaPBX (http://askozia.com/pbx)
	Copyright (C) 2008 IKT <http://itison-ikt.de>.
	All rights reserved.

	Redistribution and use in source and binary forms, with or without
	modification, are permitted provided that the following conditions are met:
	
	1. Redistributions of source code must retain all the above copyrights notices,
	   this list of conditions and the following disclaimer.
	
	2. Redistributions in binary form must reproduce all the above copyrights
	   notices, this list of conditions and the following disclaimer in the
	   documentation and/or other materials provided with the distribution.
	
	THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
	AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
	AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
	POSSIBILITY OF SUCH DAMAGE.
*/

/* local functions  */

/**
 * Wrapper for echo, output a message to standard output or append to a global "buffer" variable ($msg_buf)
 *
 * Written to give feedback to user during package boot...
 * but the way package routinea executed redirect to dev/null :-((
 * This is the reason of $do_echo parameter which defaults to FALSE  
 * 
 * @param mixed var $msg							The message
 * @param bool	 		$do_echo					Default to false, echo $msg when true
 */
function put_msg($msg, $do_echo = FALSE)
{
	global $msg_buf;
	if(!isset($msg_buf)) { $msg_buf = ''; }
	if(is_array($msg))
	{
		$msg = print_r($msg, TRUE);
	}
	if(PKG_SYSBOOT & $do_echo)
	{
		echo $msg;
	}
	$msg_buf .= "$msg\n";
}

/**
 * Dump the message buffer (global $msg_buf) to log file (constant PKG_LOGFILE)
 *
 * @return integer										0 if no error, else return 1
 */
function log_msg()
{
	$result = 0;
	global $msg_buf;
	if($msg_buf <> '')
	{
		if(!$log_fhandle = fopen(PKG_LOGFILE, 'a'))
		{
			$result = 1;
		}
		else
		{
			$msg_buf = '[Message log dump at: ' . date("F j, Y, G:i") . "]\n$msg_buf";
			if(fwrite($log_fhandle, $msg_buf) === FALSE)
			{
				$result = 1;
			}
			$msg_buf = '';
		}
		fclose($log_fhandle);
	}
	return $result;
}

/**
 * Os check
 *
 * @param array $alloweds (optional)	List of alloweds OSs
 * @return boolean										True if OS is allowed, else...
 */
function allowed_os($alloweds = array('FreeBSD'))
{
	$os = php_uname('s');
	return in_array($os, $alloweds);
}

/**
 * Get which device is mounted on /mountpoint
 *
 * @param string $mnt_point						Mount point (no initial slash '/')
 * @return mixed_var									A string device name or integer 1 if error occured
 */
function get_mntdev($mnt_point = 'asterisk')
{
	$exp = '/(\/dev\/[\\da-zA-Z]+)\\son\\s\/'. $mnt_point .'/';
	$mnt_list = shell_exec('/sbin/mount');
	if (preg_match($exp, $mnt_list, $regs))
	{
		$result = $regs[1];
	}
	else { $result = 1; }
	return $result;
}

/**
 * Remount a device in ro or rw mode
 *
 * @param string $by_device						Device to mount
 * @param string $mount_point					Mount point (no initial slash '/')
 * @param string $mode								'ro' to remount read only, 'rw' to remount read/write
 * @return integer										0 if no error, else return 1
 */
function remount($by_device, $mount_point, $mode)
{
	$result = 1;
	exec("/sbin/umount -f /$mount_point > /dev/null 2>&1", $arr_out, $result);
	if($result == 0)
	{
		switch($mode)
		{
			case 'ro':
			{
				exec("/sbin/mount -o ro $by_device /$mount_point > /dev/null 2>&1", $arr_out, $result);
				break;
			}
			case 'rw':
			{
				exec("/sbin/mount -o rw $by_device /$mount_point > /dev/null 2>&1", $arr_out, $result);
				break;
			}
		}
	}
	return $result;
}

/**
 * Makes directory recursively, returns TRUE if exists or made
 *
 * @param string $pathname 						The directory path.
 * @return boolean 										returns TRUE if exists or made, FALSE on failure.
 */
function mkdir_recursive($pathname, $mode = 0755)
{
	is_dir(dirname($pathname)) || mkdir_recursive(dirname($pathname), $mode);
	return is_dir($pathname) || @mkdir($pathname, $mode);
}


/**
 * Copy a file/dir recursively, destination will be made if don't exists
 *
 * @param string $source							The source file/dir
 * @param string $target							Copy destination
 * @param bool $overwrite (opt.)			Defaults to FALSE, do not overwrite destination 
 * @return integer										0 where no errors
 */
function copy_recursive($source, $target, $overwrite = FALSE)
{
	$result = 0;
	// TO_DO: check if source is a link and already point to destination
	if(is_link($source))
	{
		// we don't wont to copy following the symlink but move to the file/s where it's pointing to
		$source = readlink($source);
	}
	if(file_exists($source))
	{
		if(is_dir($source))
		{
			mkdir_recursive($target);
			$dh = dir($source);
			while(FALSE !== ($curr_fp = $dh->read()))
			{
				if($curr_fp == '.' OR $curr_fp == '..')
				{
					continue;
				}
				$source_fp = $source . '/' . $curr_fp;
				$target_fp = $target . '/' . $curr_fp;
				if(is_dir($source . '/' . $curr_fp))
				{
					copy_recursive($source_fp, $target_fp);
					continue;
				}
				if(!file_exists($target_fp) OR $overwrite)
				{
					put_msg("Message, info (Copy: $source_fp -> $target_fp)");
					$result |= !(copy($source_fp, $target_fp));
				}
			}
			$dh->close();
		}
		else
		{
			if(!file_exists($target) OR $overwrite)
			{
				$target_dir = dirname($target);
				if(!file_exists($target_dir))
				{
					mkdir_recursive($target_dir);
				}
				put_msg("Message, info (Copy: $source -> $target)");
				$result |= !(copy($source, $target));
			}
		}
	}
	else put_msg("Message, warning (Copy source does not exists: $source)");
	return $result;
}

/**
 * Create symlinks in src directory pointing any file found in the target directory/tree.
 * 
 * If a file exists in the target path but not in src path, make a symlink in src to point the target
 * If a file exists on both paths, rename the file in src then make a symlink in src to point the target
 * 
 * @param string	$pkg_dir						The directory you want to start in to scan
 * @param string	$src_dir						The directory where [find original] files to symlink
 * @param boolean (opt.) $recursive		Recurse directory tree, default to FALSE.
 * @param string (opt.)								Suffix used where renaming files in $src_dir
 * @return int 												0 where no errors or the number of errors
 */
function pkg_files_ml($pkg_dir, $src_dir, $recursive = false, $ren_sfx = '-renamed')
{
	static $errors;
	if(!isset($errors)) {$errors = 0;}
	//
	if(is_dir($pkg_dir))
	{
		if($dh = opendir($pkg_dir))
		{
			while(($file = readdir($dh)) !== false)
			{
				$pkg_file = $pkg_dir . '/' . $file;
				$src_file = $src_dir . '/' . $file;
				if($file != '.' AND $file != '..')
				{
					put_msg("Info, file scan for user modules (data: $pkg_file -> $src_file)");
					if(is_dir($pkg_file))
					{
						// check if a matching src dir does not exists, isn't a symlink nor a file
						if(!is_link($src_file) AND !is_dir($src_file) AND !is_file($src_file))
						{
							// make a symlink to point the package dir
							if(!symlink($pkg_file, $src_file))
							{
								/* error */
								$errors++;
								put_msg("!Failed symlink (dir: $pkg_file-> $src_file)");
							}
						}
						else
						{
							// our recursion don't follow symlinked src dirs
							if($recursive AND !is_link($src_file))
							{
								ln_pkg_files($pkg_file, $src_file, $recursive, $ren_sfx);
							}
						}
					}
					else
					{
						// if current file is not a symlink...
						if(!is_link($src_file))
						{
							// if a matching file already exists in source dir...
							if(file_exists($src_file))
							{
								// ...first rename it
								if(!rename($src_file, $src_file . $ren_sfx))
								{
									/* error */
									$errors++;
									put_msg("!Failed rename (file: $src_file -> $src_file$ren_sfx)");
								}
							}
							// now create a symbolic link in source dir to to the file in target dir (the package dir)
							if(!symlink($pkg_file, $src_file))
							{
								/* error */
								$errors++;
								put_msg("!Failed symlink (file: $pkg_file-> $src_file)");
							}
						}
					}
					if($errors == 0) { put_msg('.'); /*progress*/ }
				}
			}
			closedir($dh);
		}
	}
	return $errors;
}

/**
 * Remove symlinks in src directory pointing any file found in the target directory/tree.
 * 
 * 
 * @param string	$pkg_dir						The directory where to find symlinks target
 * @param string	$src_dir						The directory you want to start in
 * @param boolean (opt.) $recursive		Recurse directory tree, default to FALSE.
 * @param string (opt.)								Prefix used whre files renamed
 * @return int 												0 where no errors or the number of errors
 */
function pkg_files_rl($pkg_dir, $src_dir, $recursive = false, $ren_sfx = '-renamed')
{
	static $errors;
	if(!isset($errors)) {$errors = 0;}
	//
	if(is_dir($src_dir))
	{
		if($dh = opendir($src_dir))
		{
			while(($file = readdir($dh)) !== false)
			{
				$old_file =  $src_dir . '/' . $file . $ren_sfx;
				$src_file = $src_dir . '/' . $file;
				$pkg_file = $pkg_dir . '/' . $file;
				if($file != '.' AND $file != '..')
				{
					if(is_link($src_file))
					{
						// check if current symlink point to our target in package files
						if(readlink($src_file) == $pkg_file)
						{
							// delete this symlink
							if(unlink($src_file))
							{
								// restore original filename
								if(is_file($old_file) OR is_dir($old_file))
								{
									if(!rename($old_file, $src_file))
									{
										$errors++;
										put_msg("\n!Failed rename on restore (file: $old_file -> $src_file)\n"); 
									}
								}
							}
							else
							{
								/* error */
								$errors++;
								put_msg("\n!Unexpected error deleting symlink (file: $src_file)\n");; 
							}
						}
					}
					elseif ($recursive AND is_dir($src_file))
					{
						// note: recursion don't follow symlinked src dirs
						if(!is_link($src_file))
						{
							pkg_files_rl($pkg_file, $src_file, $recursive, $ren_sfx);
						}
					}
				}
			}
			closedir($dh);
		}
	}
	return $errors;
}

/**
 * Stop/Start Asterisk
 *
 * @param string $opt									What to do, 'start' or 'stop'
 * @return integer										0 if success
 */
function ast_control($opt = 'stop')
{
	switch($opt)
	{
		case 'start':
		{
			// start asterisk
			exec("/usr/local/sbin/asterisk > /dev/null 2>&1", $oarr, $result);
			break;
		}
		case 'stop':
		{
			// stop asterisk
			exec("/usr/local/sbin/asterisk -rx \"stop now\" > /dev/null 2>&1", $oarr, $result);
			break;
		}
		default: { return 1; }
	}
	return $result;	
}

/**
 * Select which files copy/link to package directory tree & command options
 *
 * @param string $what								Option
 * @param string $rp (optional)				Prefix prepended to renamed files
 * @return array 											Command paths and options, plus status
 */
function media_select($what, $rs = '-renamed')
{
	$cmdpaths = array();
	$cmdpaths['status'] = 0;
	$cmdpaths['cp_src'] = '';						// File or directory to be copied
	$cmdpaths['mv_src'] = '';						// File or directory to be renamed
	$cmdpaths['mv_trg'] = '';						// The new filename
	//	
	switch($what)
	{
		case 'sounds':
		{
			$cmdpaths['cp_src'] = PKG_SOUNDS;
			$cmdpaths['mv_trg'] = PKG_SOUNDS . $rs;
			break;
		}
		case 'moh':
		{
			$cmdpaths['cp_src'] = PKG_MOH;
			$cmdpaths['mv_trg'] = PKG_MOH . $rs;
			break;
		}
		case 'voicemail':
		{
			$cmdpaths['cp_src'] = PKG_VOICEMAIL;
			$cmdpaths['mv_trg'] = PKG_VOICEMAIL . $rs;			
			break;
		}
		case 'astdb':
		{
			$cmdpaths['cp_src'] = PKG_ASTDB;
			$cmdpaths['mv_trg'] = PKG_ASTDB . $rs;
			break;
		}
		case 'agibin':
		{
			$cmdpaths['cp_src'] = PKG_AGIBIN;
			$cmdpaths['mv_trg'] = PKG_AGIBIN . $rs;
			break;
		}
		case 'keys':
		{
			$cmdpaths['cp_src'] = PKG_KEYS;
			$cmdpaths['mv_trg'] = PKG_KEYS . $rs;
			break;
		}
		default: $cmdpaths['status'] = 1;
	}
	return $cmdpaths;
}

/**
 * Copy files to package directory tree, link files to new target.
 *
 * @param array $arr_cmdpaths					Command paths and options plus status
 * @param string $pkg_media_root			Where media files resides on the pkg storage
 * @return integer										0 if no error, else an error code
 */
function media_move($arr_cmdpaths, $pkg_media_root)
{
	$result = 0;
	// chek if no errors selecting media
	if($arr_cmdpaths['status'] <> 0) { return 100; }
	//
	$arr_cmdpaths['mv_src'] = $arr_cmdpaths['cp_src'];
	$cp_trg = rtrim($pkg_media_root . $arr_cmdpaths['cp_src'], '/');
	// check if already done
	$real_file = readlink($arr_cmdpaths['cp_src']);
	if($real_file == $cp_trg)
	{
		// nothing to do, log event then exit
		// TO_DO: check also destination integrity?
		put_msg("Message, warning (Link already exists: {$arr_cmdpaths['cp_src']} == $cp_trg)");
		return 0;
	}
	$result = copy_recursive($arr_cmdpaths['cp_src'], $cp_trg);
	if($result <> 0)
	{
		put_msg("Error (Failed copy execution: {$arr_cmdpaths['cp_src']} -> $cp_trg)");
		// nothing will work if copy cmd failed, so exit immediately
		return $result;
	}
	// rename source file/dir
	if(!rename($arr_cmdpaths['mv_src'], $arr_cmdpaths['mv_trg']))
	{
		put_msg("Error (Failed rename: {$arr_cmdpaths['mv_src']} {$arr_cmdpaths['mv_trg']})");
		return 120;
	}
	// create a symlink to point the new file/dir
	if(!symlink($cp_trg, $arr_cmdpaths['mv_src']))
	{
		put_msg("Error (Failed symlink: $cp_trg {$arr_cmdpaths['mv_src']})");
		// rollback to old filename;
		if(!rename($arr_cmdpaths['mv_trg'], $arr_cmdpaths['mv_src']))
		{
			put_msg("Error (Failed rollback: {$arr_cmdpaths['mv_src']} {$arr_cmdpaths['mv_trg']})");
			return 121;
		}
		return 122;
	}
	return 0;	
}

/**
 * Clear all symbolic links created in media_move() restoring original pointers
 * do not delete files in the live storage media
 *
 * @param array $arr_cmdpaths					Command paths
 * @param string $pkg_media_root			Where media files resides on the pkg storage
 * @return integer										0 if no error, else an error code
 */
function media_restore($arr_cmdpaths, $pkg_media_root)
{
	$pkg_file = rtrim($pkg_media_root . $arr_cmdpaths['cp_src'], '/');
	if(is_link($arr_cmdpaths['cp_src']))
	{
		// check if point to package mirror
		if(readlink($arr_cmdpaths['cp_src']) == $pkg_file)
		{
			//remove symlink to pkg files
			$result = unlink($arr_cmdpaths['cp_src']);
			if($result)
			{
				//restore the original system file/dir (was renamed at option activation)
				$result = rename($arr_cmdpaths['mv_trg'], $arr_cmdpaths['cp_src']);
				if($result)
				{
					put_msg("Message, info (Restore: {$arr_cmdpaths['mv_trg']} -> {$arr_cmdpaths['cp_src']})");
					return 0;
				}
				put_msg("Message, error (Rename: {$arr_cmdpaths['mv_trg']} -> {$arr_cmdpaths['cp_src']})");
				return 200;			
			}
			return 210;
		}
		put_msg("Message, warning (Link not found: {$arr_cmdpaths['cp_src']})");
		return 220;
	}
	put_msg("Message, warning (Source isn't a link: {$arr_cmdpaths['cp_src']})");
	return 230;
}

/**
 * Symlinks modules found in the package modules directory or remove those symlinks
 *
 * Stop asterisk process, remount the /asterisk partition rw then symlinks any module found
 * in the package modules directory.
 * Remount /asterisk ro then restart asterisk
 * 
 * @param string $opt									Action to execute, may be 'make' to link & load modules or 'break' to unlink and reaload asterisk
 * @return integer										0 if success...
 */
function media_newmodules($opt = 'make')
{
	$result = 0;
	$dev = get_mntdev(PKG_ASTMOUNTP);
	if($dev <> 1)
	{
		if(remount($dev, PKG_ASTMOUNTP, 'rw') == 0)
		{
			put_msg("Asterisk system mount point remounted rw (data: $dev -> /" . PKG_ASTMOUNTP. ")");
			$pkg_mod_dir = PKG_MEDIAROOT . PKG_MODULES;
			$sys_mod_dir = PKG_MODULES;
			switch($opt)
			{
				case 'make':
				{	
					put_msg("Attempt to link Asterisk user modules (data: $pkg_mod_dir -> $sys_mod_dir)");
					$result = pkg_files_ml($pkg_mod_dir, $sys_mod_dir, FALSE, PKG_MVSUFFIX);
					break;
				}
				case 'break':
				{	
					put_msg("Attempt to remove links to Asterisk user modules (data: $pkg_mod_dir -> $sys_mod_dir)");
					// remove symlinks to package files;
					$result = pkg_files_rl($pkg_mod_dir, $sys_mod_dir, FALSE, PKG_MVSUFFIX);
					break;
				}				
			}
			// remount the filesystem read only
			$rm_result = remount($dev, PKG_ASTMOUNTP, 'ro');
			if($rm_result == 0)
			{
				put_msg("/asterisk system mount point remounted ro (data: $dev -> /" . PKG_ASTMOUNTP . ")");
			}
			$result |= $rm_result;
		}
		else
		{
			put_msg("Error remounting rw the /asterisk system mount point(data: $dev -> /" . PKG_ASTMOUNTP . ")");
			$result = 1;
		}
	}
	return $result;
}

function pkgopt_flagfile($what, $do = '')
{
	$flag_fname = PKG_DATAPATH . '/' . $what . '.active';
	switch($do)
	{
		case 'make':
		{
			touch($flag_fname); 
			break;
		}
		case 'break':
		{
			if(file_exists($flag_fname))
			{
				unlink($flag_fname);
			}
			break;
		}
		default:
		{
			return (file_exists($flag_fname));
		}
	}
	return $flag_fname;
}

function pkgopt_set($gui_opts = '')
{
	$result = 0;
	$write_cnf = FALSE;
	global $config;
	// get settings from gui configuration page
	$local_options = $config['system']['astmedia.pkg']['opt'];
	if(is_array($gui_opts))
	{
		$local_options = $gui_opts;
	}
	put_msg('--- array $local_options ---');
	put_msg(print_r($local_options, TRUE));
	// stop asterisk
	put_msg("Info, stopping Asterisk");
	ast_control('stop');
	// check configuration
	foreach($local_options as $op_key => $op_value)
	{
		$opt_changed = ($op_value XOR pkgopt_flagfile($op_key));
		if($opt_changed)
		{
			put_msg("Package change option in pkgopt_set (data: $op_key -> $op_value)");
			if($op_value == 1)
			{
				if($op_key == 'xmod')
				{
					$op_result = media_newmodules('make');
				}
				else
				{				
					$op_result = media_move(media_select($op_key, PKG_MVSUFFIX), PKG_MEDIAROOT);
				}
				pkgopt_flagfile($op_key, 'make');
			}
			else 
			{
				if($op_key == 'xmod')
				{
					$op_result = media_newmodules('break');
				}
				else
				{	
					$op_result |= media_restore(media_select($op_key, PKG_MVSUFFIX), PKG_MEDIAROOT);
				}
				pkgopt_flagfile($op_key, 'break');
			}
			// set flag file ad config params
			if($op_result == 0)
			{
				$config['system']['astmedia.pkg']['opt'][$op_key] = $op_value;
			}
			else
			{
				put_msg("Failure in pkgopt_set, disabling option (Opt: $op_key)");
				pkgopt_flagfile($op_key, 'break');
				$config['system']['astmedia.pkg']['opt'][$op_key] = 0;
			}
			//		
			$result |= $op_result;
			$write_cnf = TRUE;
		}
	} 
	//
	if($write_cnf)
	{
		write_config();
	}
	put_msg("Info, starting Asterisk");
	ast_control('start');
	return $result;	
}

/**
 * Apply active package options at boot time
 *
 * @return integer										0 if success
 */
function pkgopt_boot()
{
	$result = 0;
	$err_count = 0;
	$op_result == 0;
	global $config;
	// workaround for ast db. Because Asterisk isn't running at this boot stage
	// we create a dummy file to avoid breaking the astdb linking option
	if(($config['system']['astmedia.pkg']['opt']['astdb'] == 1))
	{
		touch(PKG_ASTDB);
	}
	foreach($config['system']['astmedia.pkg']['opt'] as $op_key => $op_value)
	{
		put_msg("Package change option in pkgopt_boot (data: $op_key -> $op_value)");
		if($op_value == 1)
		{
			if($op_key == 'xmod')
			{
				// nothing to activate because the /asterisk partition is persistent...
				// a file checking may be useful?
				// $op_result = media_newmodules('check');
			}
			else
			{
				$op_result = media_move(media_select($op_key, PKG_MVSUFFIX), PKG_MEDIAROOT);
			}
			if($op_result == 0)
			{
				pkgopt_flagfile($op_key, 'make');
			}
			else
			{
				put_msg("Failure in pkgopt_boot, disabling option (Opt: $op_key)");
				pkgopt_flagfile($op_key, 'break');
				$config['system']['astmedia.pkg']['opt'][$op_key] = 0;
				$err_count++;
			}
			$result |= $op_result;
		}
	}
	//
	if($err_count > 0)
	{
		write_config();
	}
	return $result;		
}

function pkg_disable()
{
	$result = 0;
	$err_count = 0;
	$pbx_restart = 0;
	global $config;
	put_msg("Info, stopping Asterisk");
	ast_control('stop');
	//
	foreach($config['system']['astmedia.pkg']['opt'] as $op_key => $op_value)
	{
		put_msg("Package deactivate (data: $op_key -> $op_value)");
		if($op_value == 1)
		{
			if($op_key == 'xmod')
			{
				$op_result = media_newmodules('break');
			}
			else
			{
				$op_result = media_restore(media_select($op_key, PKG_MVSUFFIX), PKG_MEDIAROOT);
			}
			if($op_result == 0)
			{
				pkgopt_flagfile($op_key, 'break');
			}
			else
			{
				put_msg("Failure in pkg_disable, disabling package option (Opt: $op_key)");
				put_msg("Inconsistent system state, !!Reboot suggested!!");
				$config['system']['astmedia.pkg']['opt'][$op_key] = 0;
				$err_count++;
			}
			$result |= $op_result;
		}		
	}
	if($err_count > 0)
	{
		write_config();
	}
	put_msg("Info, starting Asterisk");
	ast_control('start');		
	return $result;
}

function pkg_clean()
{
	global $config;
	$result = pkg_disable();
	unset($config['system']['astmedia.pkg']);
	write_config();
	return $result;
}

?>