<?php
/*+**********************************************************************************
 * The contents of this file are subject to the vtiger CRM Public License Version 1.0
 * ("License"); You may not use this file except in compliance with the License
 * The Original Code is:  vtiger CRM Open Source
 * The Initial Developer of the Original Code is vtiger.
 * Portions created by vtiger are Copyright (C) vtiger.
 * All Rights Reserved.
 ************************************************************************************/

chdir(dirname(__FILE__) . '/../..');
include_once 'vtlib/Vtiger/Module.php';
include_once 'vtlib/Vtiger/Package.php';
include_once 'includes/main/WebUI.php';

include_once 'include/Webservices/Utils.php';

class Vtiger_Tools_Console_Controller {
	const PROMPT_ANY      = 1;
	const PROMPT_OPTIONAL = 2;
	const PROMPT_NUMERIC  = 3;
	const PROMPT_ALPHANUMERIC = 4;
	const PROMPT_NAME     = 5;
	const PROMPT_LABEL    = 6;
	const PROMPT_PATH     = 7;
	
	protected $interactive = true;
	protected $arguments   = array();

	protected function __construct() { }
	
	public function setArguments($args, $interactive) {
		$this->arguments   = $args;
		$this->interactive = $interactive;
		return $this;
	}

	protected function handle() {
		global $argv;
		$this->arguments = $argv;
		
		// Discard the script name.
		array_shift($this->arguments);
		
		if ($this->arguments) {
			$this->arguments = explode('=', $this->arguments[0]);
			$this->interactive = false;
		}

		$this->welcome();
		$this->options();	
	}

	protected function welcome() {
		if ($this->interactive) {
			echo "Welcome to Vtiger CRM Creator.\n";
			echo "This tool will enable you to get started with developing extensions with ease.\n";
			echo "Have a good time. Press CTRL+C to \"quit\".\n";
		}
	}

	protected function options() {
		if ($this->interactive) {
			echo "Choose the options below:\n";
			echo "1. Create New Module.\n";
			echo "2. Create New Layout.\n";
			echo "3. Create New Language Pack.\n";
			echo "4. Create Test Language Pack.\n";
			echo "5. Import Module.\n";
			echo "6. Update Module.\n";
			echo "7. Remove Module.\n";
			$option = $this->prompt("Enter your choice: ", self::PROMPT_NUMERIC);
		} else {
			$option = array_shift($this->arguments);
			switch ($option) {
				case '--import': $option = 5; break;
				case '--update': $option = 6; break;
				case '--remove': $option = 7; break;
			}
		}

		try {
			switch (intval($option)) {
				case 1: $this->handleCreateModule(); break;
				case 2: $this->handleCreateLayout(); break;
				case 3: $this->handleCreateLanguage(); break;
				case 4: $this->handleCreateTestLanguage(); break;
				case 5: $this->handleImportModule(); break;
				case 6: $this->handleUpdateModule(); break;
				case 7: $this->handleRemoveModule(); break;
			}
		} catch (Exception $e) {
			echo "ERROR: " .$e->getMessage() . "\n";
			echo $e->getTraceAsString();
			echo "\n";
		}
	}

	protected function prompt($msg='', $type=self::PROMPT_ANY) {
		do {
			if ($msg) echo $msg;
			$value = trim(fgets(STDIN));

			if (!$value && $type == self::PROMPT_OPTIONAL) {
				return $value;

			} else if ($value) {

				switch ($type) {
					case self::PROMPT_NUMERIC:
						if (is_numeric($value)) {
							return $value;
						}
						break;
					case self::PROMPT_ALPHANUMERIC:
						if (!preg_match("/^[a-zA-Z0-9]+$/", $value)) {
							return $value;
						}
						break;
					case self::PROMPT_NAME:
						if (!preg_match("/^[a-zA-Z][^a-zA-Z0-9_ ]*$/", $value)) {
							return $value;
						}
						break;
					case self::PROMPT_LABEL:
						if (!preg_match("/^[a-zA-Z0-9_ ]+$/", $value)) {
							return $value;
						}
						break;
					case self::PROMPT_PATH:
						if (!preg_match("/^[a-zA-Z0-9_:+.-\/\\\\]+$/", $value)) {
							return $value;
						}
					default:
						return $value;
				}
			}
		} while (true);
	}

