#!/usr/bin/php -q

<?php 
/** Command-line Module Administration script
 * (c) 2006 Greg MacLellan
 * 
 * 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; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

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

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

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

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

function fatal($text) {
	echo "[FATAL] ".$text."\n";
	exit(1);
}

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


function install_parse_amportal_conf($filename) {
	$file = file($filename);
	foreach ($file as $line) {
		if (preg_match("/^\s*([a-zA-Z0-9]+)\s*=\s*(.*)\s*([;#].*)?/",$line,$matches)) { 
			$conf[ $matches[1] ] = $matches[2];
		}
	}
	return $conf;
}

function init_amportal_environment($ampconfpath) {
	global $amp_conf, $asterisk_conf, $db, $astman;
	global $amp_conf_defaults;
	
	if (!file_exists($ampconfpath)) {
		fatal('Cannot find conf file: '.$ampconfpath);
	}
	
	// primitive parse function, just to grab AMPWEBROOT
	$inst_amp_conf = install_parse_amportal_conf(AMP_CONF);
	
	define('AMP_BASE_INCLUDE_PATH', $inst_amp_conf['AMPWEBROOT'].'/admin');
	
	if (!file_exists(AMP_BASE_INCLUDE_PATH.'/functions.inc.php')) {
		fatal('Cannot locate '.AMP_BASE_INCLUDE_PATH.'/functions.inc.php');
	}	
	// include the functions file from WEBROOT
	include(AMP_BASE_INCLUDE_PATH.'/functions.inc.php');
	// include astman api
	include(AMP_BASE_INCLUDE_PATH.'/common/php-asmanager.php');
	
	// now apply the real parse function (this makes some default assumptions and does a bit more error checking)
	$amp_conf = parse_amportal_conf($ampconfpath);
	
	$asterisk_conf = parse_asterisk_conf($amp_conf['ASTETCDIR']);

	// connect to database
	if (!file_exists(AMP_BASE_INCLUDE_PATH.'/common/db_connect.php')) {
		fatal('Cannot locate '.AMP_BASE_INCLUDE_PATH.'/common/db_connect.php');
	}
	require_once(AMP_BASE_INCLUDE_PATH.'/common/db_connect.php'); //PEAR must be installed

	$astman= new AGI_AsteriskManager();
	if (! $res = $astman->connect("127.0.0.1", $amp_conf["AMPMGRUSER"] , $amp_conf["AMPMGRPASS"])) {
		unset( $astman );
	}
}

function doReload() {
	$result = do_reload();

	if ($result['status'] != true) {
		out("Error(s) have occured, the following is the retrieve_conf output:");
		$retrieve_array = explode('<br/>',$result['retrieve_conf']);
		foreach ($retrieve_array as $line) {
			out($line);
		};
	} else {
		out($result['message']);
	}
}

function doDisable($modulename, $force) {
	getIncludes();
	if (is_array($errors = module_disable($modulename, $force))) {
		out("The following error(s) occured:");
		out(' - '.implode("\n - ",$errors));
		exit(2);
	} else {
		out("Module ".$modulename." successfully disabled");
	}
}

function doEnable($modulename, $force) {
	getIncludes();
	if (is_array($errors = module_enable($modulename, $force))) {
		out("The following error(s) occured:");
		out(' - '.implode("\n - ",$errors));
		exit(2);
	} else {
		out("Module ".$modulename." successfully enabled");
	}
}

function doInstall($modulename, $force) {
	getIncludes();
	if (is_array($errors = module_install($modulename, $force))) {
		out("The following error(s) occured:");
		out(' - '.implode("\n - ",$errors));
		exit(2);
	} else {
		out("Module ".$modulename." successfully installed");
	}
}

function doDelete($modulename, $force) {
	getIncludes();
	if (is_array($errors = module_delete($modulename, $force))) {
		out("The following error(s) occured:");
		out(' - '.implode("\n - ",$errors));
		exit(2);
	} else {
		out("Module ".$modulename." successfully deleted");
	}
}

function doDeleteAll($force=true) {
	getIncludes(); //get functions from other modules, in case we need them here
	$modules = module_getinfo(false, false, true);
	unset($modules['builtin']);//builtin never gets deleted, so remove it from the list
	$temp = array();
	if (isset($modules['core'])){ //move core to the end
		$temp['core']=$modules['core'];
		unset($modules['core']); 
		$modules['core']=$temp['core'];
	}
	unset($temp);
		foreach ($modules as $module){
			if (is_array($errors = module_delete($module['rawname'], true))) {
				out("The following error(s) occured:");
				out(' - '.implode("\n - ",$errors));
				//exit(2);
			} else {
				out("Module ".$module['rawname']." successfully deleted");
			}	
	}
		out('All modules successfully removed');
}

function doDownload($modulename, $force) {

	global $modulexml_path;
	global $modulerepository_path;

	if (is_array($errors = module_download($modulename, $force, 'download_progress', $modulerepository_path, $modulexml_path))) {
		out("The following error(s) occured:");
		out(' - '.implode("\n - ",$errors));
		exit(2);
	} else {
		out("Module ".$modulename." successfully downloaded");
	}
}


function download_progress($action, $params) {
	switch ($action) {
		case 'untar':
			outn("\nUntaring..");
		break;
		case 'downloading':
			outn("\rDownloading ".$params['read'].' of '.$params['total'].' ('.($params['total'] ? round($params['read']/$params['total']*100) : '0').'%)            ');
			if ($params['read'] == $params['total']) {
				out('');
			}
		break;
		case 'done';
			out('Done');
		break;
	}
}

function doUninstall($modulename, $force) {
	getIncludes();
	if (is_array($errors = module_uninstall($modulename, $force))) {
		out("The following error(s) occured:");
		out(' - '.implode("\n - ",$errors));
		exit(2);
	} else {
		out("Module ".$modulename." successfully uninstalled");
	}
}

function doUpgrade($modulename, $force) {
	// either will exit() if there's a problem
	doDownload($modulename, $force);
	doInstall($modulename, $force);
}

function doInstallAll($force) {
	doUpgradeAll(true);
	$modules = getInstallableModules();
	if (in_array('core', $modules)){
		out("Installing core...");
		doDownload('core', $force);
		doInstall('core', $force);
	}
	if (count($modules) > 0) {
		out("Installing: ".implode(', ',$modules));
		sleep(1); // a bit of time to ^C abort..
		foreach ($modules as $module => $name) {
			if (($name != 'core')){//we dont want to reinstall core
				getIncludes(); //get functions from other modules, in case we need them here
				out("Installing $name...");
				doDownload($name, $force);
				doInstall($name, $force);
			}
		}	
		out("Done. All modules installed.");
	} else {
		out("All modules up to date.");
	}
}

function getIncludes(){
	$active_modules = module_getinfo(false, MODULE_STATUS_ENABLED);
	if(is_array($active_modules)){
		foreach($active_modules as $key => $module) {
			//include module functions
			if (is_file("modules/{$key}/functions.inc.php")) {
				require_once("modules/{$key}/functions.inc.php");
			}
		}
	}
}

/** 
 * @param bool Controls if a simple (names only) or extended (array of name,versions) array is returned
 */
