<?php
// vim: set ai ts=4 sw=4 ft=php:
namespace FreePBX\modules;
class Versionupgrade implements \BMO {
	private $upgradeVer = 13;
	private $minAsteriskVer = 11;
	private $minPHPVer = '5.3.3';
	private $features = array();
	/**
	 * The modules in this list are updates IN order
	 * SEPARATELY from upgradeALL!
	 * @var array
	 */
	private $upgradeModules = array(
		'backup',
		'recording_report'
	);
	/**
	 * The modules in this list are disabled until
	 * proper updates come out for them
	 * @var array
	 */
	private $disableModules = array(
		'digium_phones'
	);

	public function __construct($freepbx = null) {
		$this->freepbx = $freepbx;
		$this->brand = $freepbx->Config->get("DASHBOARD_FREEPBX_BRAND");
		$this->features = array(
			_("New look and feel") => "<p>".sprintf(_("Introduced in %s 12 in minimal ways, %s 13 takes full advantage of Twitter Bootstrap. Creating a larger more fluid feel that you are already familar with"),$this->brand,$this->brand).'</p><p><img src="assets/versionupgrade/images/tabsandbootstrap.png"></p>',
			_("The Grid") => '<p>'._('In the past most modules brought you to a random page that had either an add form or in some cases nothing. We are happy to introduce "The Grid". When you enter most modules you will be met with a quick summary of entries for that module and any additional information as appropriate.').'</p><p><img src="assets/versionupgrade/images/13-1-gridlayout.png"></p>',
			_("Global Search Navigation") => '<p>'.sprintf(_('%s 13 now has a search bar that allows you to quickly navigate to where you need to go. Rather than clicking through "Applications->Extensions->1000 you can simply type 1000 and go'),$this->brand).'</p><p><img src="assets/versionupgrade/images/13-4-search2.png"></p>',
			_("Call Logging") => '<p>'.sprintf(_('With %s 13 using Asterisk 12+ we have introduced the CEL (Call Event Log) module that allows you to take advantage of the CEL functionality recently introduced in Asterisk. This call logging method allows you to see the full life and path of the call and channel.'),$this->brand).'</p><p><img src="assets/versionupgrade/images/13-6-cel.png"></p>',
			_("Bulk Importer") => '<p>'.sprintf(_('Bulk Management has been a big request and has seen minimal support in the past. %s 13 introduces the new Bulk Manager module which is a pluggable module that allows you to do mass "spreadsheet" management of Users, Extensions, Contacts and DIDs to start with. Designed to grow and scale with the evolution of %s and the introduction of features from other modules that affect these.'),$this->brand,$this->brand).'</p><p><img src="assets/versionupgrade/images/13-5-bulk.png"></p>',
			_("Wizards") => '<p>'.sprintf(_('Simplied wizards have finally come to %s. Complex situations like creating extensions or backups are now simplified with a step by step guide'),$this->brand).'</p><p><img src="assets/versionupgrade/images/13-3-wizard.png"></p>',
			_("Mobile First") => '<p>'.sprintf(_('Thanks to Bootstrap, %s now implements a mobile first design. This means that most pages in %s will work out of the box on your mobile device with minimal changes'),$this->brand,$this->brand).'</p><p><img src="assets/versionupgrade/images/mobile.png" style="height: 350px"></p>',
			_("Localized Sounds") => '<p>'.sprintf(_('With %s prevalent in over 220 countries, language localization has been a priority and version 12 already saw huge efforts in the improved localization abilities of %s for different languages. This new version adds the next dimension of easy sound file management with simple online access to other languages. The last leg of this effort will be the System Recording module which will be re-written to enable the concept of multiple language versions of the same call recording and will be following right on the heals of this release. This means, for instance, you will be able to have a single IVR that can play in different languages with the exact same recording based on language selection decisions in the call flow.'),$this->brand,$this->brand).'</p><p><img src="assets/versionupgrade/images/13-7-localization.png"></p>',
			_("CLI Upgrades") => '<p>'.sprintf(_('%s has always had a very limited ability to work with it at the CLI level via various "amportal" commands. This latest version has adopted the Symfony framework and began to offer much more extensive CLI access through the "fwconsole" command.'),$this->brand).'</p><p><img src="assets/versionupgrade/images/13-8-fwconsole.png"></p>',
		);
	}

