#!/usr/bin/php -q

<?php

if (! function_exists("_")) {
	function _($str) {
		return $str;
	}
}

ini_set('error_reporting', E_ALL & ~E_NOTICE);

define("AMP_CONF", "/etc/amportal.conf");
$amportalconf = AMP_CONF;

//define("ASTERISK_CONF", "/etc/asterisk/asterisk.conf");
define("WARNING_BANNER", "; "._("do not edit this file, this is an auto-generated file by freepbx\n")."; "._("all modifications must be done from the web gui")."\n\n\n");

// Emulate gettext extension functions if gettext is not available
if (!function_exists('_')) {
	function _($str) {
		return $str;
	}
}

function out($text) {
	echo $text."\n";
}

function outn($text) {
	echo $text;
}

function error($text) {
	echo "[ERROR] ".$text."\n";
}

function fatal($text, $extended_text="", $type="FATAL") {
	echo "[$type] ".$text." ".$extended_text."\n";

	$nt = notifications::create($db);
	$nt->add_critical('retrieve_conf', $type, $text, $extended_text);

	exit(1);
}

function debug($text) {
	global $debug;
	
	if ($debug) echo "[DEBUG-preDB] ".$text."\n";
}

function showHelp() {
	out(_("Optional parameters:"));
	out(_("  --help, -h, -?           Show this help"));
	out(_("  --debug                  Enable debug output"));
	out(_("  --dry-run                Don't actually do anything"));
}


// bootstrap retrieve_conf by getting the AMPWEBROOT since that is currently where the necessary
// functions.inc.php resides, and then use that parser to properly parse the file and get all
// the defaults as needed.
//
function parse_amportal_conf_bootstrap($filename) {
	$file = file($filename);
	foreach ($file as $line) {
		if (preg_match("/^\s*([\w]+)\s*=\s*\"?([\w\/\:\.\*\%-]*)\"?\s*([;#].*)?/",$line,$matches)) {
			$conf[ $matches[1] ] = $matches[2];
		}
	}
	if ( !isset($conf["AMPWEBROOT"]) || ($conf["AMPWEBROOT"] == "")) {
		$conf["AMPWEBROOT"] = "/var/www/html";
	} else {
		$conf["AMPWEBROOT"] = rtrim($conf["AMPWEBROOT"],'/');
	}

	return $conf;
}

/** Adds a trailing slash to a directory, if it doesn't already have one
 */
function addslash($dir) {
	return (($dir[ strlen($dir)-1 ] == '/') ? $dir : $dir.'/');
}


/********************************************************************************************************************/

// **** Make sure we have STDIN etc

// from  ben-php dot net at efros dot com   at  php.net/install.unix.commandline
if (version_compare(phpversion(),'4.3.0','<') || !defined("STDIN")) {
	define('STDIN',fopen("php://stdin","r"));
	define('STDOUT',fopen("php://stdout","r"));
	define('STDERR',fopen("php://stderr","r"));
	register_shutdown_function( create_function( '' , 'fclose(STDIN); fclose(STDOUT); fclose(STDERR); return true;' ) );
}
   
// **** Make sure we have PEAR's DB.php, and include it

outn(_("Checking for PEAR DB.."));
if (! @ include('DB.php')) {
	out(_("FAILED"));
	fatal(_("PEAR Missing"),sprintf(_("PEAR must be installed (requires DB.php). Include path: %s "), ini_get("include_path")));
}
out(_("OK"));


// **** Make sure we have PEAR's GetOpts.php, and include it

outn(_("Checking for PEAR Console::Getopt.."));
if (! @ include("Console/Getopt.php")) {
	out(_("FAILED"));
	fatal(_("PEAR Getopt.php Missing"),sprintf(_("PEAR must be installed (requires Console/Getopt.php). Include path: %s"), ini_get("include_path")));
}
out(_("OK"));


// **** Parse out command-line options