function getInstallableModules($extarray = false) {
	$modules_online = module_getonlinexml();
	$module_info=module_getinfo(false);
	$modules_installable = array();

	foreach ($modules_online as $name) {
		if ((!isset($module_info[$name['rawname']]['status'])) || ($module_info[$name['rawname']]['status'] == MODULE_STATUS_NEEDUPGRADE) || ($module_info[$name['rawname']]['status'] == MODULE_STATUS_NOTINSTALLED)){
			$modules_installable[]=$name['rawname'];
		}
	}
	return $modules_installable;
}

/** 
 * @param bool Controls if a simple (names only) or extended (array of name,versions) array is returned
 */
function getUpgradableModules($extarray = false) {
	$modules_local = module_getinfo(false, MODULE_STATUS_ENABLED);
	$modules_online = module_getonlinexml();
	$modules_upgradable = array();
	
	foreach (array_keys($modules_local) as $name) {
		if (isset($modules_online[$name])) {
			if (version_compare_freepbx($modules_local[$name]['version'], $modules_online[$name]['version']) < 0) {
				if ($extarray) {
					$modules_upgradable[] = array(
						'name' => $name,
						'local_version' => $modules_local[$name]['version'],
						'online_version' => $modules_online[$name]['version'],
					);
				} else {
					$modules_upgradable[] = $name;
				}
			}
		}
	}
	return $modules_upgradable;
}

function doUpgradeAll($force) {
	$modules = getUpgradableModules();
	if (count($modules) > 0) {
		out("Upgrading: ".implode(', ',$modules));
		sleep(1); // a bit of time to ^C abort..
		foreach ($modules as $modulename) {
			out("Upgrading $modulename..");
			doUpgrade($modulename, $force);
		}
		out("All upgrades done!");
	} else {
		out("Up to date.");
	}
}

