<?php
class Admin_UpdateController extends Zend_Controller_Action
{

	public function init()
	{
        $this->view->addHelperPath('library/Atmail/View/Helper/', 'Atmail_View_Helper');
		$this->log = Zend_Registry::get('log');

		$this->_helper->pluginCall('initAdminController', $this);
		 
		$this->request  = $this->getRequest();

		$this->view->baseUrl = $this->request->getBaseUrl();

		$this->view->appBaseUrl = $this->request->getBaseUrl() . (strpos($this->request->getBaseUrl(),'index.php')?'':'/index.php');

		$this->view->siteBaseUrl = Zend_Registry::get('siteBaseUrl');
		$this->view->moduleBaseUrl = $this->view->appBaseUrl . ($this->request->module == 'default'?'':'/' . $this->request->module);
		$this->config = Zend_Registry::get('config');
		$this->dbAdapter = Zend_Registry::get('dbAdapter');
		$this->dbTables = new dbTables();

		require_once 'application/models/admin.php';
		$this->admin = new admin();

		if ($this->request->action == 'index' )
		{
			return;
		}

		$this->_helper->viewRenderer->setNoRender();

		if( !isset($this->view->errors) )
		{
			$this->view->errors = array();
		}

		if( !isset($this->view->notices) )
		{
			$this->view->notices = array();
		}

		if( !isset($this->view->jsonIdsToRender) )
		{
			$this->view->jsonIdsToRender = array();
		}

		require_once 'library/jQuery/jQuery.php';
		// TODO: Get the Username, Password and server API, pass to admin API
	}

	public function preDispatch()
	{

		// Check the session is active, otherwise redirect to the login page
		$this->view->userData = $this->admin->getCurrentAdminData();

		if( empty($this->view->userData) ) 
		{

			$this->_forward( 'timeout', 'index', 'default' );
			return;

		}

		$this->tmpDownloadLocation = APP_ROOT . 'tmp/';
		$this->updateHostBaseUrl = 'http://atmail.com/download/';
		$this->downloadFilename = "atmail-mailserver.el6.x86_64.rpm";
		$this->downloadMD5Filename = "atmail-mailserver.el6.x86_64.rpm.md5";
		$this->downloadLogFilename = "download.log";
		$this->updateLogFilename = "update.log";
		
	}

	/**
	* Display version and available update info
	*/
	public function indexAction()
	{
		$expiry = Zend_Registry::get('config')->reg['expiry'];
		if(time() > strtotime($expiry) && !empty($expiry))
			$this->view->licenseExpired = 1;

		//first check if new update code has already been unpacked over old code
		$this->view->versionCurrentDBSchema = generateVersionString($this->_getVersionCurrentDBSchema(), true);
		$this->view->availableVersion = generateVersionString($this->_getAvailableVersion(), true);
		$this->view->reg = $this->config->reg;
		$cliUpdater = new Atmail_Update_Cli($this->dbAdapter);
		$this->view->cliUpdateAvailable = $cliUpdater->isUpdateAvailable();
		// If we're an appliance, and RPMEligibleAppliance
		$this->view->isAppliance = false;
		$this->view->isRPMEligibleAppliance = false;
		$this->view->RPMUpdateFileReadyToInstall = false;
		if( is_file("/usr/local/atmail/mailserver/bin/atmail-update-version") )
		{
			
			//if appliance do a simpler version string without revision (we currently dont know revision without downloading the RPM)
			$this->view->versionCurrentDBSchema = generateVersionString($this->_getVersionCurrentDBSchema(), false);
			$this->view->availableVersion = generateVersionString($this->_getAvailableVersion(), false);
			
			$this->view->isAppliance = true;
		
				
			$os = getOperatingSystemInfo();

			if($os['brand'] == 'CentOS' && version_compare($os['version'], '6.0', '>=') ) {

			$this->view->isRPMEligibleAppliance = true;
	
			//check the webmail/tmp folder for download.
			//only show the new appliance update facility if on a CentOS 6 box /etc/redhat-release > CentOS 6
				
				if( is_file($this->tmpDownloadLocation . $this->downloadFilename) )
				{
					if( is_file($this->tmpDownloadLocation . $this->downloadMD5Filename) )
					{
						
						$origionalFileMD5 = file_get_contents($this->tmpDownloadLocation . $this->downloadMD5Filename);
						$origionalFileMD5 = explode(" ", $origionalFileMD5);
						$origionalFileMD5 = $origionalFileMD5[0];
						$downloadedFileMD5 = md5_file($this->tmpDownloadLocation . $this->downloadFilename);
						if( $origionalFileMD5 === $downloadedFileMD5 )
							$this->view->RPMUpdateFileReadyToInstall = true;
						
					}
					else //TODO: consider requiring users who upload an update file to also input the MD5 for this validation
						$this->view->RPMUpdateFileReadyToInstall = true;
				}
				
			}	
			
		}
		else if( $this->view->cliUpdateAvailable )
		{

			if( $this->config->global['install_type'] == 'server')
				$this->view->updatePage = 'update/updateserverinstructions.phtml';
			else
				$this->view->updatePage = 'update/updateclientinstructions.phtml';

		}

	}
	
