<?php
 namespace TCMS\Gen;
 use Config;
 use TCMS\Model;
 class TCMSGenMigration {
 public function createMigration($options) {
 if(isset($options['help'])) {
 $this->showHelpCreateMigration();
 return true;
 }
 $appId = readAppId();
 if(!$appId) {
 showLogError("Không tìm thấy APP_ID trong file public/index.php");
 return false;
 }
 include "env-function.php";
 setEnvData(ROOT_PATH, $appId);
 $migrationName = $this->formatMigrationName($options);
 if(!$migrationName) {
 showLogError("Vui lòng nhập tên migration cần tạo.");
 showLogInfo("Nhập --help để xem hướng dẫn tạo migration");
 return false;
 }
 $fileName = date('d-m-Y-His') . '-' . $migrationName . '.php';
 $filePath = ROOT_PATH . 'apps' . DIRECTORY_SEPARATOR . $appId . DIRECTORY_SEPARATOR . 'migrations' . DIRECTORY_SEPARATOR;
 if(!file_exists($filePath)) {
 mkdir($filePath);
 }
 $filePath .= $fileName;
 $configPath = ROOT_PATH . 'apps' . DIRECTORY_SEPARATOR . $appId . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR;
 $className = $this->getMigrationClassName($migrationName);
 $tableName = $this->getMigrationTableName($options);
 $model = $this->getMigrationModel($options, $appId);
 $this->createMigrationFile($filePath, $migrationName, $className, $tableName, $model, $configPath);
 }
 public function updateMigration($options) {
 $appId = readAppId();
 if(!$appId) {
 showLogError("Không tìm thấy APP_ID trong file public/index.php");
 return false;
 }
 $this->migrationProcess($appId, $options, false);
 }
 public function rollBackMigration($options) {
 $appId = readAppId();
 if(!$appId) {
 showLogError("Không tìm thấy APP_ID trong file public/index.php");
 return false;
 }
 $this->migrationProcess($appId, $options, true);
 }
 private function migrationProcess($appId, $options, $isRollback = false) {
 include_once LIB_PATH . 'models' . DIRECTORY_SEPARATOR . 'Db.php';
 include_once LIB_PATH . 'app.php';
 include "env-function.php";
 setEnvData(ROOT_PATH, $appId);
 define('APP_ID', $appId);
 define('CONFIG_PATH', ROOT_PATH . 'apps' . DIRECTORY_SEPARATOR . APP_ID . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR);
 define('MODULE_PATH', ROOT_PATH . 'modules' . DIRECTORY_SEPARATOR);
 define('EXTENSION_PATH', ROOT_PATH . 'extensions' . DIRECTORY_SEPARATOR);
 define('MIGRATION_PATH', ROOT_PATH . 'apps' . DIRECTORY_SEPARATOR . APP_ID . DIRECTORY_SEPARATOR . 'migrations' . DIRECTORY_SEPARATOR);
 define('APP_PATH_M', ROOT_PATH . 'apps' . DIRECTORY_SEPARATOR . APP_ID . DIRECTORY_SEPARATOR);
 $app = new \TCmsApplication();
 $app->initAppConfig();
 \Config::$dbConfig = require_once(CONFIG_PATH . 'db.php');
 $isMongodb = \Config::$dbConfig['driver'] == 'mongodb';
 if($isRollback && $isMongodb) {
 showLogError('Mongodb ko hỗ trợ rollback!');
 return;
 }
 include_once LIB_PATH . 'migrations' . DIRECTORY_SEPARATOR . 'TCmsMigrationDb.php';
 include_once LIB_PATH . 'migrations' . DIRECTORY_SEPARATOR . 'TCmsMigration.php';
 set_error_handler(function($errno, $errstr, $errfile, $errline) {
 releaseApp();
 showLogError($errstr);
 }
);
 set_exception_handler(function($exception) {
 releaseApp();
 if(strpos($exception . "", "php_network_getaddresses: getaddrinfo failed") !== false) {
 showLogError("Kết nối database thất bại, vui lòng kiểm tra lại thông số kết nối");
 print_r(Config::$dbConfig);
 }
 else {
 showLogError($exception);
 }
 }
);
 $db = new \TCMS\Migration\MigrationDb();
 $migration = new \TCMS\TCMSMigration();
 if(!$isMongodb) {
 $migration->createMigrationTable($db);
 $db->__cur_version = $migration->getNextMigrationVersionIndex($db);
 }
 else {
 define('APP_PATH', ROOT_PATH . 'apps'. DIRECTORY_SEPARATOR . $appId . DIRECTORY_SEPARATOR);
 $lastUpdateTime = false;
 if(file_exists(MIGRATION_PATH . 'last-update.txt')) {
 $dateTime = trim(file_get_contents(MIGRATION_PATH . 'last-update.txt'));
 if($dateTime) {
 try {
 $ymd = \DateTime::createFromFormat('Y-m-d H:i:s', $dateTime);
 $lastUpdateTime = $ymd->getTimestamp();
 }
 catch(\Exception $ex) {
 $lastUpdateTime = false;
 }
 }
 }
 }
 $db->_isMongodb = $isMongodb;
 $db->_pathToMigration = MIGRATION_PATH;
 if(!$isRollback) {
 $files = scandir(MIGRATION_PATH);
 }
 else {
 $fileName = false;
 if(isset($options['migration:rollback'])) {
 $fileName = $options['migration:rollback'];
 }
 $files = $migration->getAllFileInCurVersion($db, $fileName);
 }
 foreach ($files as $f) {
 $lenght = strlen($f);
 if (strpos($f, ".php") === $lenght - 4) {
 $fileName = basename($f);
 if(!$isMongodb) {
 if ($isRollback === false && $migration->isExistFile($db, $fileName)) {
 continue;
 }
 }
 else {
 if($lastUpdateTime) {
 try {
 $dateTime = substr($fileName, 0, 17);
 $ymd = \DateTime::createFromFormat('d-m-Y-His', $dateTime);
 if($ymd->getTimestamp() <= $lastUpdateTime) continue;
 }
 catch(\Exception $ex) {
 }
 }
 }
 $file = MIGRATION_PATH . $f;
 $className = false;
 try {
 $fn = fopen($file, "r");
 while (!feof($fn)) {
 $line = fgets($fn);
 $line = trim($line);
 if (strpos($line, "class") === 0) {
 $line = trim(substr($line, 5));
 $index = strpos($line, " extends ");
 if ($index !== -1) {
 $className = substr($line, 0, $index);
 }
 break;
 }
 }
 fclose($fn);
 }
 catch (\Exception $e) {
 }
 if($className) {
 include_once $file;
 $db->__file_name = $fileName;
 $table = "\\Migration\\" . $className;
 $table = new $table();
 $table->run($db, $isRollback);
 }
 else {
 showLogError("Không tìm thấy tên class trong file $fileName");
 }
 }
 }
 if($isRollback) {
 $fileName = false;
 if(isset($options['migration:rollback'])) {
 $fileName = $options['migration:rollback'];
 }
 $migration->deleteAllFileInVersion($db, $fileName);
 }
 }
 private function createMigrationFile($filePath, $migrationName, $className, $tableName, $model, $configPath) {
 $content = "<?php\n";
 $content .= "namespace Migration;\n\nuse TCMS\Migration;\nuse TCMS\Migration\TableColumn;\n\n";
 $content .= "class $className extends Migration {\n\n";
 $content .= "\t" . 'public function up(\\TCMS\\Migration\\MigrationDb $db) {' . "\n";
 $dbConfig = include($configPath . 'db.php');
 $dbDriver = $dbConfig['driver'];
 $isMongodb = $dbConfig['driver'] == 'mongodb';
 if($model) {
 if(!$isMongodb) {
 $content .= "\t\t" . '$db->create(\'' .$model->tableName. '\', function(TableColumn $tableColumn) {' . "\n";
 $content .= "\t\t\t" . '$tableColumn->int(\'id\')->autoIncrement()->primaryKey();';
 }
 else {
 $content .= "\t\t" . '$db->update(\'' .$model->tableName. '\', function(TableColumn $tableColumn) {' . "\n";
 }
 $filelds = $model->getFields();
 foreach($filelds as $fileld) {
 if($fileld->name == 'id' || $fileld->isCustomField) continue;
 if($fileld instanceof \TCMS\FieldModel) {
 $s = ($fileld->required ? '->nullable(false)' : '');
 if($fileld->defaultValue !== null && $fileld->defaultValue === false || $fileld->defaultValue === true) {
 $fileld->defaultValue = $fileld->defaultValue ? "1" : "0";
 }
 switch($fileld->type) {
 case \FIELD_DATA_TYPE::INTEGER: case \FIELD_DATA_TYPE::PRICE: case \FIELD_DATA_TYPE::SELECT: case \FIELD_DATA_TYPE::CHECKBOX_LIST: case \FIELD_DATA_TYPE::RADIO_LIST: $s .= ($fileld->defaultValue !== null ? "->default($fileld->defaultValue)" : "");
 $content .= "\n\t\t\t" . "\$tableColumn->int('$fileld->name')$s;";
 break;
 case \FIELD_DATA_TYPE::BIGINT: $s .= ($fileld->defaultValue !== null ? "->default($fileld->defaultValue)" : "");
 $content .= "\n\t\t\t" . "\$tableColumn->bigint('$fileld->name')$s;";
 break;
 case \FIELD_DATA_TYPE::STRING: case \FIELD_DATA_TYPE::JSON: case \FIELD_DATA_TYPE::PASSWORD: case \FIELD_DATA_TYPE::EMAIL: case \FIELD_DATA_TYPE::FILE: case \FIELD_DATA_TYPE::FILE_LIST: case \FIELD_DATA_TYPE::IMAGE: case \FIELD_DATA_TYPE::IMAGE_LIST: case \FIELD_DATA_TYPE::PHONE: $s .= (($fileld->defaultValue !== null && is_string($fileld->defaultValue)) ? "->default('$fileld->defaultValue')" : "");
 if($fileld->validate && isset($fileld->validate['max'])) {
 if(is_array($fileld->validate['max']) && isset($fileld->validate['max']['value'])) {
 $s .= '->length(' . $fileld->validate['max']['value'] . ')';
 }
 else {
 $s .= '->length(' . $fileld->validate['max'] . ')';
 }
 }
 else {
 $s .= "";
 }
 if($isMongodb && $fileld->type == \FIELD_DATA_TYPE::STRING) {
 if(strrpos(strtolower($fileld->name), 'id') !== false) {
 $content .= "\n\t\t\t" . "\$tableColumn->objectId('$fileld->name')$s;";
 }
 else {
 $content .= "\n\t\t\t" . "\$tableColumn->string('$fileld->name')$s;";
 }
 }
 else {
 $content .= "\n\t\t\t" . "\$tableColumn->string('$fileld->name')$s;";
 }
 break;
 case \FIELD_DATA_TYPE::TEXT: $s .= ($fileld->defaultValue !== null ? "->default('$fileld->defaultValue')" : "");
 $content .= "\n\t\t\t" . "\$tableColumn->text('$fileld->name')$s;";
 break;
 case \FIELD_DATA_TYPE::BOOLEAN: $s .= ($fileld->defaultValue !== null ? "->default(" . ($fileld->defaultValue ? 1 : 0) . ")" : "");
 $content .= "\n\t\t\t" . "\$tableColumn->boolean('$fileld->name')$s;";
 break;
 case \FIELD_DATA_TYPE::DATETIME: $content .= "\n\t\t\t" . "\$tableColumn->dateTime('$fileld->name')$s;";
 break;
 case \FIELD_DATA_TYPE::DOUBLE: $s .= ($fileld->defaultValue !== null ? "->default($fileld->defaultValue)" : "");
 $content .= "\n\t\t\t" . "\$tableColumn->double('$fileld->name')$s;";
 break;
 case \FIELD_DATA_TYPE::DATE: $content .= "\n\t\t\t" . "\$tableColumn->date('$fileld->name')$s;";
 break;
 case \FIELD_DATA_TYPE::TIME: $content .= "\n\t\t\t" . "\$tableColumn->time('$fileld->name')$s;";
 break;
 }
 }
 }
 $content .= "\n\t\t});";
 }
 else if($tableName) {
 $content .= "\t\t" . '$db->create(\'' .$tableName. '\', function(TableColumn $tableColumn) {' . "\n";
 $content .= "\t\t\t" . '$tableColumn->int("id")->primaryKey();';
 $content .= "\n\t\t});";
 }
 $content .= "\n\t}";
 $content .= "\n\n\t" . 'public function down(\\TCMS\\Migration\\MigrationDb $db) {' . "\n";
 if(!$isMongodb) {
 if($model) {
 $content .= "\t\t" . '$db->drop(\'' .$model->tableName. '\');';
 }
 else if($tableName) {
 $content .= "\t\t" . '$db->drop(\'' .$tableName. '\');';
 }
 }
 $content .= "\n\t}\n\n";
 $content .= "\tpublic function seed() {\n";
 $content .= "\n\t}";
 $content .= "\n\n}";
 $fp = fopen($filePath, 'w');
 fwrite($fp, $content);
 fclose($fp);
 showLogSuccess("Tạo thành công migration $filePath");
 }
 private function formatMigrationName($options) {
 if(!isset($options['make:migration'])) {
 return false;
 }
 $migrationName = $options['make:migration'];
 $migrationName = strtolower($migrationName);
 $migrationName = preg_replace('/[^a-z0-9]/i', '-', $migrationName);
 $migrationName = str_replace('--', '-', $migrationName);
 $migrationName = str_replace('--', '-', $migrationName);
 $migrationName = str_replace('.php', '', $migrationName);
 return $migrationName;
 }
 private function getMigrationClassName($migrationName) {
 $className = '';
 $arr = explode('-', $migrationName);
 foreach($arr as $v) {
 if($v != "-") {
 $className .= strtoupper(substr($v, 0, 1)) . substr($v, 1);
 }
 }
 return $className . '_' . date('dmYHis');
;
 }
 private function getMigrationTableName($options) {
 if(isset($options['table'])) {
 return $options['table'];
 }
 return false;
 }
 private function getMigrationModel($options, $appId) {
 if(isset($options['model'])) {
 $modelName = $options['model'];
 if(strripos($modelName, "Model") === false) {
 $modelName .= "Model";
 }
 if(strripos($modelName, ".php") === false) {
 $modelClassName = $modelName;
 $modelName .= ".php";
 }
 else {
 $modelClassName = substr($modelName, 0, strlen($modelName) - 4);
 }
 $modelName = strtoupper(substr($modelName, 0, 1)) . substr($modelName, 1);
 if(isset($options['private'])) {
 $modelPath = ROOT_PATH . 'apps' . DIRECTORY_SEPARATOR . $appId . DIRECTORY_SEPARATOR . 'models' . DIRECTORY_SEPARATOR . $modelName;
 }
 else {
 if(isset($options['module'])) {
 $modelPath = ROOT_PATH . 'modules' . DIRECTORY_SEPARATOR . $options['module'] . DIRECTORY_SEPARATOR . 'models' . DIRECTORY_SEPARATOR . $modelName;
 }
 else {
 $modelPath = ROOT_PATH . 'models' . DIRECTORY_SEPARATOR . $modelName;
 }
 }
 if(!file_exists($modelPath)) {
 showLogError("Không tìm thấy model $modelPath");
 exit();
 }
 $modelNamespace = $this->getNamespaceModel($modelPath);
 if(!$modelNamespace) {
 showLogError("Đọc model namespace thất bại. Vui lòng kiểm tra lại!");
 exit();
 }
 try {
 $model = $modelNamespace . "\\" . $modelClassName;
 require_once(LIB_PATH . 'global.class.php');
 require_once(LIB_PATH . 'models' . DIRECTORY_SEPARATOR . 'TCmsModel.php');
 require_once($modelPath);
 $model = new $model();
 if($model instanceof Model) {
 return $model;
 }
 else {
 showLogError("$modelName không phải là model");
 exit();
 }
 }
 catch(\Exception $ex) {
 showLogError("Tạo model " . ($modelNamespace . "\\" . $modelClassName) . " thất bại. Tên model phải trùng với tên file");
 exit();
 }
 }
 return false;
 }
 private function getNamespaceModel($modelPath) {
 if(file_exists($modelPath)) {
 try {
 $modelNamespace = false;
 $fn = fopen($modelPath, "r");
 if($fn) {
 while (!feof($fn)) {
 $line = fgets($fn);
 $line = trim($line);
 $startIndex = strpos($line, "namespace ");
 if($startIndex !== false) {
 $startIndex += 10;
 $endIndex = strpos($line, ";", $startIndex);
 $modelNamespace = trim(substr($line, $startIndex, $endIndex - $startIndex));
 break;
 }
 }
 fclose($fn);
 }
 return $modelNamespace;
 }
catch(\Exception $ex) {
 }
 }
 return false;
 }
 public function showHelpCreateMigration() {
 drawLine();
 echo "Description:\n";
 showLogInfo("  Tự động tạo một migration trong thư mục app\[app-id]\migrations");
 echo "\nFormat:\n";
 echo "  make:migration [migration-name] [options]\n\n";
 echo "Required:\n";
 showHelpTextColor("[migration-name]", "Tên migration cần tạo. VD: create-table-product");
 echo "\nOptions:\n";
 showHelpTextColor("-m, --model", "Tự thêm các field đã đăng ký trong model vào migration\n");
 showHelpTextColor("--module", "Nếu cần lấy model trong module, thì truyền thêm thống số này vào, bao gồm cả area\n");
 showHelpTextColor("-p, --private", "\tNếu model là private\n");
 showHelpTextColor("--table", "Tên table trong database\n");
 drawLine();
 }
 }
 