function mirrorrepo(){
	doInstallAll(true);
	$modules_online = module_getonlinexml();
	$modules_local = module_getinfo();
	unset($modules_local['builtin']); //builtin never gets deleted, so remove it from the list
	foreach ($modules_local as $localmod){
		if (!module_getonlinexml($localmod['rawname'])){
			doDelete($localmod['rawname'],1);
		}
	}
}

function showi18n($modulename) {
	$modules = module_getinfo($modulename);
	if (!isset($modules[$modulename])) {
		fatal($modulename.' not found');
	}
	if (isset($modules[$modulename]['name'])) {
		echo "# name:\n_(\"".$modules[$modulename]['name']."\");\n";
	}
	if (isset($modules[$modulename]['category'])) {
		echo "# category:\n_(\"".$modules[$modulename]['category']."\");\n";
	}
	if (isset($modules[$modulename]['description'])) {
		echo "# description:\n_(\"".trim(str_replace("\n","",$modules[$modulename]['description']))."\");\n";
	}
	if (isset($modules[$modulename]['menuitems'])) {
		foreach ($modules[$modulename]['menuitems'] as $key => $menuitem) {
			echo "# $key:\n_(\"$menuitem\");\n";
		}
	}
}

function showCheckDepends($modulename) {
	$modules = module_getinfo($modulename);
	if (!isset($modules[$modulename])) {
		fatal($modulename.' not found');
	}
	if (($errors = module_checkdepends($modules[$modulename])) !== true) {
		out("The following dependencies are not met:");
		out(' - '.implode("\n - ",$errors));
		exit(1);
	} else {
		out("All dependencies met for module ".$modulename);
	}
}

function showEngine() {
	$engine = engine_getinfo();
	foreach ($engine as $key=>$value) {
		out(str_pad($key,15,' ',STR_PAD_LEFT).': '.$value);
	}
}

function showHelp() {
	global $argv;
	out("USAGE:");
	out("  ".$argv[0]." [params] <operation> <module> [parameters.. ] ");
	out("PARAMETERS: ");
	out("  -f  Force operation (skips dependency and status checks)");
	out("      WARNING: Use at your own risk, modules have dependencies for a reason!");
	out("OPERATIONS:");
	out("  checkdepends <module>");
	out("      Check if module meets all dependencies");
	out("  delete <module>");
	out("      Disable, uninstall, and delete the specified module");
	out("  deleteall");
	out("      Disable, uninstall, and delete ALL MODULES");
	out("      WARNING: Use at your own risk, this will remove ALL MODULES from your system!");
	out("  disable <module>");
	out("      Disable the specified module");
	out("  download <module>");
	out("      Download the module from the website");
	out("      If -f is used, downloads even if there is already a copy.");
	out("  enable <module>");
	out("      Enable the specified module");
	out("  info <module>");
	out("      Get information about a given module");
	out("  i18n <module>");
	out("      print out i18n required text for the given module");
	out("  install <module>");
	out("      Install the module (must exist in the modules directory)");
	out("  installall");
	out("      Installs all module that exist in the repository");
	out("  list");
	out("      List all local modules and their current status");
	out("  listonline");
	out("      List all local and repository modules and their current status");
	out("  reload");
	out("      Reload the configuration (same as pressing the reload bar)");
	out("  reversedepends <module>");
	out("      Show all modules that depend on this one");
	out("  showupgrades");
	out("      Show a list of upgradable modules");
	out("  showannounce");
	out("      Shows any annoucements that maybe displayed at freepbx.org for this version");
	out("  uninstall <module>");
	out("      Disable and uninstall the specified module");
	out("  upgrade <module>");
	out("      Equivalent to running download and install");
	out("  upgradeall");
	out("      Downloads and upgrades all modules with pending updates");
	
	out("  --help, -h, -?           Show this help");
}