	protected function toAlphaNumeric($value) {
		return preg_replace("/[^a-zA-Z0-9_]/", "", $value);
	}

	protected function findFiles($dir, $file_pattern, &$files) {
		$items = glob($dir . '/*', GLOB_NOSORT);
		foreach ($items as $item) {
			if (is_file($item)) {
				if (!$file_pattern || preg_match("/$file_pattern/", $item)) {
					$files[] = $item;
				}
			} else if (is_dir($item) && ($dir != $item)) {
				$this->findFiles($item, $file_pattern, $files);
			}
		}
	}

	// Option Handlers
	protected function handleCreateModule() {
		$controller = new Vtiger_Tools_Console_ModuleController();
		$controller->setArguments($this->arguments, $this->interactive)->handle();
	}

	protected function handleCreateLanguage() {
		$controller = new Vtiger_Tools_Console_LanguageController();
		$controller->setArguments($this->arguments, $this->interactive)->handle();
	}

	protected function handleCreateTestLanguage() {
		$controller = new Vtiger_Tools_Console_TestLanguageController();
		$controller->setArguments($this->arguments, $this->interactive)->handle();
	}

	protected function handleCreateLayout() {
		$controller = new Vtiger_Tools_Console_LayoutController();
		$controller->setArguments($this->arguments, $this->interactive)->handle();
	}
	
	protected function handleImportModule() {
		$controller = new Vtiger_Tools_Console_ImportController();
		$controller->setArguments($this->arguments, $this->interactive)->handle();
	}
	
	protected function handleUpdateModule() {
		$controller = new Vtiger_Tools_Console_UpdateController();
		$controller->setArguments($this->arguments, $this->interactive)->handle();
	}
	
	protected function handleRemoveModule() {
		$controller = new Vtiger_Tools_Console_RemoveController();
		$controller->setArguments($this->arguments, $this->interactive)->handle();
	}

	// Static
	public static function run() {
		$singleton = new self();
		$singleton->handle();
	}
}

class Vtiger_Tools_Console_ModuleController extends Vtiger_Tools_Console_Controller {

	public function handle() {
		echo ">>> MODULE <<<\n";

		$moduleInformation = array();
		do {
			$moduleInformation['name'] = ucwords($this->prompt("Enter module name: ", self::PROMPT_NAME));
			$module = $this->find($moduleInformation['name']);
			if (!$module) {
				break;
			}
			echo "ERROR: " . $moduleInformation['name'] . " module already exists, try another.\n";
		} while (true);

		$moduleInformation['entityfieldlabel'] = 'Name';

		$entityfieldlabel = ucwords($this->prompt(sprintf("Entity field (%s): ",
				$moduleInformation['entityfieldlabel']), self::PROMPT_OPTIONAL));
		if ($entityfieldlabel) {
			$moduleInformation['entityfieldlabel'] = $entityfieldlabel;
		}

		$moduleInformation['parent'] = 'Tools';

		echo "Creating ...";
		$this->create($moduleInformation);
		echo "DONE.\n";
	}

	public function find($name) {
		return Vtiger_Module::getInstance($name);
	}