$shortopts = "h?u:p:";
$longopts = array("help","debug","dry-run","run-install","amportalconf=");

$args = Console_Getopt::getopt(Console_Getopt::readPHPArgv(), $shortopts, $longopts);
if (is_object($args)) {
	// assume it's PEAR_ERROR
	out($args->message);
	exit(255);
}

$debug = false;
$dryrun = false;
$run_install = false;

foreach ($args[0] as $arg) {
	switch ($arg[0]) {
		case "--help": case "h": case "?":
			showHelp();
			exit(10);
		break;
		case "--dry-run":
			out(_("Dry-run only, no files will be written"));
			$dryrun = true;
		break;
		case "--debug":
			$debug = true;
			debug(_("Debug mode enabled"));
		break;
		case "--run-install":
			$run_install = true;
			out(_("Running module install.php and install.sql scripts"));
		break;
		case "--amportalconf":
			$amportalconf = $arg[1];
			out(sprintf(_("Using %s configuration file"), $amportalconf));
		break;
	}
}

// **** Check for amportal.conf

outn(sprintf(_("Checking for %s "), $amportalconf)._(".."));
if (!file_exists($amportalconf)) {
	fatal(_("amportal.conf access problem: "),sprintf(_("The %s file does not exist, or is inaccessible"), $amportalconf));
}
out(_("OK"));

// **** read amportal.conf

outn(sprintf(_("Bootstrapping %s .."), $amportalconf));
$amp_conf = parse_amportal_conf_bootstrap($amportalconf);
if (count($amp_conf) == 0) {
	fatal(_("amportal.conf parsing failure"),sprintf(_("no entries found in %s"), $amportalconf));
}
out(_("OK"));

outn(sprintf(_("Parsing %s .."), $amportalconf));
require_once($amp_conf['AMPWEBROOT']."/admin/functions.inc.php");
$amp_conf = parse_amportal_conf($amportalconf);
if (count($amp_conf) == 0) {
	fatal(_("amportal.conf parsing failure"),sprintf(_("no entries found in %s"), $amportalconf));
}
out(_("OK"));

$asterisk_conf_file = $amp_conf["ASTETCDIR"]."/asterisk.conf";
outn(sprintf(_("Parsing %s .."), $asterisk_conf_file));
$asterisk_conf = parse_asterisk_conf($asterisk_conf_file);
if (count($asterisk_conf) == 0) {
	fatal(_("asterisk.conf parsing failure"),sprintf(_("no entries found in %s"), $asterisk_conf_file));
}
out(_("OK"));

// **** Connect to database

outn(_("Connecting to database.."));

# the engine to be used for the SQL queries,
# if none supplied, backfall to mysql
$db_engine = "mysql";
if (isset($amp_conf["AMPDBENGINE"])){
	$db_engine = $amp_conf["AMPDBENGINE"];
}

// Define the notification class for logging to the dashboard
//
$nt = notifications::create($db);

// **** Create symlinks array
$symlink_dirs = array();
$symlink_dirs['sounds'] = $amp_conf['ASTVARLIBDIR'].'/sounds';
$symlink_dirs['bin']    = $amp_conf['AMPBIN'];
$symlink_dirs['etc']    = $amp_conf['ASTETCDIR'];
$symlink_dirs['images'] = $amp_conf['AMPWEBROOT']."/admin/images"; 

$cp_errors = "";
$cp_dirs = array();
$cp_dirs['agi-bin'] = $amp_conf['ASTAGIDIR'];

