* @package ZenTaoPMS * @version 1.0 * @link http://www.zentao.net/ */ class fields { /** * Field Arr. * * @var array * @access public */ public $fieldArr = array(); /** * Field. * * @var int * @access private */ private $field; /** * Set yaml field. * * @param string $value * @access public * @return object */ public function setField($value) { $this->fieldArr[$value] = array(); $this->field = $value; return $this; } /** * Set field rang. * * @param string $range * @access public * @return object */ public function range($range) { $this->fieldArr[$this->field]['range'] = $range; return $this; } /** * Set field prefix. * * @param string $prefix * @access public * @return object */ public function prefix($prefix) { $this->fieldArr[$this->field]['prefix'] = $prefix; return $this; } /** * Set a flag to unset field. * * @access public * @return void */ public function setNull() { $this->fieldArr[$this->field]['null'] = true; return $this; } /** * Set field postfix. * * @param string $postfix * @access public * @return object */ public function postfix($postfix) { $this->fieldArr[$this->field]['postfix'] = $postfix; return $this; } /** * Set field type. * * @param string $type * @access public * @return object */ public function type($type) { $this->fieldArr[$this->field]['type'] = $type; return $this; } /** * Set field format. * * @param string $format * @access public * @return object */ public function format($format) { $this->fieldArr[$this->field]['format'] = $format; return $this; } /** * Set field fields. * * @param array $fields * @access public * @return object */ public function setFields($fields) { if(!is_array($fields)) { echo "fields must be an array"; return; } $this->fieldArr[$this->field]['fields'] = $fields; return $this; } /** * Get field array. * * @access public * @return array */ public function getFields() { return $this->fieldArr; } /** * Assembly field generation rules. * * @param array $fieldArr * @access public * @return array */ public function setFieldRule($fieldArr) { $ruleArr = array(); $index = 0; foreach($fieldArr as $field => $rule) { $ruleArr[$index]['field'] = $field; if(array_key_exists('fields', $rule)) { $ruleArr[$index]['fields'] = $this->setFieldRule($rule['fields']); } else { if(isset($rule['range'])) $ruleArr[$index]['range'] = $rule['range']; } if(isset($rule['prefix'])) $ruleArr[$index]['prefix'] = $rule['prefix']; if(isset($rule['postfix'])) $ruleArr[$index]['postfix'] = $rule['postfix']; if(isset($rule['type'])) $ruleArr[$index]['type'] = $rule['type']; if(isset($rule['format'])) $ruleArr[$index]['format'] = $rule['format']; if(isset($rule['null'])) $ruleArr[$index]['null'] = $rule['null']; $index ++; } return $ruleArr; } } /** * Create test data from yaml file. * * @copyright Copyright 2009-2022 QingDao Nature Easy Soft Network Technology Co,LTD (www.cnezsoft.com) * @author liyang * @package * @uses field * @license LGPL * @version 1.0 * @Link https://www.zentao.net */ class yaml { /** * Set fields for yaml file. * * @var object * @access public */ public $fields; /** * Global config. * * @var object * @access public */ public $config; /** * $dao对象,用于访问或者更新数据库。 * The $dao object, used to access or update database. * * @var dao * @access public */ public $dao; /** * The generated data table name. * * @var string * @access public */ public $tableName; /** * The config files will be merged. * * @var string[] * @access private */ private $configFiles = array(); /** * __construct function load config and tableName. * @param string $tableName * @access public * @return void */ public function __construct($tableName) { global $config, $tester, $uiTester; $this->config = $config; $this->dao = isset($tester->dao) ? $tester->dao : $uiTester->dao; $this->tableName = $tableName; $this->fields = new fields(); $yamlFile = dirname(dirname(__FILE__)) . "/data/{$this->tableName}.yaml"; $this->configFiles[] = $yamlFile; if(!file_exists($yamlFile)) $this->buildYamlFile($this->tableName); } /** * Build the initial yaml file. * * @param string $tableName * @access public * @return void */ public function buildYamlFile($tableName) { $yamlData['title'] = 'table zt_' . $tableName; $yamlData['author'] = 'automated export'; $yamlData['version'] = '1.0'; $yamlData['fields'][0]['field'] = 'id'; $yamlData['fields'][0]['range'] = '1-1000'; $yamlFile = dirname(dirname(__FILE__)) . "/data/{$this->tableName}.yaml"; yaml_emit_file($yamlFile, $yamlData, YAML_UTF8_ENCODING); } /** * Yaml configuration file for script。 * * @param string $fileName * @param bool $useCommon * @access public * @return object $this */ public function loadYaml($fileName, $useCommon = false, $depths = 2) { $backtrace = debug_backtrace(); $runPath = $backtrace[count($backtrace)-1]['file']; if($useCommon) { $yamlFile = dirname($runPath, $depths) . DS . 'yaml' . DS . "{$fileName}.yaml"; if(is_file($yamlFile)) $this->configFiles[] = $yamlFile; return $this; } $runFileName = str_replace(strrchr($_SERVER['SCRIPT_FILENAME'], "."), "", $_SERVER['SCRIPT_FILENAME']); $pos = strripos($runFileName, DS); if($pos !== false) $runFileName = mb_substr($runFileName, $pos+1); $yamlFile = dirname($runPath) . DS . 'yaml' . DS . $runFileName . DS . "{$fileName}.yaml"; /* Try to load common yaml file if yaml file not found in $runFileName path.*/ if(!is_file($yamlFile)) $yamlFile = dirname($runPath, $depths) . DS . 'yaml' . DS . "{$fileName}.yaml"; if(is_file($yamlFile)) $this->configFiles[] = $yamlFile; return $this; } /** * Magic method, return fild. * * @param string $property_name * @access protected * @return object */ public function __get($property_name) { $this->fields->setField($property_name); return $this->fields; } /** * 合并原始yaml与用户自定义yaml文件 * Merege yaml file. * * @param string $runFileDir * @param string $runFileName * @access private * @return string */ private function mergeYaml($runFileDir, $runFileName) { $mergeData = array('fields' => array()); foreach($this->configFiles as $configFile) { $configData = yaml_parse_file($configFile); $configData['title'] = $configData['title']; $configData['author'] = $configData['author']; $configData['version'] = $configData['version']; foreach($configData['fields'] as $configItem) { $field = $configItem['field']; $mergeData['fields'][$field] = $configItem; } } if(!is_dir("{$runFileDir}/data")) mkdir("{$runFileDir}/data", 0777); $yamlFile = "{$runFileDir}/data/{$this->tableName}_{$runFileName}.yaml"; if(!empty($this->fields->fieldArr)) { $fields = $this->fields->setFieldRule($this->fields->fieldArr); foreach($fields as $field) { if(isset($field['null'])) { unset($mergeData['fields'][$field['field']]); continue; } $mergeData['fields'][$field['field']] = $field; } } $mergeData['fields'] = array_values($mergeData['fields']); yaml_emit_file($yamlFile, $mergeData, YAML_UTF8_ENCODING); return $yamlFile; } /** * 查找当前执行脚本的相对路径与文件名 * Find script path and name. * * @access private * @return array */ private function getScriptPathAndName() { $runFileDir = dirname($_SERVER['SCRIPT_FILENAME']); /* 用例中如果使用了chdir改变了运行路径,那么在这里会还原 */ if(strpos(getcwd(), $runFileDir)) chdir(str_replace($runFileDir, '', getcwd())); $runFileName = str_replace(strrchr($_SERVER['SCRIPT_FILENAME'], "."), "", $_SERVER['SCRIPT_FILENAME']); $pos = strripos($runFileName, DS); if($pos !== false) $runFileName = mb_substr($runFileName, $pos+1); return array($runFileDir, $runFileName); } /** * Build yaml file and insert table. * * @param int $rows * @param bool $isClear Truncate table if set isClear to true. * @access public * @return void */ public function gen($rows, $isClear = true, $useCache = true) { list($runFileDir, $runFileName) = $this->getScriptPathAndName(); if($this->tableName == 'company' && $rows === 0) { echo 'the table company is not empty!' . PHP_EOL; return false; } $sqlPath = sprintf("%s%sdata%ssql%s%s_%s_zd.sql", $runFileDir, DS, DS, DS, $this->tableName, $runFileName); $scriptPath = sprintf("%s%s%s.php", $runFileDir, DS, $runFileName); $yamlPath = sprintf("%s%sdata%s%s_%s.yaml", $runFileDir, DS, DS, $this->tableName, $runFileName); if($rows && (!$useCache || $this->checkNeedReGenerateSql($sqlPath, $scriptPath, $yamlPath))) { $runtimeRoot = dirname(dirname(__FILE__)) . '/runtime/'; $zdPath = $runtimeRoot . 'zd'; $configYaml = $runtimeRoot . 'tmp/config.yaml'; $tableName = $this->config->db->prefix . $this->tableName; $yamlFile = $this->mergeYaml($runFileDir, $runFileName); $genSQL = "$zdPath -c %s -d %s -n %d -t %s -o %s 2>&1"; $execGenSQL = sprintf($genSQL, $configYaml, $yamlFile, $rows, $tableName, $sqlPath); exec($execGenSQL, $output, $code); if($code !== 0) { echo $execGenSQL . PHP_EOL; print_r($output); } } elseif(!$rows && $isClear && file_exists($sqlPath) && is_writable($sqlPath)) { unlink($sqlPath); } $dbDriver = $this->config->db->driver; if($dbDriver == 'dm') { $this->insertDM($sqlPath, $this->tableName, $isClear, $rows); } else { $this->insertDB($sqlPath, $this->tableName, $isClear, $rows); } return $this; } /** * 根据脚本以及yaml文件修改时间判断是否需要重新生成sql文件. * Check if it is necessary to regenerate the SQL file. * * @param string $sqlPath * @param string $scriptPath * @param string $yamlPath * @access private * @return bool */ private function checkNeedReGenerateSql($sqlPath, $scriptPath, $yamlPath) { $sqlFileUpdateTime = file_exists($sqlPath) ? filemtime($sqlPath) : 0; $scriptUpdateTime = filemtime($scriptPath); if($sqlFileUpdateTime < $scriptUpdateTime) return true; foreach($this->configFiles as $configFile) { if($sqlFileUpdateTime < filemtime($configFile)) return true; } /* Check if the range function is used in the YAML file */ $content = file_get_contents($yamlPath); if(preg_match('/range:.*?:R\s*(,|$|\n)/', $content)) return true; return false; } /** * Insert the data into database. * * @param string $sqlPath * @param string $tableName * @param bool $isClear Truncate table if set isClear to true. * @param int $rows truncate table only if rows is o. * @access public * @return string */ function insertDB($sqlPath, $tableName, $isClear = true, $rows = null) { $tableName = $this->config->db->prefix . $tableName; $dbName = $this->config->db->name; $dbHost = $this->config->db->host; $dbPort = $this->config->db->port; $dbUser = $this->config->db->user; $dbPWD = $this->config->db->password; $tableSqlDir = dirname($sqlPath); if(!is_dir($tableSqlDir)) mkdir($tableSqlDir, 0777, true); if($isClear === true) { /* Truncate table to reset auto increment number. */ system(sprintf("mysql -u%s -p%s -h%s -P%s %s -e 'truncate %s' 2>/dev/null", $dbUser, $dbPWD, $dbHost, $dbPort, $dbName, $tableName)); if($tableName == $this->config->db->prefix . 'action') system(sprintf("mysql -u%s -p%s -h%s -P%s %s -e 'truncate %s' 2>/dev/null", $dbUser, $dbPWD, $dbHost, $dbPort, $dbName, $this->config->db->prefix . 'actionrecent')); if($rows === 0) return; } if(!file_exists($sqlPath)) return; $command = "mysql -u%s -p%s -h%s -P%s --default-character-set=utf8 -D%s < %s"; $execInsert = sprintf($command, $dbUser, $dbPWD, $dbHost, $dbPort, $dbName, $sqlPath); $this->execWithStderr($execInsert); } /** * Insert the data into dm database. * * @param string $sqlPath * @param string $tableName * @param bool $isClear Truncate table if set isClear to true. * @param int $rows truncate table only if rows is o. * @access public * @return string */ function insertDM($sqlPath, $tableName, $isClear = true, $rows = null) { global $app; $tableName = $this->config->db->prefix . $tableName; $dbName = $this->config->db->name; $dbHost = $this->config->db->host; $dbPort = $this->config->db->port; $dbUser = $this->config->db->user; $dbPWD = $this->config->db->password; $disql = $app->getAppRoot() . 'test/runtime/disql'; $tableSqlDir = dirname($sqlPath); if(!is_dir($tableSqlDir)) mkdir($tableSqlDir, 0777, true); if($isClear === true) { /* Truncate table to reset auto increment number. */ shell_exec(sprintf("$disql %s/%s@%s:%s -E 'TRUNCATE TABLE %s.%s;' 2>/dev/null", $dbUser, $dbPWD, $dbHost, $dbPort, $dbName, $tableName)); if($tableName == $this->config->db->prefix . 'action') shell_exec(sprintf("$disql %s/%s@%s:%s -E 'TRUNCATE TABLE %s.%s;' 2>/dev/null", $dbUser, $dbPWD, $dbHost, $dbPort, $dbName, $this->config->db->prefix . 'actionrecent')); if($rows === 0) return; } if(!file_exists($sqlPath)) return; $isIdentityCmd = sprintf("$disql %s/%s@%s:%s -E \"SELECT b.TABLE_NAME, a.NAME AS COLUMN_NAME FROM SYS.SYSCOLUMNS a, ALL_TABLES b, SYS.SYSOBJECTS c WHERE a.INFO2 & 0x01 = 0x01 AND a.ID = c.ID AND c.NAME = b.TABLE_NAME AND b.TABLE_NAME = '%s' AND b.OWNER = '%s'\" 2>/dev/null", $dbUser, $dbPWD, $dbHost, $dbPort, $tableName, $dbName); $isIdentity = shell_exec($isIdentityCmd); $isIdentity = strpos($isIdentity, $tableName) !== false; $originSql = file_get_contents($sqlPath); unlink($sqlPath); $sql = 'SET SCHEMA ' . $dbName . ';' . PHP_EOL; $sql .= 'WHENEVER SQLERROR EXIT 1;' . PHP_EOL . 'SET DEFINE OFF;' . PHP_EOL; if($isIdentity) $sql .= 'SET IDENTITY_INSERT ' . $tableName . ' ON;' . PHP_EOL; $sql .= $originSql; $sql .= 'commit;' . PHP_EOL; $sql .= 'exit;' . PHP_EOL; file_put_contents($sqlPath, $sql); $command = "$disql %s/%s@%s:%s \`%s"; $execInsert = sprintf($command, $dbUser, $dbPWD, $dbHost, $dbPort, $sqlPath); $this->execWithStderr($execInsert . ' >/dev/null'); } /** * 执行系统命令,并捕捉错误。 * exec command and catch stderror. * * @param string $cmd * @access private * @return void */ private function execWithStderr($cmd) { $proc = proc_open($cmd, array(2 => array('pipe', 'w')), $pipes); $stderr = stream_get_contents($pipes[2]); fclose($pipes[2]); proc_close($proc); if(empty($stderr)) return; $errors = explode(PHP_EOL, $stderr); $errors = array_filter($errors, function($error) { return !empty($error) && !strpos($error, 'Using a password on the command line interface can be insecure'); }); if(!empty($errors)) echo implode(PHP_EOL, $errors) . PHP_EOL . "error cmd: '{$cmd}'" . PHP_EOL; } /** * Restore table data. * * @param string $tableName * @access public * @return mixed */ public function restoreTable($tableName) { $tableSql = "{$_SERVER['PWD']}/data/sql/$tableName.sql"; if(!is_file($tableSql)) return false; $dbName = $this->config->db->name; $dbHost = $this->config->db->host; $dbPort = $this->config->db->port; $dbUser = $this->config->db->user; $dbPWD = $this->config->db->password; $command = "mysql -u%s -p%s -h%s -P%s %s < %s"; $execRestore = sprintf($command, $dbUser, $dbPWD, $dbHost, $dbPort, $dbName, $tableSql); system($execRestore); } /** * Generate grade field and path field according to id field and parent field * * @access public * @return object */ public function fixPath() { if($this->config->db->driver == 'dm') { echo 'error: Cann\'t fix path because the driver is dm.' . PHP_EOL; return exit(1); } $fieldPairs = array(); $table = $this->config->db->prefix . $this->tableName; $tableFields = $this->dao->query("DESC {$table}")->fetchAll(); foreach($tableFields as $field) $fieldPairs[] = $field->Field; /* 任务表没有grade字段。*/ $hasGrade = true; foreach(array('id', 'parent', 'grade', 'path') as $field) { if(in_array($field, $fieldPairs)) continue; if($field == 'grade') { $hasGrade = false; continue; } echo "error: Cann't fix path because the table {$table} doesn't has {$field} column." . PHP_EOL; return $this; } $dataList = array(); $groupDataList = $this->dao->select('id, parent')->from($table)->fetchGroup('parent', 'id'); /* Cycle the groupDataList until it has no item any more. */ while(count($groupDataList) > 0) { $oldCounts = count($groupDataList); // Record the counts before processing. foreach($groupDataList as $parentDataID => $childDataList) { /* If the parentData doesn't exsit in the dataList, skip it. If exists, compute it's child dataList. */ if(!isset($dataList[$parentDataID]) && $parentDataID != 0) continue; if($parentDataID == 0) { $parentData = new stdclass(); $parentData->path = ','; if($hasGrade) $parentData->grade = 0; } else { $parentData = $dataList[$parentDataID]; } /* Compute it's child dataList. */ foreach($childDataList as $childDataID => $childData) { $childData->path = $parentData->path . $childData->id . ','; if($hasGrade) $childData->grade = $parentData->grade + 1; $dataList[$childDataID] = $childData; // Save child data to dataList, thus the child of child can compute it's grade and path. } unset($groupDataList[$parentDataID]); // Remove it from the groupDataList. } if(count($groupDataList) == $oldCounts) break; // If after processing, no data processed, break the cycle. } /* Save dataList to database. */ foreach($dataList as $data) $this->dao->update($table)->data($data)->where('id')->eq($data->id)->limit(1)->exec(); return $this; } } /** * Return yaml class * * @param string $table * @access public * @return mixed */ function zendata($table) { return new yaml($table); }