	protected function create($moduleInformation) {
		$moduleInformation['entityfieldname']  = strtolower($this->toAlphaNumeric($moduleInformation['entityfieldlabel']));

		$module = new Vtiger_Module();
		$module->name = $moduleInformation['name'];
		$module->parent=$moduleInformation['parent'];
		$module->save();

		$module->initTables();

		$block = new Vtiger_Block();
		$block->label = 'LBL_'. strtoupper($module->name) . '_INFORMATION';
		$module->addBlock($block);

		$blockcf = new Vtiger_Block();
		$blockcf->label = 'LBL_CUSTOM_INFORMATION';
		$module->addBlock($blockcf);

		$field1  = new Vtiger_Field();
		$field1->name = $moduleInformation['entityfieldname'];
		$field1->label= $moduleInformation['entityfieldlabel'];
		$field1->uitype= 2;
		$field1->column = $field1->name;
		$field1->columntype = 'VARCHAR(255)';
		$field1->typeofdata = 'V~M';
		$block->addField($field1);

		$module->setEntityIdentifier($field1);

		/** Common fields that should be in every module, linked to vtiger CRM core table */
		$field2 = new Vtiger_Field();
		$field2->name = 'assigned_user_id';
		$field2->label = 'Assigned To';
		$field2->table = 'vtiger_crmentity';
		$field2->column = 'smownerid';
		$field2->uitype = 53;
		$field2->typeofdata = 'V~M';
		$block->addField($field2);

		$field3 = new Vtiger_Field();
		$field3->name = 'createdtime';
		$field3->label= 'Created Time';
		$field3->table = 'vtiger_crmentity';
		$field3->column = 'createdtime';
		$field3->uitype = 70;
		$field3->typeofdata = 'T~O';
		$field3->displaytype= 2;
		$block->addField($field3);

		$field4 = new Vtiger_Field();
		$field4->name = 'modifiedtime';
		$field4->label= 'Modified Time';
		$field4->table = 'vtiger_crmentity';
		$field4->column = 'modifiedtime';
		$field4->uitype = 70;
		$field4->typeofdata = 'T~O';
		$field4->displaytype= 2;
		$block->addField($field4);

		// Create default custom filter (mandatory)
		$filter1 = new Vtiger_Filter();
		$filter1->name = 'All';
		$filter1->isdefault = true;
		$module->addFilter($filter1);
		// Add fields to the filter created
		$filter1->addField($field1)->addField($field2, 1)->addField($field3, 2);

		// Set sharing access of this module
		$module->setDefaultSharing();

		// Enable and Disable available tools
		$module->enableTools(Array('Import', 'Export', 'Merge'));

		// Initialize Webservice support
		$module->initWebservice();

		// Create files
		$this->createFiles($module, $field1);

		// Link to menu
		Settings_MenuEditor_Module_Model::addModuleToApp($module->name, $module->parent);
	}

	protected function createFiles(Vtiger_Module $module, Vtiger_Field $entityField) {
		$targetpath = 'modules/' . $module->name;

		if (!is_file($targetpath)) {
			mkdir($targetpath);
			mkdir($targetpath . '/language');

			$templatepath = 'vtlib/ModuleDir/6.0.0';

			$moduleFileContents = file_get_contents($templatepath . '/ModuleName.php');
			$replacevars = array(
				'ModuleName'   => $module->name,
				'<modulename>' => strtolower($module->name),
				'<entityfieldlabel>' => $entityField->label,
				'<entitycolumn>' => $entityField->column,
				'<entityfieldname>' => $entityField->name,
			);

			foreach ($replacevars as $key => $value) {
				$moduleFileContents = str_replace($key, $value, $moduleFileContents);
			}
			file_put_contents($targetpath.'/'.$module->name.'.php', $moduleFileContents);
		}
	}

}

class Vtiger_Tools_Console_LayoutController extends Vtiger_Tools_Console_Controller {

	// Similar grouped patterns to identify the line on which tpl filename is specified.
	const VIEWERREGEX = '/\$viewer->(view|fetch)[^\(]*\(([^\)]+)/';
	const RETURNTPLREGEX = '/(return)([ ]+[\'"]+[^;]+)/';

	const TPLREGEX    = '/[\'"]([^\'"]+)/';

	public function handle() {
		echo ">>> LAYOUT <<<\n";

		$layoutInformation = array();
		do {
			$layoutInformation['name'] = strtolower($this->prompt("Enter layout name: ", self::PROMPT_NAME));
			if (!file_exists( 'layouts/' . $layoutInformation['name'])) {
				break;
			}
			echo "ERROR: " . $layoutInformation['name'] . " already exists, try another.\n";
		} while (true);

		echo "Creating ...";
		$this->create($layoutInformation);
		echo "DONE.\n";
	}