	public function install() {
	}
	public function uninstall() {
	}
	public function backup() {
	}
	public function restore($backup) {
	}
	public function doConfigPageInit($page) {
	}

	public function ajaxRequest($command, $settings) {
		switch($command) {
			case "submit":
			case "upgrade":
				return true;
			break;
			default:
				return false;
			break;
		}
	}

	private function sendData($data=array()) {
		$repo = $this->freepbx->Config->get("MODULE_REPO");
		if(isset($_POST['module_repo']) && $_POST['module_repo'] == 'reset') {
			$this->freepbx->Config->update("MODULE_REPO", "http://mirror1.freepbx.org,http://mirror2.freepbx.org", true);
		}
		if (!function_exists('sysadmin_get_license')) {
			$awr = $this->freepbx->Config->get("AMPWEBROOT");
			include $awr ."/admin/modules/sysadmin/functions.inc.php";
		}
		$pest = new \PestJSON('https://katanafpbx.schmoozecom.com');
		$mf = \module_functions::create();
		$install_hash = $mf->_generate_unique_id();
		$installid = $install_hash['uniqueid'];
		$type = $install_hash['type'];
		$distro_hash = $mf->_distro_id();
		$distro = $distro_hash['pbx_type'];
		$version = $distro_hash['pbx_version'];
		$data['installid'] = $installid;
		$data['tos'] = ($_POST['tos'] == 'true') ? 1 : 0;
		$data['deploymentid'] = $mf->_deploymentid();
		$data['distro_name'] = $distro;
		$data['distro_version'] = $version;
		$data['user_distro_name'] = $_POST['pbx_type'];
		$data['user_distro_name_custom'] = $_POST['pbx_type_name'];
		$data['install_type'] = $type;
		$data['brand'] = $mf->_brandid();
		$data['user_name'] = $_POST['name'];
		$data['user_company'] = $_POST['company'];
		$data['user_phone'] = $_POST['phone'];
		$data['user_email'] = $_POST['email'];
		$data['repo_reset'] = (isset($_POST['module_repo']) && $_POST['module_repo'] == 'reset') ? 1 : 0;
		$data['repo'] = $repo;
		try{
			$ret = $pest->post('/versionupgrader/form',$data);
		} catch(\Exception $e) {
			return array(
				"status" => false,
				"continue" => false,
				"message" => sprintf(_("Error contacting server: %s"),$e->getMessage())
			);
		}
		return $ret;
	}

	public function ajaxHandler() {
		switch($_REQUEST['command']) {
			case "submit":
				$ret = $this->sendData();
				if(!$ret['status'] && $ret['action'] == 'regenerateid') {
					$mf = \module_functions::create();
					$mf->_regenerate_unique_id();
					$ret = $this->sendData(array("regenerated" => true));
				}
				return $ret;
			break;
			default:
				return false;
			break;
		}
	}

	public function showPage() {
		$action = !empty($_REQUEST['action']) ? $_REQUEST['action'] : array();
		$html = "";
		switch($action) {
			case "check":
				$checks = $this->runChecks();
				$out = $this->freepbx->Modules->getInfo("sysadmin");
				$deid = $data = \module_functions::create()->_deploymentid();
				$show_form = (empty($out['sysadmin']['status']) || $out['sysadmin']['status'] != MODULE_STATUS_ENABLED) || empty($deid);
				$did = \module_functions::create()->_distro_id();
				$repo = $this->freepbx->Config->get('MODULE_REPO');
				$repo_check = ($repo == "http://mirror1.freepbx.org,http://mirror2.freepbx.org");
				$distros = array(
					"freepbxdistro" => sprintf(_('%s Distro'),$this->brand),
					"trixbox" => "Trixbox",
					"asterisknow" => "AsteriskNow",
					"elastix" => "Elastix",
					"piaf" => "PBX in a Flash",
					"raspbx" => "RasPBX (Raspberry Pi)",
					"fonica" => "Fonica"
				);
				asort($distros);
				$html = load_view(__DIR__."/views/check.php",array("distros" => $distros, "repo_check" => $repo_check, "did" => $did, "brand" => $this->brand, "show_form" => $show_form, "upgradeVersion" => $this->upgradeVer, "checks" => $checks['checks'], "allowUpgrade" => $checks['allowUpgrade']));
			break;
			default:
				$deid = $data = \module_functions::create()->_deploymentid();
				$out = $this->freepbx->Modules->getInfo("sysadmin");
				if(!empty($out['sysadmin']['status']) && ($out['sysadmin']['status'] == MODULE_STATUS_ENABLED) && empty($deid)) {
					\FreePBX::Sysadmin()->O()->restartOobe('versionupgrade');
				}
				$html = load_view(__DIR__."/views/landing.php",array("brand" => $this->brand, "upgradeVersion" => $this->upgradeVer, "features" => $this->features));
			break;
		}
		return $html;
	}