function showInfo($modulename) {
	function recursive_print($array, $parentkey = '', $level=0) {
		foreach ($array as $key => $value) {
			if (is_array($value)) {
				// check if there is a numeric key in the sub-array, if so, we don't print the title
				if (!isset($value[0])) {
					out(str_pad($key,15+($level * 3),' ',STR_PAD_LEFT).': ');
				}
				recursive_print($value, $key, $level + 1);
			} else {
				if (is_numeric($key)) {
					// its just multiple parent keys, so we don't indent, and print the parentkey instead
					out(str_pad($parentkey,15+(($level-1) * 3),' ',STR_PAD_LEFT).': '.$value);
				} else {
					if ($key == 'status') {
						switch ($value) {
							case MODULE_STATUS_NOTINSTALLED: $value = 'Not Installed'; break;
							case MODULE_STATUS_NEEDUPGRADE: $value = 'Disabled; Needs Upgrade'; break;
							case MODULE_STATUS_ENABLED: $value = 'Enabled'; break;
							case MODULE_STATUS_DISABLED: $value = 'Disabled'; break;
							case MODULE_STATUS_BROKEN: $value = 'Broken'; break;
						}
					}
					out(str_pad($key,15+($level * 3),' ',STR_PAD_LEFT).': '.$value);
				}
			}
		}
	}
	$modules = module_getinfo($modulename);
	if (!isset($modules[$modulename])) {
		fatal($modulename.' not found');
	}
	
	recursive_print($modules[$modulename]);
	
}

function showList($online = false) {
  $repo = "http://mirror.freepbx.org/";
	$modules_local = module_getinfo(false,false,true);
	$modules = $modules_local;

	if ($online) {
    $has_extended_modules = false;
    foreach ($modules as $rawname => $attribs) {
      if ($attribs['category'] == 'Third Party Addon') {
        $repo .= "extended-";
        break;
      }
    }
		$modules_online = module_getonlinexml(false, $repo);
		if (isset($modules_online)) {
			$modules += $modules_online;
		}
	}
	ksort($modules);
	
	outn(str_pad("Module", 20));
	outn(str_pad("Version", 18));
	out("Status");
	
	outn(str_repeat('-', 19).' ');
	outn(str_repeat('-', 17).' ');
	out(str_repeat('-', 19).' ');
	
	foreach (array_keys($modules) as $name) {
		
		$status_index = isset($modules[$name]['status'])?$modules[$name]['status']:'';
		switch ($status_index) {
			case MODULE_STATUS_NOTINSTALLED:
				if (isset($modules_local[$name])) {
					$status = 'Not Installed (Locally available)';
				} else {
					$status = 'Not Installed (Available online: '.$modules_online[$name]['version'].')';
				}
			break;
			case MODULE_STATUS_DISABLED:
				$status = 'Disabled';
			break;
			case MODULE_STATUS_NEEDUPGRADE:
				$status = 'Disabled; Pending upgrade to '.$modules[$name]['version'];
			break;
			case MODULE_STATUS_BROKEN:
				$status = 'Broken';
			break;
			default:
				// check for online upgrade
				if (isset($modules_online[$name]['version'])) {
					$vercomp = version_compare_freepbx($modules[$name]['version'], $modules_online[$name]['version']);
					if ($vercomp < 0) {
						$status = 'Online upgrade available ('.$modules_online[$name]['version'].')';
					} else if ($vercomp > 0) {
						$status = 'Newer than online version ('.$modules_online[$name]['version'].')';
					} else {
						$status = 'Enabled and up to date';
					}
				} else if (isset($modules_online)) {
					// we're connected to online, but didn't find this module
					$status = 'Enabled; Not available online';
				} else {
					$status = 'Enabled';
				}
			break;
		}
		
		outn(str_pad($name, 20));
		$module_version = isset($modules[$name]['dbversion'])?$modules[$name]['dbversion']:'';
		outn(str_pad($module_version, 18));
		out($status);
	}
}

function showReverseDepends($modulename) {
	$modules = module_getinfo($modulename);
	if (!isset($modules[$modulename])) {
		fatal($modulename.' not found');
	}
	
	if (($depmods = module_reversedepends($modulename)) !== false) {
		out("The following modules depend on this one: ".implode(', ',$depmods));
		exit(1);
	} else {
		out("No enabled modules depend on this module.");
	}
}

function showUpgrades() {
	$modules = getUpgradableModules(true);
	if (count($modules) > 0) {
		out("Upgradable: ");
		foreach ($modules as $mod) {
			outn('   ');
			out($mod['name'].' '.$mod['local_version'].' -> '.$mod['online_version']);
		}
	} else {
		out("Up to date.");
	}
}
/****************************************************************************************************
 ****************************************************************************************************/

// 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
if (! @ include('DB.php')) {
	fatal("PEAR must be installed (requires DB.php). Include path: ".ini_get("include_path"));
}

// **** Make sure we have PEAR's GetOpts.php, and include it
if (! @ include("Console/Getopt.php")) {
	fatal("PEAR must be installed (requires Console/Getopt.php). Include path: ".ini_get("include_path"));
}