switch ($db_engine)
{
	case "pgsql":
	case "mysql":
		/* datasource in in this style:
		dbengine://username:password@host/database */
	
		$db_user = $amp_conf["AMPDBUSER"];
		$db_pass = $amp_conf["AMPDBPASS"];
		$db_host = $amp_conf["AMPDBHOST"];
		$db_name = $amp_conf["AMPDBNAME"];
	
		$datasource = $db_engine.'://'.$db_user.':'.$db_pass.'@'.$db_host.'/'.$db_name;
		$db = DB::connect($datasource); // attempt connection
		break;
	
	case "sqlite":
		require_once('DB/sqlite.php');
	
		if (!isset($amp_conf["AMPDBFILE"]))
			fatal(_("AMPDBFILE not setup properly"),sprintf(_("You must setup properly AMPDBFILE in %s "), $amportalconf));
	
		if (isset($amp_conf["AMPDBFILE"]) == "")
			fatal(_("AMPDBFILE not setup properly"),sprintf(_("AMPDBFILE in %s cannot be blank"), $amportalconf));
	
		$DSN = array (
			"database" => $amp_conf["AMPDBFILE"],
			"mode" => 0666
		);
	
		$db = new DB_sqlite();
		$db->connect( $DSN );
		break;
	
	case "sqlite3":
		if (!isset($amp_conf["AMPDBFILE"]))
			fatal("You must setup properly AMPDBFILE in $amportalconf");
			
		if (isset($amp_conf["AMPDBFILE"]) == "")
			fatal("AMPDBFILE in $amportalconf cannot be blank");

		require_once('DB/sqlite3.php');
		$datasource = "sqlite3:///" . $amp_conf["AMPDBFILE"] . "?mode=0666";
		$db = DB::connect($datasource);
		break;

	default:
		fatal( "Unknown SQL engine: [$db_engine]");
}

if(DB::isError($db)) {
	out(_("FAILED"));
	debug($db->userinfo);
	fatal(_("database connection failure"),("failed trying to connect to the configured database"));
	
}
out(_("OK"));


//TODO : make this engine-neutral
outn(_("Connecting to Asterisk manager interface.."));
// connect to asterisk manager
require_once($amp_conf['AMPWEBROOT'].'/admin/common/php-asmanager.php');
$astman = new AGI_AsteriskManager(); 
if (! $res = $astman->connect("127.0.0.1:".$amp_conf["ASTMANAGERPORT"], $amp_conf["AMPMGRUSER"] , $amp_conf["AMPMGRPASS"])) {
	out(_("FAILED"));
	fatal(_("Asterisk Manager Connection Failure"),sprintf(_("Failed to connect to the Asterisk manager through port: %s"), $amp_conf['ASTMANAGERPORT']));
}
out(_("OK"));

//include common functions
require_once($amp_conf['AMPWEBROOT']."/admin/extensions.class.php");
freepbx_log("retrieve_conf", "devel-debug", "Started retrieve_conf, DB Connection OK");

// query for our modules
// var_dump( $db );
$modules = module_getinfo();

//Putting the core module last, to move outbound-allroutes 
// last in from-internals-additional
if (array_key_exists('core', $modules)) {
        $core_tmp = $modules['core'];
        unset($modules['core']);
        $modules['core'] = $core_tmp;
}

// include any module global functions
if(is_array($modules)){
	foreach($modules as $key => $module) {
		//only use this module if it's enabled (status=2)
		if (isset($module['status']) && $module['status'] == MODULE_STATUS_ENABLED) {
			// Make sure the module is installed and up to date
			if ($run_install) module_install($key);
			// active_modules array used in genConf function
			$active_modules[] = $key;
			//include module functions
			if (is_file($amp_conf['AMPWEBROOT']."/admin/modules/{$key}/functions.inc.php")) {
				freepbx_log('retrieve_conf', 'devel-debug', 'Including '.$amp_conf['AMPWEBROOT']."/admin/modules/{$key}/functions.inc.php");
				include_once($amp_conf['AMPWEBROOT']."/admin/modules/{$key}/functions.inc.php");
				freepbx_log('retrieve_conf', 'devel-debug', $amp_conf['AMPWEBROOT']."/admin/modules/{$key}/functions.inc.php processed OK");
			}

			// create symlinks for files in appropriate sub directories
			symlink_subdirs( $amp_conf['AMPWEBROOT'].'/admin/modules/'.$key );
			cp_subdirs( $amp_conf['AMPWEBROOT'].'/admin/modules/'.$key );
		}
	}
}