	protected function create($layoutInformation) {
		$files = array();
		$this->findFiles( 'includes', '.php$', $files);
		$this->findFiles( 'modules', '.php$', $files);

		$layoutdir =  'layouts/' . $layoutInformation['name'] . '/';

		foreach ($files as $file) {
			$tplfolder = $layoutdir . "modules/Vtiger";
			if (preg_match("/modules\/([^\/]+)\/([^\/]+)\//", $file, $fmatch)) {
				$tplfolder = $layoutdir . "modules/" . $fmatch[1];
				if ($fmatch[1] == 'Settings') {
					$tplfolder .= '/' . $fmatch[2];
				}
			}

			$tpls = array();
			$this->findTemplateNames($file, $tpls);
			$tpls = array_unique($tpls);

			if ($tpls) {
				foreach ($tpls as $tpl) {
					$tplname = basename($tpl, true);
					// Fix sub-directory path
					$tplpath = $tplfolder . '/'. substr($tpl, 0, strpos($tpl, $tplname));
					if (!file_exists($tplpath)) {
						mkdir($tplpath, 0755, true);
					}
					if (!file_exists($tplpath.$tplname)) {
						$initialContent = "{* License Text *}\n";
						// Enable debug to make it easy to implement.
						$initialContent.= "{debug}{* REMOVE THIS LINE AFTER IMPLEMENTATION *}\n\n";
						file_put_contents($tplpath.$tplname, $initialContent);
					}
					file_put_contents($tplpath.$tplname, "{* $file *}\n", FILE_APPEND);
				}
			}
		}
	}

	protected function findTemplateNames($file, &$tpls, $inreturn=false) {
		$contents = file_get_contents($file);

		$regex = $inreturn ? self::RETURNTPLREGEX : self::VIEWERREGEX;
		if (preg_match_all($regex, $contents, $matches)) {
			foreach ($matches[2] as $match) {
				if (preg_match(self::TPLREGEX, $match, $matches2)) {
					if (stripos($matches2[1], '.tpl') !== false) {
						$tpls[] = $matches2[1];
					}
				}
			}
			// Viewer files can also have return tpl calls - find them.
			if (!$inreturn) {
				$this->findTemplateNames($file, $tpls, true);
			}
		}
	}
}

class Vtiger_Tools_Console_LanguageController extends Vtiger_Tools_Console_Controller {

	const BASE_LANG_PREFIX = 'en_us';

	public function handle() {
		echo ">>> LANGUAGE <<<\n";

		$languageInformation = array();
		do {
			$languageInformation['prefix'] = strtolower($this->prompt("Enter (languagecode_countrycode): ", self::PROMPT_NAME));
			if (!file_exists( 'languages/' . $languageInformation['prefix'])) {
				break;
			}
			echo "ERROR: " . $languageInformation['prefix'] . " already exists, try another.\n";
		} while (true);

		echo "Creating ...";
		$this->create($languageInformation);
		echo "DONE.\n";
	}

	protected function create($languageInformation) {
		$files = array();
		$this->findFiles( 'languages/'.self::BASE_LANG_PREFIX, '.php$', $files);

		foreach ($files as $file) {
			$filename = basename($file, true);
			$dir = substr($file, 0, strpos($file, $filename));
			$dir = str_replace('languages/'.self::BASE_LANG_PREFIX, 'languages/'.$languageInformation['prefix'], $dir);
			if (!file_exists($dir)) mkdir($dir);

			if (isset($languageInformation['prefix_value'])) {
				$contents = file_get_contents($file);
				$contents = preg_replace("/(=>[^'\"]+['\"])(.*)/", sprintf('$1%s$2', $languageInformation['prefix_value']), $contents);
				file_put_contents($dir.'/'.$filename, $contents);
			} else {
				copy($file, $dir.'/'.$filename);
			}
		}
	}