	public function initiateupdatedownloadAction() //only for RPMEligibleAppliance
	{
		
		if( is_file("/usr/local/atmail/mailserver/bin/atmail-update-version") )
		{
			
			$existingWgetLastLine = exec("ps aux | grep wget | grep \"" . $this->downloadFilename . "\"  | grep -v grep | awk '{ print $2 }'", $existingWgetArray);
			foreach( $existingWgetArray as $pid )
			{
			
				@exec( "kill " . $pid ); //kills any existing/stale/dead/stalled downloads
				
			}
			@unlink($this->tmpDownloadLocation . $this->downloadFilename);
			@unlink($this->tmpDownloadLocation . $this->downloadFilename . ".size");
			@unlink($this->tmpDownloadLocation . $this->downloadMD5Filename);
			@unlink($this->tmpDownloadLocation . $this->downloadLogFilename);
			
			//check the webmail/tmp folder for download.
			exec("cd " . $this->tmpDownloadLocation . " && wget -o" . $this->downloadLogFilename. " " . $this->updateHostBaseUrl . $this->downloadMD5Filename);
			exec("cd " . $this->tmpDownloadLocation . " && wget -o" . $this->downloadLogFilename. " -b " . $this->updateHostBaseUrl . $this->downloadFilename);
			//wait for log to start so we can populate the download size file for progress reporting
			//try up to 10 times to wait for the file to start downloading
			$retryCount = 1;
			while( !file_exists($this->tmpDownloadLocation . $this->downloadLogFilename) && $retryCount++ < 10 )
			{
				
				sleep(1);
				
			}
			if( !file_exists($this->tmpDownloadLocation . $this->downloadLogFilename) )
			{
				
				echo '{"result":"failed","initiated":false}';
				return;
				
			}
			echo Zend_Json::encode(array("initiated"=>true));

		}
		
	}
	