// create an object of the extensions class
require_once($amp_conf['AMPWEBROOT']."/admin/extensions.class.php");
$ext = new extensions;

// create objects for any module classes
// currently only 1 class can be declared per module, not sure if that will be an issue
if(isset($active_modules) && is_array($active_modules)){
	foreach($active_modules as $active_module) { 
		freepbx_log('retrieve_conf', 'devel-debug', "Creating ".$active_module."_conf class");
		$classname = $active_module."_conf";
		if(class_exists($classname)) {
			${$classname} = new $classname;
		}
	}
}


$engineinfo = engine_getinfo();
if($engineinfo['version'] == 0){
	freepbx_log('retrieve_conf', 'fatal', "Failed to get engine information (engine_getinfo: {$engineinfo['engine']})");
	fatal(_("Failed to get engine_info"),_("retreive_conf failed to get engine information and cannot configure up a softwitch with out it. Error: {$engineinfo['engine']}"));
}
// was setting these variables before, assume we still need them
$engine = $engineinfo['engine'];
$version = $engineinfo['version'];

// run all of the *_get_config and _hookGet_config functions, which will populate the appropriate objects
if(isset($active_modules) && is_array($active_modules)){
	foreach($active_modules as $module) {
		$funcname = $module."_get_config";
		if (function_exists($funcname)) { 
			freepbx_log('retrieve_conf', 'devel-debug', 'Calling '.$funcname.'()');
			$funcname($engine);
		}
	}
	foreach($active_modules as $module) {
		$funcname = $module."_hookGet_config";
		if (function_exists($funcname)) { 
			freepbx_log('retrieve_conf', 'devel-debug', 'Calling '.$funcname.'()');
			$funcname($engine);
		}
	}
}

// extensions_additional.conf
// create the from-internal-additional context so other can add to it
$ext->add('from-internal-additional', 'h', '', new ext_hangup(''));
//echo $ext->get_filename();
//echo $ext->generateConf();
write_file($ext->get_filename(),$ext->generateConf());

// now we write out our conf files for modules
// check for any objects for each of the active modules
// ** conferences is an example of a module that write a conf
if(isset($active_modules) && is_array($active_modules)){
	foreach($active_modules as $active_module) { 
		$classname = $active_module."_conf";
		if(class_exists($classname) && get_class(${$classname}) !== false) {
			//echo ${$classname}->get_filename();
			//echo ${$classname}->generateConf();
			
			// if the module returns an array, it wants to write multiple files
			// ** pinsets is an example of a module that does this
			if (is_array(${$classname}->get_filename())) {
				foreach(${$classname}->get_filename() as $modconf) {
					freepbx_log('retrieve_conf', 'devel-debug', 'generateConf from '.$classname.'->'.$modconf.'');
					write_file($modconf,${$classname}->generateConf($modconf));
				}
			} else {
				freepbx_log('retrieve_conf', 'devel-debug', 'generateConf from '.$classname);
				write_file(${$classname}->get_filename(),${$classname}->generateConf());
			}
		}
	}
}


function write_file($filename,$contents) {
	global $asterisk_conf;
	freepbx_log('retrieve_conf', 'devel-debug', 'Writing '.$filename);
	if (isset($filename) && !empty($filename)) {
		if ($fd = fopen(addslash($asterisk_conf['astetcdir']).$filename, "w")) {
			fwrite($fd, WARNING_BANNER );
			fwrite($fd, $contents);
			fclose($fd);
		}
	}
}