	/**
	 * Send event to browser
	 * @param  string $type Event type
	 * @param  mixed $data Data elements
	 */
	public function sendEvent($type, $data) {
		echo "event: ".$type."\n";
		echo 'data: '.json_encode($data);
		echo "\n\n";
		ob_flush();
		flush();
	}

	/**
	 * Custom ajax handler
	 */
	public function ajaxCustomHandler() {
		set_time_limit(0);
		apache_setenv('no-gzip', '1');
		switch($_REQUEST['command']) {
			case "upgrade":
				header("Content-Type: text/event-stream\n\n");
				echo ":" . str_repeat(" ", 2048) . "\n"; // 2 kB padding for IE
				echo "retry: 2000\n";
				flush();
				$mf = \module_functions::create();
				$bin = $this->freepbx->Config->get('AMPBIN');
				switch($_REQUEST['stage']) {
					case 1:
						$_SESSION['modules'] = null;
						$this->sendEvent("message", array("message" => _("Running checks..."), "newline" => false));
						$checks = $this->runChecks();
						if(!$checks['allowUpgrade']) {
							foreach($checks['checks'] as $check) {
								if(!$check['status']) {
									$this->sendEvent("action", array("action" => "error", "message" => $check['description']));
								}
							}
							return true;
						} else {
							$this->sendEvent("message", array("message" => _("Passed")));
						}
						$this->sendEvent("message", array("message" => "<strong>".sprintf(_("Stage %s"),1)."</strong>"));

						//Bump Version to alpha of next release
						$this->sendEvent("message", array("message" => sprintf(_("Bumping %s to version %s..."),$this->brand, $this->upgradeVer), "newline" => false));
						$this->freepbx->Database->query("UPDATE admin SET value = '".$this->upgradeVer.".0.0alpha1' WHERE variable = 'version'");
						$sth = $this->freepbx->Database->prepare("SELECT value FROM admin WHERE variable = 'version'");
						$sth->execute();

						//Make sure version got properly bumped
						$data = $sth->fetch(\PDO::FETCH_ASSOC);
						if($data['value'] != $this->upgradeVer.'.0.0alpha1') {
							$this->sendEvent("action", array("action" => "error", "message" => _("Unable to set version")));
							return true;
						}

						//Turn off caching if enabled and mark it to be re-enabled later
						$this->sendEvent("message", array("message" => _("Done")));
						$sc = $this->freepbx->Config->get('MODULEADMIN_SKIP_CACHE');
						if(empty($sc)) {
							$_SESSION['sc'] = false;
						} else {
							$_SESSION['sc'] = true;
						}
						$this->freepbx->Config->update('MODULEADMIN_SKIP_CACHE',1,true);

						//Check online server to get all new modules
						$this->sendEvent("message", array("message" => _("Checking online servers..."), "newline" => false));
						$mf->getonlinexml();
						$this->sendEvent("message", array("message" => _("Done")));

						//Download framework through normal processes as we need to get fwconsole
						$this->sendEvent("message", array("message" => sprintf(_("Downloading %s Framework..."), $this->upgradeVer), "newline" => false));
						$mf->download("framework", true, 'FreePBX\modules\vumfcallback');
						$this->sendEvent("message", array("message" => _("...Done")));
						$this->sendEvent("message", array("message" => sprintf(_("Installing %s Framework..."), $this->upgradeVer), "newline" => false));
						$mf->install("framework", true);
						$this->sendEvent("message", array("message" => _("Done")));

						//fwconsole should be in place. Fix any zend issues
						exec($bin."/fwconsole --fix_zend");
						//Store all modules that need upgrading in modules, along with total
						$_SESSION['modules'] = $this->getUpgradable();
						$_SESSION['totalModules'] = count($_SESSION['modules']);
						$_SESSION['modulesProcessed'] = 1;

						//Set our total progress bar
						$this->sendEvent("total", array("percent" => floor(($_SESSION['modulesProcessed']/$_SESSION['totalModules']) * 100)));
						$_SESSION['step'] = 1;
						$this->freepbx->Config->update('MODULEADMIN_SKIP_CACHE',0,true);
						$this->sendEvent("action", array("action" => "step", "step" => "2"));
					break;
					case 2:
						//Next upgrade core AFTER framework (which is very important)
						$this->sendEvent("message", array("message" => "<strong>".sprintf(_("Stage %s"),2)."</strong>"));
						$this->runMACommand("upgrade core");
						$_SESSION['modulesProcessed']++;
						$key = array_search("core", $_SESSION['modules']);
						if($key !== false) {
							unset($_SESSION['modules'][$key]);
						}
						$this->sendEvent("total", array("percent" => floor(($_SESSION['modulesProcessed']/$_SESSION['totalModules']) * 100)));
						$_SESSION['step'] = 2;
						$this->sendEvent("action", array("action" => "step", "step" => "3"));
					break;
					case 3:
						//Next upgrade sysadmin AFTER framework AFTER core (important!)
						$this->sendEvent("message", array("message" => "<strong>".sprintf(_("Stage %s"),3)."</strong>"));
						$out = $this->freepbx->Modules->getInfo("sysadmin");
						//only do this if they have sysadmin installed
						if(!empty($out['sysadmin']['status']) && ($out['sysadmin']['status'] == MODULE_STATUS_ENABLED || $out['sysadmin']['status'] == MODULE_STATUS_NEEDUPGRADE)) {
							$this->runMACommand("upgrade sysadmin");
							$_SESSION['modulesProcessed']++;
							$key = array_search("sysadmin", $_SESSION['modules']);
							if($key!==false) {
								unset($_SESSION['modules'][$key]);
							}
							$this->sendEvent("total", array("percent" => floor(($_SESSION['modulesProcessed']/$_SESSION['totalModules']) * 100)));
						} else {
							$this->sendEvent("message", array("message" => "<strong>".sprintf(_("Stage %s not needed, skipping"),3)."</strong>"));
						}
						$_SESSION['step'] = 3;
						$this->sendEvent("action", array("action" => "step", "step" => "4"));
					break;
					case 4:
						//special case modules
						$this->sendEvent("message", array("message" => "<strong>".sprintf(_("Stage %s"),4)."</strong>"));
						foreach($this->upgradeModules as $mod) {
							$out = $this->freepbx->Modules->getInfo($mod);
							if(!empty($out[$mod]['status'])) {
								$disabled = false;
								if($out[$mod]['status'] == MODULE_STATUS_DISABLED) {
									$disabled = true;
								}
								if($out[$mod]['status'] == MODULE_STATUS_ENABLED) {
									$this->runMACommand("disable ".$mod);
								}
								$this->runMACommand("download ".$mod);
								$this->runMACommand("enable ".$mod);
								$this->runMACommand("install ".$mod);
								if($disabled) {
									$this->runMACommand("disable ".$mod);
								}
								$_SESSION['modulesProcessed']++;
								$key = array_search($mod, $_SESSION['modules']);
								if($key!==false) {
									unset($_SESSION['modules'][$key]);
								}
								$this->sendEvent("total", array("percent" => floor(($_SESSION['modulesProcessed']/$_SESSION['totalModules']) * 100)));
							}
						}
						foreach($this->disableModules as $mod) {
							$out = $this->freepbx->Modules->getInfo($mod);
							if(!empty($out[$mod]['status'])) {
								if($out[$mod]['status'] == MODULE_STATUS_ENABLED) {
									$this->runMACommand("disable ".$mod);
								}
							}
						}
						$_SESSION['step'] = 4;
						$this->sendEvent("action", array("action" => "step", "step" => "5"));
					break;
					case 5:
						if($_SESSION['step'] == 4) {
							$this->sendEvent("message", array("message" => "<strong>".sprintf(_("Stage %s"),5)."</strong>"));
							$_SESSION['step'] = 5;
						}
						//No modules left to upgrade, move on to the next step
						if(empty($_SESSION['modules'])) {
							$_SESSION['modules'] = array();
							$this->sendEvent("action", array("action" => "step", "step" => "6"));
						} else {
							//get next module
							$module = array_shift($_SESSION['modules']);
							try {
								$this->runMACommand("download ".$module);
								$this->runMACommand("install ".$module);
							} catch(\Exception $e) {
								//TODO log for a redo at the end
							}
							$_SESSION['modulesProcessed']++;
							$this->sendEvent("total", array("percent" => floor(($_SESSION['modulesProcessed']/$_SESSION['totalModules']) * 100)));
							$this->sendEvent("action", array("action" => "step", "step" => "5"));
						}
					break;
					case 6:
						$_SESSION['step'] = 6;
						if(empty($_SESSION['sc'])) {
							$this->freepbx->Config->update('MODULEADMIN_SKIP_CACHE',0,true);
							$_SESSION['sc'] = true;
						} else {
							$this->freepbx->Config->update('MODULEADMIN_SKIP_CACHE',1,true);
						}
						//force the bar to the end
						$this->sendEvent("total", array("percent" => floor(($_SESSION['totalModules']/$_SESSION['totalModules']) * 100)));
						$_SESSION['modules'] = array();
						$_SESSION['totalModules'] = 0;
						$_SESSION['step'] = 1;
						$this->sendEvent("action", array("action" => "finish"));
					break;
				}
				return true;
			break;
		}
		return false;
	}