	public function getupdatedownloadprogressAction() //only for RPMEligibleAppliance
	{
		
		$log = $this->tmpDownloadLocation . $this->downloadLogFilename;
		$downloadedBytes = @filesize($this->tmpDownloadLocation . $this->downloadFilename); 
		if( $downloadedBytes === false )
			$downloadedBytes = -1;
		//last wget line
		$lines = explode( "\n", file_get_contents($log) );
		
		$result = array("downloadInitiated" => true, "downloadCompleted" => false, "downloadedKBytes" => 0, "percentComplete" => 0, "MBps" => 0, "estimatedTimeRemaining" => 99, "md5Pass" => false);
		if( count($lines) < 7 )
		{
			
			echo Zend_Json::encode($result);
			return;
			
		}
		//now search backwards up the log to find the last download progress line
		$foundLastRelevantLogLine = false;
		for( $a = count($lines)-1 ; $a > 4 && $a > count($lines)-10 ; $a-- )
		{
			
			$line = $lines[$a];
			
			$lineArray = preg_split('/[\s=]+/', $line);
			$relevantParts = array();
			foreach($lineArray as $component )
			{
				if( str_replace(".","",$component) != '' )
					$relevantParts[] = $component;
			}
			if( count($relevantParts) == 4 )
			{
				$foundLastRelevantLogLine = true;
				break;
			}
			
		}
		if( $foundLastRelevantLogLine )
		{

			$result["downloadedKBytes"] = trim($relevantParts[0], "K");
			$result["percentComplete"] = trim($relevantParts[1], "%");
			//if last char of $relevantParts[2] is K then need to divide by 1024
			if( substr($relevantParts[2], -1) == "K" )
				$relevantParts[2] = round(trim($relevantParts[2])/1024,2) . "M";
			$result["MBps"] = trim($relevantParts[2], "M");
			if( $result["percentComplete"]==100 )
			{
				$result["estimatedTimeRemaining"] = "0";
				$result["downloadCompleted"] = true;
				$origionalFileMD5 = file_get_contents($this->tmpDownloadLocation . $this->downloadMD5Filename);
				$origionalFileMD5 = explode(" ", $origionalFileMD5);
				$origionalFileMD5 = $origionalFileMD5[0];
				$downloadedFileMD5 = md5_file($this->tmpDownloadLocation . $this->downloadFilename);
				if( $origionalFileMD5 === $downloadedFileMD5 )
					$result["md5Pass"] = true;
				else
					$result["md5Pass"] = false;
			}
			else
				$result["estimatedTimeRemaining"] = $relevantParts[3];
			
		}
		echo Zend_Json::encode($result);
		
	}
	
	public function uploadupdateAction() //receives the upload of the Appliance update file  //only for RPMEligibleAppliance
	{
		
		$this->_helper->viewRenderer->setNoRender();
		
		// Get the file uplaod via the DND
		$fileName = $_SERVER['HTTP_X_FILE_NAME'];
		
		if( !array_key_exists('HTTP_X_FILE_NAME', $_SERVER) )
		{
			
			echo '{"result":"failed","errorMessage":"this script can only be called with valid update package."}';
			return;
			
		}
		
		$fileSize = $_SERVER['HTTP_X_FILE_SIZE'];
		exec("ps aux | grep wget | grep \"$this->downloadFilename\"  | grep -v grep | awk '{ print $2 }'", $existingWgetArray);
		foreach( $existingWgetArray as $pid )
		{
		
			@exec( "kill " . $pid ); //kills any existing/stale/dead/stalled downloads
			
		}
		@unlink($this->tmpDownloadLocation . $this->downloadFilename);
		@unlink($this->tmpDownloadLocation . $this->downloadFilename . ".size");
		@unlink($this->tmpDownloadLocation . $this->downloadMD5Filename);
		@unlink($this->tmpDownloadLocation . $this->downloadLogFilename);
		file_put_contents($updateFilename . ".size", $fileSize);
		
		try
		{
			$uploadedFile = fopen("php://input", "r");
			$newFile = fopen($this->tmpDownloadLocation . $this->downloadFilename, 'w');
			while( !feof($uploadedFile) ) 
			{
			   fwrite($newFile, fread($uploadedFile, 1024));
			}
			fclose($newFile);
			//$this->fileAdapter->createFile($updateFilename, fopen("php://input", "r"));
			echo '{"result":"success", "md5": "' . md5_file($this->tmpDownloadLocation . $this->downloadFilename) . '"}';
		}
		catch(Atmail_Exception $e)
		{
			echo '{"result":"failed","errorMessage":"' . $e->getMessage() . '"}';
		}

	}
	