// **** Parse out command-line options
$shortopts = "h?fc:x:r:";
$longopts = array("help","debug","no-warnings","config=","modulexml-path=","modulerepository-path=");

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

$ampconfpath = AMP_CONF;

// force operations
$force = false;
$no_warnings = false;

// override xml and svn repository paths from hardcoded and amportal.conf defaults
// currently only implemented for downloading (to facilitate automatic installations)
//
$modulexml_path = false;
$modulerepository_path = false;

foreach ($args[0] as $arg) {
	switch ($arg[0]) {
		case "--help": case "h": case "?":
			showHelp();
			exit(10);
		break;
		case 'f': 
			$force = true;
		break;
		case '--no-warnings': 
			$no_warnings = true;
		break;
		case "--config": case "c":
			$ampconfpath = $arg[1];
		break;
		case "--modulexml-path": case "x":
			$modulexml_path = rtrim($arg[1],DIRECTORY_SEPARATOR)."/";
		break;
		case "--modulerepository-path": case "r":
			$modulerepository_path = rtrim($arg[1],DIRECTORY_SEPARATOR)."/";
		break;
	}
}

// create $amp_conf, $db, etc
init_amportal_environment($ampconfpath);

require_once( $amp_conf["AMPWEBROOT"].'/admin/common/php-asmanager.php' );
$astman = new AGI_AsteriskManager();
if (! $res = $astman->connect("127.0.0.1", $amp_conf["AMPMGRUSER"] , $amp_conf["AMPMGRPASS"])) {
	unset( $astman );
}

$operation = $args[1][0];
$param = isset($args[1][1]) ? $args[1][1] : null;;

if (!isset($argv[1])) {
	showhelp();
	exit(10);
}

if ($force && !$no_warnings) {
	out('WARNING: "force" is enabled, it is possible to create problems');
}

// freepbx modules will include the unsintall scripts
// using relative paths. why not modifiying the include dir...?
// fix for ticket:1731
chdir ( $amp_conf["AMPWEBROOT"] . "/admin/" );


switch ($operation ) {
	case 'checkdepends':
		if (empty($param)) {
			fatal("Missing module name");
		}
		showCheckDepends($param);
	break;
	case 'delete':
		if (empty($param)) {
			fatal("Missing module name");
		}
		doDelete($param, $force);
	break;
	case 'deleteall': case 'removeall':
		doDeleteAll();
	break;
	case 'disable':
		if (empty($param)) {
			fatal("Missing module name");
		}
		doDisable($param, $force);
	break;
	case 'download':
		$announcements = module_get_annoucements();
		if (empty($param)) {
			fatal("Missing module name");
		}
		doDownload($param, $force);
	break;
	case 'enable':
		if (empty($param)) {
			fatal("Missing module name");
		}
		doEnable($param, $force);
	break;
	case 'i18n':
		if (empty($param)) {
			fatal("Missing module name");
		}
		showi18n($param);
	break;	
	case 'info':
		if (empty($param)) {
			fatal("Missing module name");
		}
		showInfo($param);
	break;
	case 'install':
		if (empty($param)) {
			fatal("Missing module name");
		}
		doInstall($param, $force);
	break;
	case 'installall':
		doInstallAll(true);
	break;	
	case 'list':
		showList();
	break;
	case 'listonline':
		$announcements = module_get_annoucements();
		showList(true);
	break;
	case 'mirrorrepo': case 'mirrorrepro':
		mirrorrepo();
	break;
	case 'reload':
		doReload();
	break;
	case 'reversedepends':
		if (empty($param)) {
			fatal("Missing module name");
		}
		showReverseDepends($param);
	break;
	case 'showannounce';
		$announcements = module_get_annoucements();
		if (trim($announcements) == "") {
			echo "No Annoucements Available\n";
		} else {
			echo "The following Annoucements are Available:\n";
			echo $annoucements."\n";
		}

	break;
	case 'showengine':
		showEngine();
	break;
	case 'showupgrade':
	case 'showupgrades':
		showUpgrades();
	break;
	case 'uninstall':
		if (empty($param)) {
			fatal("Missing module name");
		}
		doUninstall($param, $force);
	break;
	case 'upgrade':
		doUpgrade($param, $force);
	break;
	case 'upgradeall':
		doUpgradeAll($force);
	break;
	default:
	case 'help':
		showhelp();
		exit(10);
	break;
}

exit(0);

?>