	/**
	 * Check if a string is actually valid JSON
	 * @param  string  $string String to check
	 * @return boolean         True if is JSON
	 */
	public function isJson($string) {
		json_decode($string);
		return (json_last_error() == JSON_ERROR_NONE);
	}

	/**
	 * Run module admin command using fwconsole
	 * @param  string $command Command to run
	 */
	private function runMACommand($command) {
		$bin = $this->freepbx->Config->get('AMPBIN');
		if(!file_exists($bin."/fwconsole")) {
			throw new \Exception("Cant find fwconsole!");
		}
		$process = new \Symfony\Component\Process\Process($bin."/fwconsole ma ".$command." --format=json");
		//15 minute timeout
		$process->setTimeout(900);
		$process->run(function ($type, $buffer) {
				if (\Symfony\Component\Process\Process::ERR === $type) {
						\FreePBX::Versionupgrade()->sendEvent("action", array("type" => "error", "message" => $buffer));
				} else {
						$buffer = trim($buffer);
						if(\FreePBX::Versionupgrade()->isJson($buffer)) {
							$buffer = json_decode($buffer,true);
							\FreePBX::Versionupgrade()->sendEvent("array", $buffer);
						} else {
							$buffers = explode("\n",$buffer);
							foreach($buffers as $buffer) {
								$buffer = trim($buffer);
								if(\FreePBX::Versionupgrade()->isJson($buffer)) {
									$buffer = json_decode($buffer,true);
									\FreePBX::Versionupgrade()->sendEvent("array", $buffer);
								} else {
									\FreePBX::Versionupgrade()->sendEvent("message", array("message" => $buffer));
								}
							}
						}
				}
		});
	}