function symlink_subdirs($moduledir) {
	global $symlink_dirs;
	$symlink_errors = false;

	$nt = notifications::create($db);

	foreach ($symlink_dirs as $subdir => $targetdir) {
		$dir = addslash($moduledir).$subdir;
		if (is_dir($dir)) {
			$d = opendir($dir);
			while ($file = readdir($d)) {
				if ($file[0] != '.') {
					$src = addslash($dir).$file;
					$dest = addslash($targetdir).$file;
					if (file_exists($dest)) {
						if (!is_link($dest)) {
							freepbx_log('retrieve-conf', 'error', $dest.' already exists, and is not a symlink!');
							$nt->add_error('retrieve_conf', 'SYMLINK', _("symlink from modules failed"), sprintf(_("retrieve_conf failed to sym link the %s file from modules"),$dest));
							$symlink_errors = true;
						} else if (readlink($dest) != $src) {
							// TODO : is this the proper handling? should we just overwrite..?
							freepbx_log('retrieve-conf', 'error', $dest.' already exists, and is linked to something else!');
							$nt->add_error('retrieve_conf', 'SYMLINK', _("symlink from modules failed"), sprintf(_("retrieve_conf failed to sym link the %s file from modules"),$dest));
							$symlink_errors = true;
						} else {
							freepbx_log('retrieve-conf', 'devel-debug', $dest.' already points to '.$src.' - OK');
						}
					} else {
//						// symlink, unlike copy, doesn't overwrite - have to delete first
//						if (is_link($dest) || file_exists($dest)) {
//							unlink($dest);
//						}
						if (symlink($src, $dest)) {
							freepbx_log('retrieve-conf', 'devel-debug', 'Symlinked '.$src.' to '.$dest);
						} else {
							freepbx_log('retreive-conf', 'devel-debug', 'Cannot symlink '.$src.' to '.$dest.'. Check Permissions?');
						}
					}
				}
			}
			closedir($d);
		}
	}
	if (!$symlink_errors) {
		$nt->delete('retrieve_conf', 'SYMLINK');
	}
}

// wrap copy with error handler
//
function err_copy($source, $dest) {
	$ret = false;
	set_error_handler("report_errors");
	if (copy($source, $dest)) {
		$ret = chmod($dest,'0754');
	}
	restore_error_handler();
	return $ret;
}

// wrap unlink with error handler
//
function err_unlink($dest) {
	set_error_handler("report_errors");
	$ret = unlink($dest);
	restore_error_handler();
	return $ret;
}

function cp_subdirs($moduledir) {
	global $cp_errors;
	global $cp_dirs;

	$cp_errors = "";
	foreach ($cp_dirs as $subdir => $targetdir) {
		$dir = addslash($moduledir).$subdir;
		if (is_dir($dir)) {
			$d = opendir($dir);
			while ($file = readdir($d)) {
				if ($file[0] != '.') {
					$sourcefile = addslash($dir).$file;
					$targetfile = addslash($targetdir).$file;

					if (file_exists($targetfile)) {
						if (is_link($targetfile)) {
							if (err_unlink($targetfile)) {
								freepbx_log('retrieve-conf', 'devel-debug', "$targetfile was symbolic link, unlink successful");
							} else {
								freepbx_log('retrieve-conf', 'error', "$targetfile is a symblolic link, failed to unlink!");
								break;
							}
						}
					}
					// OK, now either the file is a regular file or isn't there, so proceed
					//
					if (err_copy($sourcefile,$targetfile)) {
						freepbx_log('retrieve-conf', 'devel-debug', "$targetfile successfully copied");
						// copy was successful, make sure it has execute permissions
						chmod($targetfile,0754);
					} else {
						freepbx_log('retrieve-conf', 'error', "$targetfile failed to copy from module directory");
					}
				}
			}
			closedir($d);
		}
	}
	$nt = notifications::create($db);
	if ($cp_errors) {
		$nt->add_error('retrieve_conf', 'CPAGIBIN', _("Failed to copy from module agi-bin"), sprintf(_("Retrieve conf failed to copy file(s) from a module's agi-bin dir: %s"),$cp_errors));
	} else {
		$nt->delete('retrieve_conf', 'CPAGIBIN');
	}
}

