Files
EasySoft-ZenTaoPMS/test/lib/yaml.class.php
2025-09-30 08:49:48 +08:00

715 lines
22 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 本文件主要进行生成每个脚本文件对应的测试数据yaml文件。
*
* All request of entries should be routed by this router.
*
* @copyright Copyright 2009-2017 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
* @license ZPL (http://zpl.pub/page/zplv12.html)
* @author liyang <liyang@easycorp.ltd>
* @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 <liyang@easycorp.ltd>
* @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);
}