	protected function deploy($languageInformation) {
		if (!isset($languageInformation['label'])) {
			echo "Language label not specified.";
			return;
		}

		$db = PearDatabase::getInstance();
		$check = $db->pquery('SELECT 1 FROM vtiger_language WHERE prefix=?', $languageInformation['prefix']);
		if ($check && $db->num_rows($check)) {
			;
		} else {
			$db->pquery('INSERT INTO vtiger_language (id,name,prefix,label,lastupdated,isdefault,active) VALUES(?,?,?,?,?,?,?)',
					array($db->getUniqueId('vtiger_language'), $languageInformation['label'], $languageInformation['prefix'],
							$languageInformation['label'], date('Y-m-d H:i:s'), 0, 1));
		}
	}
}

class Vtiger_Tools_Console_TestLanguageController extends Vtiger_Tools_Console_LanguageController {

	public function handle() {
		echo ">>> TEST LANGUAGE <<<\n";

		$languageInformation = array('label' => 'TEST', 'prefix' => 'te_st', 'prefix_value' => '✔ ');

		echo "Creating ...";
		$this->create($languageInformation);
		echo "DONE\n";

		echo "Deploying ...";
		$this->deploy($languageInformation);
		echo "DONE.\n";
	}
}

class Vtiger_Tools_Console_ImportController extends Vtiger_Tools_Console_Controller {
	
	public function handle() {
		if ($this->interactive) {
			echo ">>> IMPORT MODULE <<<\n";
			do {
				$path = $this->prompt("Enter package path: ", self::PROMPT_PATH);
				if (file_exists($path)) {
					break;
				}
				echo "ERROR: " . $path . " - file not found, try another.\n";
			} while (true);
		} else {
			$path = array_shift($this->arguments);
		}
		
		if (file_exists($path)) {
			$package = new Vtiger_Package();
			$module  = $package->getModuleNameFromZip($path);
			
			$moduleInstance = Vtiger_Module::getInstance($module);
			if ($moduleInstance) {
				echo "ERROR: Module $module already exists!\n";
			} else {
				echo "Importing ...";
				$package->import($path);
				echo "DONE.\n";
			}			
			
		} else {
			throw new Exception("Package file $path not found.");
		}
		
	}
}

class Vtiger_Tools_Console_UpdateController extends Vtiger_Tools_Console_Controller {
	
	public function handle() {
		if ($this->interactive) {
			echo ">>> UPDATE MODULE <<<\n";
			do {
				$path = $this->prompt("Enter package path: ", self::PROMPT_PATH);
				if (file_exists($path)) {
					break;
				}
				echo "ERROR: " . $path . " - file not found, try another.\n";
			} while (true);
		} else {
			$path = array_shift($this->arguments);
		}
		
		if (file_exists($path)) {
			$package = new Vtiger_Package();
			$module  = $package->getModuleNameFromZip($path);
			
			$moduleInstance = Vtiger_Module::getInstance($module);
			if (!$moduleInstance) {
				echo "ERROR: Module $module not found!\n";
			} else {
				echo "Updating ...";
				$package->update($moduleInstance, $path);
				echo "DONE.\n";
			}			
			
		} else {
			throw new Exception("Package file $path not found.");
		}
		
	}
}

class Vtiger_Tools_Console_RemoveController extends Vtiger_Tools_Console_Controller {
	
	public function handle() {
		if ($this->interactive) {
			echo ">>> REMOVE MODULE <<<\n";
			do {
				$module = $this->prompt("Enter module name: ", self::PROMPT_NAME);
				$moduleInstance = Vtiger_Module::getInstance($module);
				if (!$moduleInstance) {
					echo "ERROR: Module $module not found, try another.\n";
				} else {
					echo "Removing ...";
					$moduleInstance->delete();
					echo "DONE.\n";
				}
			} while (true);
		} else {
			$module = array_shift($this->arguments);
			$moduleInstance = Vtiger_Module::getInstance($module);
			if (!$moduleInstance) {
				echo "ERROR: Module $module not found!\n";
			} else {
				echo "Removing ...";
				$moduleInstance->delete();
				echo "DONE.\n";
			}			
		}
	}
}

if (php_sapi_name() == 'cli') {
	Vtiger_Tools_Console_Controller::run();
} else {
	echo "Usage: php -f vtlib/tools/creator.php";
}