function report_errors($errno, $errstr, $errfile, $errline) {
	global $cp_errors;
	freepbx_log('retrieve-conf', 'error', "php reported: '".mysql_real_escape_string($errstr)."' after copy or unlink attempt!");
	$cp_errors .= $errstr."\n";
}

/** Check if there is a job running, if one is found then all is good, if one is not found, it will be added and a
 *  notification will be sent.
 */
function install_cron_scheduler() {
	global $amp_conf;
	global $nt;

	// crontab appears to return an error when no entries, os only fail if error returned AND a list of entries.
	// Don't know if this will ever happen, but a failure and a list could indicate something wrong.
	//
	exec("/usr/bin/crontab -l", $outlines, $ret);
	if ($ret && count($outlines)) {
		$nt->add_error('retrieve_conf', 'CRONMGR', _("Failed to check crontab for cron manager"), sprintf(_("crontab returned %s error code when checking for crontab entries to start freepbx-cron-scheduler.php crontab manager"),$ret));
	} else {
		$nt->delete('retrieve_conf', 'CRONMGR');
		$outlines2 = preg_grep("/freepbx-cron-scheduler.php/",$outlines);
		$cnt = count($outlines2);
		switch ($cnt) {
			case 0:
				/** grab any other cronjobs that are running as asterisk and NOT associated with backups
 				*  this code was taken from the backup module for the most part. But seems to work...
 				*/
				$outlines = array();
				exec("/usr/bin/crontab -l | grep -v ^#\ DO\ NOT | grep -v ^#\ \( |  grep -v freepbx-cron-scheduler.php", $outlines, $ret);
				$crontab_entry = "";
				foreach ($outlines as $line) {
					$crontab_entry .= $line."\n";
				}
				// schedule to run hourly, at a random time. The random time is explicit to accomodate things like module_admin online update checking
				// since we will want a random access to the server. In the case of module_admin, that will also be scheduled randomly within the hour
				// that it is to run
				//
				$crontab_entry .= rand(0,59)." * * * * ".$amp_conf['AMPBIN']."/freepbx-cron-scheduler.php";
				system("/bin/echo '$crontab_entry' | /usr/bin/crontab -");
				break;
			case 1:
				// already running, nothing to do
				break;
			default:
				// error, there should never be more than one running
				echo "TODO: deal with error here\n";
				$nt->add_error('retrieve_conf', 'CRONMGR', _("Multiple freepbx-cron-scheduler.php running"), sprintf(_("There were %s freepbx-cron-scheduler.php instances running. There should be only 1."),$cnt));
		}
	}
}

include("libfreepbx.confgen.php");

// run legacy retrieve scripts
// TODO these legacy retrieve scripts should be obsoleted, in favor of classes like extensions class

//script to write op_server.cfg file from mysql 
$script = $amp_conf['AMPBIN'].'/retrieve_op_conf_from_mysql.pl '.$amportalconf.' '.rtrim($asterisk_conf['astetcdir'],DIRECTORY_SEPARATOR);
exec($script);

// generate configuration files
//
generate_configurations_sip($version);
generate_configurations_iax($version);
generate_configurations_zap($version);
generate_configurations_queues($version);
	
// Check and install the freepbx-cron-scheduler.php manager
//
install_cron_scheduler();

// **** Set reload flag for AMP admin
needreload();
if (isset($amp_conf["AMPWEBADDRESS"]) && $amp_conf["AMPWEBADDRESS"])
{
	out(sprintf(_("Please update your modules and reload Asterisk by visiting %s"), "http://".$amp_conf["AMPWEBADDRESS"]."/admin"));
}
else
{
	out(_("Please update your modules and reload Asterisk by browsing to your server."));
}
	$nt->delete('retrieve_conf', 'FATAL');
?>