	public function cancelupdateinstallationAction() //only for RPMEligibleAppliance
	{
		
		@unlink($this->tmpDownloadLocation . $this->downloadFilename);
		@unlink($this->tmpDownloadLocation . $this->downloadFilename . ".size");
		@unlink($this->tmpDownloadLocation . $this->downloadMD5Filename);
		@unlink($this->tmpDownloadLocation . $this->downloadLogFilename);
		echo '{"result":"success"}';
	
	}

	public function initiateupdateinstallationAction() //only for RPMEligibleAppliance
	{
		
		$versionCurrentDBSchema = generateVersionString($this->_getVersionCurrentDBSchema(), true);
		$responseArray = array();
		$updateLogFilename = $this->tmpDownloadLocation . $this->updateLogFilename;
		@unlink($updateLogFilename);
		$command = "cd /usr/local/atmail && /usr/local/atmail/mailserver/bin/atmail-update-version $versionCurrentDBSchema server";
		file_put_contents($updateLogFilename, "Initiating update installation");
		exec($command, $responseArray); // will take time to complete installation so monitor log output with another periodic hit
		$responseLines = '...';
		if( is_array($responseArray) )
			$responseLines = implode("\n<br />\n", $responseArray);
		$responseLines = str_replace("[1;32m",'<span style="color:#00ff00">', $responseLines);
		$responseLines = str_replace("[0;39m",'</span>', $responseLines);
	
		// Delete the RPM/temp files
                @unlink($this->tmpDownloadLocation . $this->downloadFilename);
                @unlink($this->tmpDownloadLocation . $this->downloadFilename . ".size");
                @unlink($this->tmpDownloadLocation . $this->downloadMD5Filename);
                @unlink($this->tmpDownloadLocation . $this->downloadLogFilename);
	
		//if $pid > 0 then parent with returned child pid.
		$result = array( "status" => "Update installation initiated, and assumed finished.", "logDeltaLines" => $responseLines, "message" => "Check log for result details." );
		echo Zend_Json::encode( $result ); //installation initiated (not necessarily complete)
	
	}

	public function getupdateinstallationprogressAction() //only for RPMEligibleAppliance
	{
		
		$updateLogFilename = $this->tmpDownloadLocation . $this->updateLogFilename;
		$logLines = file_get_contents($updateLogFilename);
		
		$result = array( "status" => "in progress", "logDeltaLines" => $logLines, "mockVar" => time() );
		echo Zend_Json::encode( $result );
	
	}

	public function updatestatusAction() // Not Centos 6 Appliance
	{
		
		$this->_helper->viewRenderer->setNoRender();
		
		$this->request  = $this->getRequest();
		$this->requestParams = $this->request->getParams();

		$stamp = is_numeric($this->requestParams['stamp']) ? $this->requestParams['stamp'] : '000';
		$filename = APP_ROOT . 'log/atmail.update.' . $stamp . '.log';
		
		if( is_file($filename) )
			echo str_replace("\n", "<br>", file_get_contents($filename));	
		
	}
	