	/**
	 * Get all upgradable modules
	 * @return array Array of modules using rawname
	 */
	private function getUpgradable() {
		$mf = \module_functions::create();
		set_time_limit(0);
		$modules_local = $mf->getinfo(false, MODULE_STATUS_ENABLED);
		$modules_online = $mf->getonlinexml();
		$modules_upgradable = array();
		$this->second = 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) {
					$modules_upgradable[] = $name;
				}
			}
		}
		return $modules_upgradable;
	}

	/**
	 * Run through all of our inital checks
	 * @return array Array of status returns
	 */
	private function runChecks() {
		$astversion = $this->freepbx->Config->get("ASTVERSION");
		$allowUpgrade = true;
		$checks = array();
		$out = $this->freepbx->Modules->getInfo("sysadmin");
		if(!empty($out['sysadmin']['status']) && ($out['sysadmin']['status'] == MODULE_STATUS_ENABLED)) {
				$data = \module_functions::create()->_deploymentid();
				if(empty($data)) {
					$checks[] = array(
						"status" => true,
						"color" => "red",
						"title" => _("System is not registered"),
						"description" => _("By registering your system, you will be able to keep up with the latest software releases, bug fixes and security notifications. You'll also be able to take advantage of free software offers, have access to add-ons, support and the latest product news from the development team. Additionally the free features of Sysadmin will be disabled until the system is registered.").' <a href="?display=sysadmin&view=activation">'._("Register Here").'</a>',
					);
				} else {
					$checks[] = array(
						"status" => true,
						"color" => "green",
						"title" => _("System is registered"),
						"description" => sprintf(_("Your system has a valid deployement"), $astversion),
					);
				}
		}
		if(version_compare($this->minAsteriskVer,$astversion,"<=")) {
			$checks[] = array(
				"status" => true,
				"color" => "green",
				"title" => sprintf(_("Asterisk %s or higher"),$this->minAsteriskVer),
				"description" => sprintf(_("Your Asterisk version of %s is supported"), $astversion),
			);
		} else {
			$checks[] = array(
				"status" => false,
				"color" => "red",
				"title" => sprintf(_("Asterisk %s or higher"),$this->minAsteriskVer),
				"description" => sprintf(_("Your Asterisk version of %s is NOT supported. You must have a version higher than or equal to %s"), $astversion, $this->minAsteriskVer),
			);
			$allowUpgrade = false;
		}
		if(version_compare($this->minPHPVer,PHP_VERSION,"<=")) {
			$checks[] = array(
				"status" => true,
				"color" => "green",
				"title" => sprintf(_("PHP %s or higher"),$this->minPHPVer),
				"description" => sprintf(_("Your PHP Version of %s is supported"), PHP_VERSION),
			);
		} else {
			$checks[] = array(
				"status" => false,
				"color" => "red",
				"title" => sprintf(_("PHP %s or higher"),$this->minPHPVer),
				"description" => sprintf(_("Your PHP version of %s is NOT supported. You must have a version higher than or equal to %s"), PHP_VERSION, $this->minPHPVer),
			);
			$allowUpgrade = false;
		}
		$fpbxv = getVersion();
		if(version_compare_freepbx(($this->upgradeVer - 1),$fpbxv,"<=") && version_compare_freepbx($this->upgradeVer,$fpbxv,">")) {
			$checks[] = array(
				"status" => true,
				"color" => "green",
				"title" => sprintf(_("%s %s"),$this->brand, $this->upgradeVer - 1),
				"description" => sprintf(_("Your %s Version of %s is supported"), $this->brand, $fpbxv),
			);
		} else {
			$checks[] = array(
				"status" => false,
				"color" => "red",
				"title" => sprintf(_("%s %s"),$this->brand, $this->upgradeVer - 1),
				"description" => sprintf(_("Your %s version of %s is NOT supported. You must have version %s"), $this->brand, $fpbxv, $this->upgradeVer - 1),
			);
			$allowUpgrade = false;
		}
		return array(
			"allowUpgrade" => $allowUpgrade,
			"checks" => $checks
		);
	}
}

/**
 * External callback for processing of modules
 * @param  string $type The type of response
 * @param  mixed $data information
 */
function vumfcallback($type, $data) {
	$vu = \FreePBX::create()->Versionupgrade;
	switch($type) {
		case "downloading":
			if(empty($data['read'])) {
				$vu->sendEvent("download", array("progress" => "start", "read" => $data['read'], "total" => $data['total']));
			}
			if($data['read'] < $data['total']) {
				$vu->sendEvent("download", array("progress" => "processing", "read" => $data['read'], "total" => $data['total']));
			}
			if($data['read'] == $data['total']) {
				$vu->sendEvent("download", array("progress" => "finished", "read" => $data['read'], "total" => $data['total']));
			}
		break;
	}
}