	/** 
	* Update the system to the latest version 
	*/ 
	public function doversionupdateAction() 
	{ 
		$error = false;
		
		require_once('library/Atmail/Update.php');
		
		
		if( isset($this->config->global['demo']) &&  $this->config->global['demo'] == 1) 
		{ 
			throw new Exception('Disabled in demo');  
		}
		
		$expiry = Zend_Registry::get('config')->reg['expiry'];
		if(time() > strtotime($expiry) && !empty($expiry))
		{
			throw new Exception('Unable to complete version update as your current license has expired.'); 
		}
		
		// If we're an appliance, run the CLI script to fetch and extract the tgz
        if (file_exists("/usr/local/atmail/mailserver/bin/atmail-update-version"))
        {
			if ($this->config->global['install_type'] == 'server')
			{
				//`/usr/local/atmail/mailserver/bin/atmail-update-version fetch server`;
				//With RPM we no longer use fetch flag (we have already fetched it so unpacking it will unpack to binaries, so no recompilation of source required)
				exec("/usr/local/atmail/mailserver/bin/atmail-update-version fetch server");
			}
			else
			{
				//`/usr/local/atmail/mailserver/bin/atmail-update-version fetch client`;
				//With RPM we no longer use fetch flag (we have already fetched it so unpacking it will unpack to binaries, so no recompilation of source required)
				$VERSION = file_get_contents(APP_ROOT . ".VERSION");
				exec("/usr/local/atmail/mailserver/bin/atmail-update-version $VERSION client");
				
			}

            if (file_exists("/usr/local/atmail/webmail/log/.installFailed"))
            {
                throw new Exception('New version failed MD5 checksum verification, downloaded package possibly corrupted. Please try again');
            }
		}

		//first check if new update code has already been unpacked over old code
		$this->view->versionCurrentDBSchema = generateVersionString($this->_getVersionCurrentDBSchema(), true);
		$this->view->versionCurrentLocalCodebase = generateVersionString(getVersionCurrentLocalCodebase(), true);
		$this->view->versionOrig = $this->view->versionCurrentDBSchema;

		//$this->log->debug( __METHOD__ . ' #' . __LINE__ . ": \$this->view->versionCurrentDBSchema \n" . print_r($this->view->versionCurrentDBSchema, true) );
		//$this->log->debug( __METHOD__ . ' #' . __LINE__ . ": \$this->view->versionCurrentLocalCodebase \n" . print_r($this->view->versionCurrentLocalCodebase, true) );

		//local code is unpacked as latest version so ready to proceed with db schema update prior to serving server-update script (if server install)

		/* db schema changes start here */
		//CONSIDER only upgrading one chunk at a time, and ending hit so ui get updated with each chunk complete.
		// this will allow easier diagnosis of chunk update failers and will stop gracefully at a specific version
		// NB do schema changes inside a transaction to allow gracefull failure without half updated schema for a specific version chunk

		// ***** USE THE NEW UPDATER HERE ******
		$Atmail_Update_Db = new Atmail_Update_Db($this->dbAdapter);
		
		$this->request  = $this->getRequest();
		$this->requestParams = $this->request->getParams();

		$stamp = is_numeric($this->requestParams['stamp']) ? $this->requestParams['stamp'] : '000';

		// set the log file for this update attempt (trunc)
		$Atmail_Update_Db->setLogFile(rtrim(APP_ROOT . 'log', '/') . '/atmail.update.' . $stamp . '.log');

		try
		{
			$this->view->cliRequired = $Atmail_Update_Db->updateAll();
		}
		catch(Exception $e)
		{
			// some sort of error.
			$error = true;
			$Atmail_Update_Db->log( $this->view->translate('Database update ran into issues while trying to update to') . " " . generateVersionString($this->view->versionCurrentLocalCodebase, true) . "<br>" . "Error details : " . $e->getMessage() . "<br>" . $this->view->translate("Please see") . " "  . $Atmail_Update_Db->getLogFile() . " " . $this->view->translate("for more further details."));
			jQuery::addError( $this->view->translate('Database update ran into issues while trying to update to') . " " . generateVersionString($this->view->versionCurrentLocalCodebase, true) . "<br>" . "Error details : " . $e->getMessage() . "<br>" . $this->view->translate("Please see") . " "  . $Atmail_Update_Db->getLogFile() . " " . $this->view->translate("for more further details."));
		}
		$versions[] = $this->view->versionCurrentLocalCodebase;
		
		$this->view->versionCurrentDBSchema = generateVersionString($this->dbAdapter->fetchOne("select keyValue from Config where keyName = 'version'"), true);

		/* end db schema changes */
		if( !$error && version_compare($this->view->versionCurrentDBSchema, $this->view->versionCurrentLocalCodebase, '==' ) )
		{
			
			jQuery::addMessage( $this->view->translate('Database schema update complete.') );
			jQuery('#versionCurrentDBSchema')->html(generateVersionString($this->view->versionCurrentDBSchema, true));
			jQuery('#versionUpdateButtonArea')->html('<p>' . $this->view->translate('You are up to date with the latest version available.') . '</p>');
			
			//give additional instructions if server install
			
			if( $this->config->global['install_type'] == 'server' && $this->view->cliRequired == 1 )
			{
                // If we're an appliance, just auto run the CLI script - else advise user to manualy run CLI updater
                if (file_exists("/usr/local/atmail/mailserver/bin/atmail-update-version")) {
                	`/usr/local/atmail/mailserver/bin/atmail-update-version {$this->view->versionOrig} server`;

					// Check return code
					jQuery::addMessage( $this->view->translate('New version downloaded, database schema updated and new version installed') );
					
               	} else {
					$this->view->jsonIdsToRender['updateProcessInstructions'] = 'update/updateserverinstructions.phtml';
				}
			}
			elseif( $this->view->cliRequired )
			{
				
				// If we're an appliance, just auto run the CLI script
                if (file_exists("/usr/local/atmail/mailserver/bin/atmail-update-version")) {
                	`/usr/local/atmail/mailserver/bin/atmail-update-version {$this->view->versionOrig} client`;
				} else {
					$this->view->jsonIdsToRender['updateProcessInstructions'] = 'update/updateclientinstructions.phtml';
				}
			}

		}
		else if(!$error)
		{
			$error = true;
			jQuery::addError( $this->view->translate('Database update did not complete.') . ' Current version: ' . generateVersionString($this->view->versionCurrentDBSchema, true) . ' Available version: ' . generateVersionString($this->view->versionCurrentLocalCodebase, true) . "<br>" . $this->view->translate("Please see") . " "  . $Atmail_Update_Db->getLogFile() . " " . $this->view->translate("for more details."));
		}

		// Close the dialog-box on the update window
		if($error)
		{
			// make dialog RED
			jQuery('.ui-dialog, .ui-dialog-titlebar, .ui-dialog-title, .ui-dialog-titlebar-close')->addClass('errorUpdate');
			/*
			jQuery('.ui-dialog')->css('border', '4px solid #FF0000');
			jQuery('.ui-dialog-titlebar')->css('color', '#FF0000');
			jQuery('.ui-dialog-titlebar')->css('background', '#FFBABA');
			jQuery('.ui-dialog-titlebar')->css('border-color', '-moz-use-text-color -moz-use-text-color #FF0000');
			*/
			jQuery('.ui-dialog-title')->text("Update Atmail - Failed");
			
		}
		jQuery('#dialog #details')->html(str_replace("\n", "<br>", file_get_contents($Atmail_Update_Db->getLogFile())) . '<br>' . $this->view->translate("You may dismiss this dialog box now."));
		jQuery('#dialog #details')->scrollTo(10000);
		jQuery::evalScript("clearInterval($('#dialog').data('intervalHandle'))");
		$this->render('global/jsonresponse', null, true);

	}

	/**
	* Get current version number
	*
	* @return String|False
	*/
	private function _getVersionCurrentDBSchema()
	{

		return $this->config->global['version'];

	}


	/**
	 * Get current available Atmail version
	 *
	 * @return String
	 */
	 
	private function _getAvailableVersion()
	{
		// If an appliance we want to get latest version available online
		if (file_exists("/usr/local/atmail/mailserver/bin/atmail-update-version")) {
			return $this->_getLatestReleasedVersion();
		} else {
			return getVersionCurrentLocalCodebase();
		}
	}


	/**
	 * Get latest released version (for appliances only)
	 *
	 * @return String
	 */
	private function _getLatestReleasedVersion()
	{
		$resp = `curl -s http://atmail.com/store/index.php/registration/latestversion`;
		list($latestVersion,) = explode("|", $resp);
		return $latestVersion;
	}

}
