Merge branch '18.x' into ipd1.1

This commit is contained in:
tanghucheng
2023-09-13 03:23:50 -04:00
1091 changed files with 130798 additions and 4093 deletions
Vendored
+7 -13
View File
@@ -106,6 +106,9 @@ pipeline {
env.GIT_TAG_BUILD_GROUP = sh(returnStdout: true, script: 'misc/parse_tag.sh $TAG_NAME group').trim()
env.GIT_TAGGER_NAME = sh(returnStdout: true, script: 'git for-each-ref --format="%(taggername)" refs/tags/$(git tag --points-at HEAD)').trim()
env.CI_PUBLIC_IMAGE_NAMESPACE = sh(returnStdout: true,script: 'jq -r .image.public.namespace.' + env.GIT_TAG_BUILD_TYPE + ' < ci.json').trim()
env.CI_INTERNAL_IMAGE_NAMESPACE = sh(returnStdout: true,script: 'jq -r .image.internal.namespace.' + env.GIT_TAG_BUILD_TYPE + ' < ci.json').trim()
def ximUsers = sh(returnStdout: true,script: 'jq -r .notice.users < ci.json').trim()
env.XIM_USERS = ximUsers + ',' + env.GIT_TAGGER_NAME
env.XIM_GROUPS = sh(returnStdout: true,script: 'jq -r .notice.groups < ci.json').trim()
@@ -115,13 +118,14 @@ pipeline {
env.MAX_VERSION = sh(returnStdout: true, script: 'cat ${SRC_ZENTAOEXT_PATH}/MAXVERSION').trim()
env.IPD_VERSION = sh(returnStdout: true, script: 'cat ${SRC_ZENTAOEXT_PATH}/IPDVERSION').trim()
env.DOWNGRADE_ENABLED = sh(returnStdout: true, script: 'test -n "${DOWNGRADE_ENABLED}" && echo ${DOWNGRADE_ENABLED} || (jq -r .downgrade.enabled < ci.json)').trim()
env.CI_DOWNGRADE_ENABLED = sh(returnStdout: true, script: 'test -n "${DOWNGRADE_ENABLED}" && echo ${DOWNGRADE_ENABLED} || (jq -r .downgrade.enabled < ci.json)').trim()
env.QINIU_BUCKET = sh(returnStdout: true, script: 'jq -r .upload.bucket < ci.json').trim()
env.ARTIFACT_REPOSITORY = sh(returnStdout: true, script: 'misc/parse_tag.sh $TAG_NAME type | grep release >/dev/null && echo easycorp || echo easycorp-snapshot').trim()
env.ARTIFACT_HOST = "nexus.qc.oop.cc"
env.ARTIFACT_PROTOCOL = "https"
env.ARTIFACT_CRED_ID = "nexus-jenkins"
}
}
}
@@ -140,6 +144,7 @@ pipeline {
container('package') {
sh 'mkdir ${ZENTAO_RELEASE_PATH} && chown 1000:1000 ${ZENTAO_RELEASE_PATH}'
sh 'git config --global pull.ff only'
sh 'cp -av ${ZENTAO_BUILD_PATH}/adminer www/'
sh 'pwd && ls -l && make ciCommon'
sh 'ls -l ${ZENTAO_RELEASE_PATH}'
}
@@ -700,18 +705,7 @@ pipeline {
environment {
REGISTRY_HOST="hub.zentao.net"
CI_BUILD_PUBLIC_IMAGE="""${sh(
returnStdout: true,
script: 'test "$GIT_TAG_BUILD_TYPE" = release && echo true || echo false'
).trim()}"""
CI_PUBLIC_IMAGE_NAMESPACE="""${sh(
returnStdout: true,
script: "echo $GIT_URL | grep demo/zentao >/dev/null && echo test || echo app"
).trim()}"""
CI_INTERNAL_IMAGE_NAMESPACE="""${sh(
returnStdout: true,
script: "echo $GIT_URL | grep demo/zentao >/dev/null && echo test || echo app"
).trim()}"""
CI_BUILD_PUBLIC_IMAGE="true"
}
stages() {
+145
View File
@@ -0,0 +1,145 @@
pipeline {
agent {
kubernetes {
inheritFrom "xuanim"
yamlFile 'misc/ci/normal.yaml'
}
}
options {
skipDefaultCheckout()
}
environment {
TZ="Asia/Shanghai"
ZENTAO_RELEASE_PATH = "${WORKSPACE}/release"
XUANXUAN_SRC_PATH = "${WORKSPACE}/xuansrc"
SRC_ZDOO_PATH = "${WORKSPACE}/zdoo"
SRC_ZDOOEXT_PATH = "${WORKSPACE}/zdooext"
SRC_ZENTAOEXT_PATH = "${WORKSPACE}/zentaoext"
// set to blank for auto-detect from ci.json
DOWNGRADE_ENABLED = "false"
DOWNGRADE_VERSIONS = "7.2,7.1,7.0,5.4"
}
stages {
stage("Test Code") {
agent {
kubernetes {
inheritFrom "zentao-package build-docker xuanim"
yamlFile 'misc/ci/basic-build.yaml'
}
}
stages {
stage("Pull") {
steps {
checkout scm
script {
env.XUANVERSION = sh(returnStdout: true,script: 'jq -r .pkg.xuanxuan.gitVersion < ci.json').trim()
env.ZENTAOEXT_VERSION = sh(returnStdout: true,script: 'jq -r .pkg.zentaoext.gitVersion < ci.json').trim()
env.ZENTAOEXT_GIT_REPO = sh(returnStdout: true,script: 'jq -r .pkg.zentaoext.gitRepo < ci.json').trim()
env.ZDOO_VERSION = sh(returnStdout: true,script: 'jq -r .pkg.zdoo.gitVersion < ci.json').trim()
env.ZDOOEXT_VERSION = sh(returnStdout: true,script: 'jq -r .pkg.zdooext.gitVersion < ci.json').trim()
}
dir('xuansrc') {
checkout scmGit(branches: [[name: "${env.XUANVERSION}"]],
userRemoteConfigs: [[credentialsId: 'git-zcorp-cc-jenkins-bot-http', url: 'https://git.zcorp.cc/easycorp/xuanxuan.git']]
)
}
dir('zdoo') {
checkout scmGit(branches: [[name: "${env.ZDOO_VERSION}"]],
extensions: [cloneOption(depth: 2, noTags: false, reference: '', shallow: true)],
userRemoteConfigs: [[credentialsId: 'git-zcorp-cc-jenkins-bot-http', url: 'https://git.zcorp.cc/easycorp/zdoo.git']]
)
}
dir('zdooext') {
checkout scmGit(branches: [[name: "${env.ZDOOEXT_VERSION}"]],
extensions: [cloneOption(depth: 2, noTags: false, reference: '', shallow: true)],
userRemoteConfigs: [[credentialsId: 'git-zcorp-cc-jenkins-bot-http', url: 'https://git.zcorp.cc/easycorp/zdooext.git']]
)
}
dir('zentaoext') {
checkout scmGit(branches: [[name: "${env.ZENTAOEXT_VERSION}"]],
extensions: [cloneOption(depth: 2, noTags: false, reference: '', shallow: true)],
userRemoteConfigs: [[credentialsId: 'git-zcorp-cc-jenkins-bot-http', url: "${env.ZENTAOEXT_GIT_REPO}"]]
)
}
}
}
stage("Setup Global Env") {
steps {
script {
def ximUsers = sh(returnStdout: true,script: 'jq -r .notice.users < ci.json').trim()
env.XIM_USERS = "qishiyao"
env.XIM_GROUPS = "31a0008b-6e3e-4b7f-9b7b-396a46b1f8f4"
env.PMS_VERSION = sh(returnStdout: true, script: 'cat ${SRC_ZENTAOEXT_PATH}/VERSION').trim()
env.BIZ_VERSION = sh(returnStdout: true, script: 'cat ${SRC_ZENTAOEXT_PATH}/BIZVERSION').trim()
env.MAX_VERSION = sh(returnStdout: true, script: 'cat ${SRC_ZENTAOEXT_PATH}/MAXVERSION').trim()
env.IPD_VERSION = sh(returnStdout: true, script: 'cat ${SRC_ZENTAOEXT_PATH}/IPDVERSION').trim()
}
}
}
stage("Downgrade") {
stages {
stage("make Package") {
steps {
withCredentials([gitUsernamePassword(credentialsId: 'git-zcorp-cc-jenkins-bot-http',gitToolName: 'git-tool')]) {
container('package') {
sh 'mkdir ${ZENTAO_RELEASE_PATH} && chown 1000:1000 ${ZENTAO_RELEASE_PATH}'
sh 'git config --global pull.ff only'
sh 'pwd && ls -l && make ciCommon'
sh 'ls -l ${ZENTAO_RELEASE_PATH}'
}
}
}
}
stage("test downgrade") {
steps {
container('package') {
sh 'unzip -q ZenTaoPMS.${PMS_VERSION}.zip'
sh 'ls -l zentaopms'
sh './misc/downgrade.sh -p "$DOWNGRADE_VERSIONS" -r zentaopms -i -s -o "$ZENTAO_RELEASE_PATH" framework lib module/*'
}
publishHTML([
allowMissing: true,
alwaysLinkToLastBuild: false,
keepAll: true,
reportDir: env.ZENTAO_RELEASE_PATH,
reportFiles: 'downgradeReport.html',
reportName: 'DowngradeReport'
])
container('xuanimbot') {
sh 'git config --global --add safe.directory $(pwd)'
sh 'test -f $ZENTAO_RELEASE_PATH/downgradeReport.html && /usr/local/bin/xuanimbot --title "`echo -n 6ZmN57qn5aSx6LSl | base64 --decode`" --url "${RUN_DISPLAY_URL}" --content "[PHP Syntax Report]($BUILD_URL/DowngradeReport/)" --debug --custom || echo "No syntax found"'
}
}
}
}
} // End Downgrade
}
}
}
}
+5 -1
View File
@@ -157,6 +157,10 @@ zentaoxx:
sed -i "s/'..\/..\/common\/view\/header.html.php'/\$$app->getModuleRoot() . 'common\/view\/header.html.php'/g" zentaoxx/extension/xuan/conference/view/admin.html.php
sed -i "s/'..\/..\/common\/view\/footer.html.php'/\$$app->getModuleRoot() . 'common\/view\/footer.html.php'/g" zentaoxx/extension/xuan/conference/view/admin.html.php
sed -i "s/\$$this->im->userGetChangedPassword()/array()/" zentaoxx/extension/xuan/im/control.php
sed -i "s/->app->getModuleExtPath('', /->app->getModuleExtPath(/g" zentaoxx/extension/xuan/im/model/bot.php
sed -i "s/\$$this->getModuleExtPath('', /\$$this->getModuleExtPath(/g" zentaoxx/framework/xuanxuan.class.php
sed -i "s/, \$$version)\$$/, \$$version = '')/g" zentaoxx/extension/xuan/im/model.php
sed -i "s/, \$$version)\$$/, \$$version = '')/g" zentaoxx/extension/xuan/im/model/conference.php
sed -i "/.*->getAllDepts();/d" zentaoxx/extension/xuan/im/ext/bot/default.bot.php
sed -i "s/lang->user->status/lang->user->clientStatus/" zentaoxx/extension/xuan/im/ext/bot/default.bot.php
sed -i "s/.*->getRoleList();/\$$depts = \$$this->im->loadModel('dept')->getDeptPairs();\n\$$deptList = array_map(function(\$$k, \$$v) {return (object)array('id' => \$$k, 'name' => \$$v);}, array_keys(\$$depts), \$$depts);\n\$$roleList = \$$this->im->lang->user->roleList;/" zentaoxx/extension/xuan/im/ext/bot/default.bot.php
@@ -282,7 +286,7 @@ ciCommon:
zip -rq -9 ZenTaoALM.$(VERSION).int.zip zentaoalm
# downgrade
@test "$(DOWNGRADE_ENABLED)" != "true" && echo "skip downgrade" || ./misc/downgrade.sh -p "$(DOWNGRADE_VERSIONS)" -i -r zentaopms -o "$(RELEASE_PATH)" framework/ module/*
@test "$(DOWNGRADE_ENABLED)" != "true" && echo "skip downgrade" || ./misc/downgrade.sh -p "$(DOWNGRADE_VERSIONS)" -i -r zentaopms -o "$(RELEASE_PATH)" framework/ lib/ module/*
rm -fr zentaopms zentaoalm
+1 -1
View File
@@ -1 +1 @@
18.6
18.7
+1 -1
View File
@@ -25,7 +25,7 @@ class gitlabWebhookEntry extends baseEntry
$this->loadModel('repo');
$repo = $this->repo->getRepoByID($repoID);
$repo = $this->repo->getByID($repoID);
if(empty($repo)) return;
$headers = getallheaders(); /* Fetch all HTTP request headers. */
+5
View File
@@ -108,6 +108,11 @@ class taskEntry extends entry
$fields = 'name,type,desc,assignedTo,pri,estimate,left,consumed,story,parent,execution,module,closedReason,status,estStarted,deadline,team,teamEstimate,teamConsumed,teamLeft,multiple,mailto,uid';
$this->batchSetPost($fields, $oldTask);
if($_POST['status'] == 'done')
{
if(!empty($oldTask->finishedBy)) $this->setPost('finishedBy', $oldTask->finishedBy);
if(!empty($oldTask->finishedDate)) $this->setPost('finishedDate', $oldTask->finishedDate);
}
$control = $this->loadController('task', 'edit');
$control->edit($taskID);
+15 -1
View File
@@ -15,8 +15,22 @@
"gitVersion": "master"
}
},
"image": {
"public": {
"namespace": {
"release": "app",
"snapshot": "test"
}
},
"internal": {
"namespace": {
"release": "app",
"snapshot": "test"
}
}
},
"downgrade": {
"enabled": false,
"enabled": true,
"versions": "7.2,7.1,7.0,5.4"
},
"upload": {
+33 -5
View File
@@ -16,7 +16,7 @@ if(!class_exists('config')){class config{}}
if(!function_exists('getWebRoot')){function getWebRoot(){}}
/* 基本设置。Basic settings. */
$config->version = '18.6'; // ZenTaoPHP的版本。 The version of ZenTaoPHP. Don't change it.
$config->version = '18.7'; // ZenTaoPHP的版本。 The version of ZenTaoPHP. Don't change it.
$config->liteVersion = '1.2'; // 迅捷版版本。 The version of Lite.
$config->charset = 'UTF-8'; // ZenTaoPHP的编码。 The encoding of ZenTaoPHP.
$config->cookieLife = time() + 2592000; // Cookie的生存时间。The cookie life time.
@@ -66,10 +66,7 @@ $config->db->driver = 'mysql'; // 目前只支持MySQL数据库。Mus
$config->db->encoding = 'UTF8'; // 数据库编码。 Encoding of database.
$config->db->strictMode = false; // 关闭MySQL的严格模式。 Turn off the strict mode of MySQL.
$config->db->prefix = 'zt_'; // 数据库表名前缀。 The prefix of the table name.
$config->slaveDB->persistant = false;
$config->slaveDB->driver = 'mysql';
$config->slaveDB->encoding = 'UTF8';
$config->slaveDB->strictMode = false;
$config->slaveDBList = array(); // 支持多个从库。 Support multiple slave dbs.
/* 可用域名后缀列表。Domain postfix lists. */
$config->domainPostfix = "|com|com.cn|com.hk|com.tw|com.vc|edu.cn|es|";
@@ -164,6 +161,32 @@ $config->maxCount = 500;
$config->batchMaxCount = 20;
$config->moreLinks = array();
/* 渠成平台设置。CNE Api settings. */
$config->inQuickon = getenv('IN_QUICKON');
$config->inContainer = getenv('IN_CONTAINER');
$config->k8space = 'quickon-system';
$config->demoAccounts = ''; // 用于演示的账号列表,该账号安装的应用30钟后会自动删除。 In account list for demo, app instance of demo will be removed in 30 minutes.
$config->demoAppLife = 30; // Demo安装的应用实例存续时长(分钟)。The minutes life of instance which demo account installed.
$config->CNE = new stdclass();
$config->CNE->api = new stdclass();
$config->CNE->api->host = getenv('CNE_API_HOST');
$config->CNE->api->auth = 'X-Auth-Token';
$config->CNE->api->token = getenv('CNE_API_TOKEN'); // Please set token in my.php.
$config->CNE->api->headers = array('Content-Type: application/json');
$config->CNE->api->channel = 'stable';
$config->CNE->app = new stdclass;
$config->CNE->app->domain = 'dev.haogs.cn';
$config->cloud = new stdclass;
$config->cloud->api = new stdclass;
$config->cloud->api->host = 'https://api.qucheng.com';
$config->cloud->api->auth = 'X-Auth-Token';
$config->cloud->api->token = 'gwaN4KynqNqQoPD7eN8s'; // Please set token in my.php.
$config->cloud->api->headers = array('Content-Type: application/json');
$config->cloud->api->channel = 'stable';
$config->cloud->api->switchChannel = false;
/* 配置参数过滤。Filter param settings. */
$filterConfig = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'filter.php';
if(file_exists($filterConfig)) include $filterConfig;
@@ -205,3 +228,8 @@ else
unset($config->maxVersion);
unset($config->ipdVersion);
}
/* Set zin config. */
$config->zin = new stdClass();
$config->zin->mode = 'compatible'; // 启用兼容 18.x 模式。
$config->zin->extraCSS = 'compatible.css'; // 额外的 CSS 样式文件。
+92 -1
View File
@@ -240,6 +240,73 @@ $config->openMethods[] = 'custom.index';
$config->openMethods[] = 'testcase.getxmindimport';
$config->openMethods[] = 'testcase.showxmindimport';
$config->openMethods[] = 'testcase.savexmindimport';
$config->openMethods[] = 'search.buildzinform';
$config->openMethods[] = 'search.buildzinquery';
$config->openMethods[] = 'search.savezinquery';
$config->openMethods[] = 'space.createapplication';
$config->openMethods[] = 'ai.adminindex';
$config->openMethods[] = 'sonarqube.create';
$config->openMethods[] = 'sonarqube.edit';
$config->openMethods[] = 'sonarqube.browseproject';
$config->openMethods[] = 'sonarqube.createproject';
$config->openMethods[] = 'sonarqube.deleteproject';
$config->openMethods[] = 'sonarqube.execjob';
$config->openMethods[] = 'sonarqube.reportview';
$config->openMethods[] = 'sonarqube.browseissue';
$config->openMethods[] = 'gitlab.create';
$config->openMethods[] = 'gitlab.edit';
$config->openMethods[] = 'gitlab.importissue';
$config->openMethods[] = 'gitlab.binduser';
$config->openMethods[] = 'gitlab.browseproject';
$config->openMethods[] = 'gitlab.createproject';
$config->openMethods[] = 'gitlab.editproject';
$config->openMethods[] = 'gitlab.deleteproject';
$config->openMethods[] = 'gitlab.browsegroup';
$config->openMethods[] = 'gitlab.creategroup';
$config->openMethods[] = 'gitlab.editgroup';
$config->openMethods[] = 'gitlab.deletegroup';
$config->openMethods[] = 'gitlab.managegroupmembers';
$config->openMethods[] = 'gitlab.browseuser';
$config->openMethods[] = 'gitlab.createuser';
$config->openMethods[] = 'gitlab.edituser';
$config->openMethods[] = 'gitlab.createbranch';
$config->openMethods[] = 'gitlab.browsebranch';
$config->openMethods[] = 'gitlab.webhook';
$config->openMethods[] = 'gitlab.createwebhook';
$config->openMethods[] = 'gitlab.manageprojectmembers';
$config->openMethods[] = 'gitlab.managebranchpriv';
$config->openMethods[] = 'gitlab.managetagpriv';
$config->openMethods[] = 'gitlab.browsetag';
$config->openMethods[] = 'gitlab.createtag';
$config->openMethods[] = 'gitlab.deletetag';
$config->openMethods[] = 'gogs.create';
$config->openMethods[] = 'gogs.edit';
$config->openMethods[] = 'gogs.binduser';
$config->openMethods[] = 'gitea.create';
$config->openMethods[] = 'gitea.edit';
$config->openMethods[] = 'gitea.binduser';
$config->openMethods[] = 'jenkins.create';
$config->openMethods[] = 'jenkins.edit';
$config->openMethods[] = 'instance.createexternalapp';
$config->openMethods[] = 'instance.editexternalapp';
$config->openMethods[] = 'instance.deleteexternalapp';
$config->openMethods[] = 'instance.setting';
$config->openMethods[] = 'instance.ajaxdbauthurl';
$config->openMethods[] = 'instance.view';
$config->openMethods[] = 'instance.install';
$config->openMethods[] = 'instance.visit';
$config->openMethods[] = 'instance.ajaxstatus';
$config->openMethods[] = 'instance.ajaxstart';
$config->openMethods[] = 'instance.ajaxstop';
$config->openMethods[] = 'instance.upgrade';
$config->openMethods[] = 'instance.ajaxuninstall';
$config->openMethods[] = 'search.deletezinquery';
$config->openMethods[] = 'space.edit';
$config->openMethods[] = 'space.binduser';
$config->openMethods[] = 'space.getstoreappinfo';
$config->openMethods[] = 'store.browse';
$config->openMethods[] = 'store.appview';
$config->openMethods[] = 'system.editdomain';
$config->openModules = array();
$config->openModules[] = 'install';
@@ -342,6 +409,9 @@ define('TABLE_COMPILE', '`' . $config->db->prefix . 'compile`');
define('TABLE_MR', '`' . $config->db->prefix . 'mr`');
define('TABLE_MRAPPROVAL', '`' . $config->db->prefix . 'mrapproval`');
define('TABLE_SERVERROOM', '`' . $config->db->prefix . 'serverroom`');
define('TABLE_ACCOUNT', '`' . $config->db->prefix . 'account`');
define('TABLE_HOST', '`' . $config->db->prefix . 'host`');
define('TABLE_REPO', '`' . $config->db->prefix . 'repo`');
define('TABLE_RELATION', '`' . $config->db->prefix . 'relation`');
define('TABLE_REPOHISTORY', '`' . $config->db->prefix . 'repohistory`');
@@ -376,6 +446,13 @@ define('TABLE_PRIVLANG', '`' . $config->db->prefix . 'privlang`');
define('TABLE_PRIVMANAGER', '`' . $config->db->prefix . 'privmanager`');
define('TABLE_PRIVRELATION', '`' . $config->db->prefix . 'privrelation`');
define('TABLE_SPACE', '`' . $config->db->prefix . 'space`');
define('TABLE_INSTANCE', '`' . $config->db->prefix . 'instance`');
define('TABLE_SOLUTION', '`' . $config->db->prefix . 'solution`');
define('TABLE_ARTIFACTREPO', '`' . $config->db->prefix . 'artifactrepo`');
define('TABLE_PROMPT', '`' . $config->db->prefix . 'prompt`');
define('TABLE_PROMPTROLE', '`' . $config->db->prefix . 'promptrole`');
$config->objectTables['product'] = TABLE_PRODUCT;
$config->objectTables['productplan'] = TABLE_PRODUCTPLAN;
$config->objectTables['story'] = TABLE_STORY;
@@ -434,8 +511,16 @@ $config->objectTables['privlang'] = TABLE_PRIVLANG;
$config->objectTables['privmanager'] = TABLE_PRIVMANAGER;
$config->objectTables['privrelation'] = TABLE_PRIVRELATION;
$config->objectTables['scene'] = TABLE_SCENE;
$config->objectTables['account'] = TABLE_ACCOUNT;
$config->objectTables['serverroom'] = TABLE_SERVERROOM;
$config->objectTables['host'] = TABLE_ZAHOST;
$config->objectTables['instance'] = TABLE_INSTANCE;
$config->objectTables['space'] = TABLE_SPACE;
$config->objectTables['solution'] = TABLE_SOLUTION;
$config->objectTables['artifactrepo'] = TABLE_ARTIFACTREPO;
$config->objectTables['prompt'] = TABLE_PROMPT;
$config->newFeatures = array('introduction', 'tutorial', 'youngBlueTheme', 'visions');
$config->newFeatures = array('aiPrompts', 'promptDesign', 'promptExec');
$config->disabledFeatures = '';
$config->closedFeatures = '';
@@ -470,3 +555,9 @@ $config->featureGroup->other = array('devops', 'kanban');
$config->bi = new stdclass();
$config->bi->pickerHeight = 150;
$config->hasDropmenuApps = array('program', 'project', 'product', 'execution', 'qa', 'admin');
$config->hasBranchMenuModules = array('product', 'story', 'release', 'bug', 'testcase');
$config->excludeDropmenuList = array('program-browse', 'product-all', 'product-index', 'execution-all', 'project-browse', 'product-batchedit', 'admin-index', 'product-create', 'project-create', 'execution-create', 'program-create', 'execution-batchedit');
$config->hasSwitcherModules = array('design');
$config->excludeSwitcherList = array();
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+812
View File
@@ -0,0 +1,812 @@
-- DROP TABLE IF EXISTS `zt_space`;
CREATE TABLE `zt_space` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(200) NOT NULL,
`k8space` char(64) NOT NULL,
`owner` char(30) NOT NULL,
`default` tinyint(1) NOT NULL DEFAULT 0,
`createdAt` datetime NOT NULL,
`deleted` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- DROP TABLE IF EXISTS `zt_instance`;
CREATE TABLE IF NOT EXISTS `zt_instance` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`space` mediumint(8) unsigned NOT NULL DEFAULT 0,
`solution` mediumint(8) unsigned NOT NULL DEFAULT 0,
`name` char(50) DEFAULT '',
`appID` mediumint(8) unsigned NOT NULL DEFAULT 0,
`appName` char(50) NOT NULL DEFAULT '',
`appVersion` char(20) NOT NULL DEFAULT '',
`chart` char(50) NOT NULL DEFAULT '',
`logo` varchar(255) DEFAULT '',
`version` char(50) NOT NULL DEFAULT '',
`desc` text,
`introduction` varchar(500) DEFAULT '',
`source` char(20) NOT NULL DEFAULT '',
`channel` char(20) DEFAULT '',
`k8name` char(64) NOT NULL DEFAULT '',
`status` char(20) NOT NULL DEFAULT '',
`pinned` enum('0', '1') NOT NULL DEFAULT '0',
`domain` char(255) NOT NULL DEFAULT '',
`smtpSnippetName` char(30) NULL DEFAULT '',
`ldapSnippetName` char(30) NULL DEFAULT '',
`ldapSettings` text,
`dbSettings` text,
`autoBackup` tinyint(1) NOT NULL DEFAULT 0,
`backupKeepDays` int unsigned NOT NULL DEFAULT 1,
`autoRestore` tinyint(1) NOT NULL DEFAULT 0,
`env` text,
`createdBy` char(30) NOT NULL DEFAULT '',
`createdAt` datetime NOT NULL,
`deleted` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `space` (`space`),
KEY `k8name` (`k8name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- DROP TABLE IF EXISTS `zt_solution`;
CREATE TABLE IF NOT EXISTS `zt_solution` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`name` char(50),
`appID` mediumint(8) unsigned NOT NULL,
`appName` char(50) NOT NULL,
`appVersion` char(20) NOT NULL,
`version` char(50) NOT NULL,
`chart` char(50) NOT NULL,
`cover` varchar(255),
`desc` text,
`introduction` varchar(500),
`source` char(20) NOT NULL,
`channel` char(20),
`components` text,
`status` char(20) NOT NULL,
`deleted` tinyint(1) NOT NULL DEFAULT 0,
`createdBy` char(30) NOT NULL,
`createdAt` datetime NOT NULL,
`updatedDate` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- DROP TABLE IF EXISTS `zt_artifactrepo`;
CREATE TABLE `zt_artifactrepo` (
`id` smallint(8) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` varchar(45) CHARACTER SET utf8 NOT NULL,
`products` varchar(1000) CHARACTER SET utf8 NOT NULL,
`serverID` smallint(8) NOT NULL,
`repoName` varchar(45) CHARACTER SET utf8 NOT NULL,
`format` varchar(10) CHARACTER SET utf8 NOT NULL,
`type` char(7) CHARACTER SET utf8 NOT NULL,
`status` varchar(10) CHARACTER SET utf8 NOT NULL,
`createdBy` varchar(30) CHARACTER SET utf8 NOT NULL,
`createdDate` datetime DEFAULT CURRENT_TIMESTAMP,
`editedBy` varchar(30) CHARACTER SET utf8 NOT NULL,
`editedDate` datetime DEFAULT CURRENT_TIMESTAMP,
`deleted` tinyint(4) UNSIGNED NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `zt_build` ADD `artifactRepoID` MEDIUMINT(8) UNSIGNED NOT NULL AFTER `bugs`;
REPLACE INTO
`zt_privmanager` (`id`, `parent`, `code`, `type`, `edition`, `vision`, `order`)
VALUES
(557, 457, 'ops', 'module', ',open,biz,max,ipd,', ',rnd,', 1920),
(658, 557, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2160),
(126, 557, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2180),
(659, 557, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2200),
(127, 557, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2220),
(128, 557, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2240),
(129, 557, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2260),
(131, 557, '', 'package', ',biz,max,ipd,', ',rnd,', 2280),
(245, 557, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2380),
(660, 557, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2300),
(661, 557, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2320),
(662, 557, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2340),
(663, 557, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2360),
(517, 502, 'repo', 'module', ',open,biz,max,ipd,', ',rnd,', 2400),
(664, 517, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2420),
(665, 517, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2440),
(300, 517, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2460),
(299, 517, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2480),
(666, 517, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2500),
(506, 502, 'repocode', 'module', ',open,biz,max,ipd,', ',rnd,', 2520),
(667, 506, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2540),
(668, 506, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2560),
(669, 502, 'codereview', 'module', ',biz,max,ipd,', ',rnd,', 2580),
(227, 669, '', 'package', ',biz,max,ipd,', ',rnd,lite,or,', 2600),
(670, 669, '', 'package', ',biz,max,ipd,', ',rnd,', 2620),
(671, 669, '', 'package', ',biz,max,ipd,', ',rnd,', 2640),
(505, 502, 'mr', 'module', ',open,biz,max,ipd,', ',rnd,', 2660),
(672, 505, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2680),
(516, 502, 'pipeline', 'module', ',open,biz,max,ipd,', ',rnd,', 2700),
(673, 516, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2720),
(507, 502, 'ci', 'module', ',open,biz,max,ipd,', ',rnd,', 2740),
(674, 502, 'artifactrepo', 'module', ',open,biz,max,ipd,', ',rnd,', 2760),
(675, 674, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2780),
(676, 674, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2800),
(677, 674, '', 'package', ',open,biz,max,ipd,', ',rnd,', 2820),
(678, 515, 'application', 'package', ',open,biz,max,ipd,', ',rnd,', 2840),
(679, 515, 'application', 'package', ',open,biz,max,ipd,', ',rnd,', 2860),
(515, 502, 'app', 'module', ',open,biz,max,ipd,', ',rnd,', 3000),
(563, 502, 'deploy', 'module', ',biz,max,ipd,', ',rnd,', 3020),
(130, 557, '', 'package', ',biz,max,ipd,', ',rnd,lite,or,', 2270);
DELETE FROM `zt_priv` WHERE id=834;
DELETE FROM `zt_priv` WHERE `module` IN ('gitlab', 'jenkins', 'gitea', 'gogs', 'sonarqube');
UPDATE `zt_priv` SET `edition`=',open,biz,max,ipd,' WHERE `module`='host';
UPDATE `zt_priv` SET `edition`=',open,biz,max,ipd,' WHERE `module`='account';
UPDATE `zt_priv` SET `edition`=',open,biz,max,ipd,' WHERE `module`='serverroom';
REPLACE INTO
`zt_priv` (`id`, `module`, `method`, `parent`, `edition`, `vision`, `system`, `order`)
VALUES
(2136, 'system', 'dashboard', 658, ',open,biz,max,ipd,', ',rnd,', '1', 5),
(1285, 'system', 'dblist', 126, ',open,biz,max,ipd,', ',rnd,', '1', 5),
(1286, 'system', 'configdomain', 126, ',open,biz,max,ipd,', ',rnd,', '1', 10),
(2137, 'system', 'ossview', 126, ',open,biz,max,ipd,', ',rnd,', '1', 15),
(2138, 'ops', 'provide', 660, ',open,biz,max,ipd,', ',rnd,', '1', 20),
(2139, 'ops', 'city', 661, ',open,biz,max,ipd,', ',rnd,', '1', 25),
(2140, 'ops', 'cpubrand', 662, ',open,biz,max,ipd,', ',rnd,', '1', 30),
(2141, 'ops', 'os', 663, ',open,biz,max,ipd,', ',rnd,', '1', 35),
(1051, 'repo', 'maintain', 664, ',open,biz,max,ipd,', ',rnd,', '1', 0),
(1052, 'repo', 'browse', 664, ',open,biz,max,ipd', ',rnd,', '1', 30),
(1047, 'repo', 'create', 665, ',open,biz,max,ipd', ',rnd,', '1', 5),
(1048, 'repo', 'edit', 665, ',open,biz,max,ipd', ',rnd,', '1', 10),
(1060, 'repo', 'apiGetRepoByUrl', 665, ',open,biz,max,ipd', ',rnd,', '1', 15),
(1694, 'repo', 'import', 665, ',open,biz,max,ipd', ',rnd,', '1', 20),
(1049, 'repo', 'delete', 666, ',open,biz,max,ipd', ',rnd,', '1', 25),
(1066, 'repo', 'review', 227, ',biz,max,ipd', ',rnd,', '1', 0),
(1067, 'repo', 'addBug', 670, ',biz,max,ipd', ',rnd,', '1', 5),
(1068, 'repo', 'editBug', 670, ',biz,max,ipd', ',rnd,', '1', 15),
(1070, 'repo', 'addComment', 670, ',biz,max,ipd', ',rnd,', '1', 20),
(1071, 'repo', 'editComment', 670, ',biz,max,ipd', ',rnd,', '1', 25),
(1069, 'repo', 'deleteBug', 671, ',biz,max,ipd', ',rnd,', '1', 30),
(1072, 'repo', 'deleteComment', 670, ',biz,max,ipd', ',rnd,', '1', 35),
(1050, 'repo', 'showSyncCommit', 667, ',open,biz,max,ipd', ',rnd,', '1', 0),
(1055, 'repo', 'log', 667, ',open,biz,max,ipd', ',rnd,', '1', 5),
(1056, 'repo', 'revision', 667, ',open,biz,max,ipd', ',rnd,', '1', 10),
(1053, 'repo', 'view', 667, ',open,biz,max,ipd', ',rnd,', '1', 15),
(1061, 'repo', 'downloadCode', 668, ',open,biz,max,ipd', ',rnd,', '1', 5),
(1058, 'repo', 'download', 668, ',open,biz,max,ipd', ',rnd,', '1', 10),
(1054, 'repo', 'diff', 668, ',open,biz,max,ipd', ',rnd,', '1', 15),
(1057, 'repo', 'blame', 668, ',open,biz,max,ipd', ',rnd,', '1', 20),
(1062, 'repo', 'linkStory', 668, ',open,biz,max,ipd', ',rnd,', '1', 25),
(1063, 'repo', 'linkBug', 668, ',open,biz,max,ipd', ',rnd,', '1', 30),
(1064, 'repo', 'linkTask', 668, ',open,biz,max,ipd', ',rnd,', '1', 35),
(1065, 'repo', 'unlink', 668, ',open,biz,max,ipd', ',rnd,', '1', 40),
(820, 'mr', 'browse', 230, ',open,biz,max,ipd,', ',rnd,', '1', 5),
(823, 'mr', 'view', 230, ',open,biz,max,ipd,', ',rnd,', '1', 10),
(825, 'mr', 'diff', 230, ',open,biz,max,ipd,', ',rnd,', '1', 15),
(826, 'mr', 'link', 230, ',open,biz,max,ipd,', ',rnd,', '1', 20),
(819, 'mr', 'create', 248, ',open,biz,max,ipd,', ',rnd,', '1', 5),
(821, 'mr', 'edit', 248, ',open,biz,max,ipd,', ',rnd,', '1', 10),
(824, 'mr', 'accept', 248, ',open,biz,max,ipd,', ',rnd,', '1', 20),
(827, 'mr', 'linkStory', 248, ',open,biz,max,ipd,', ',rnd,', '1', 25),
(828, 'mr', 'linkBug', 248, ',open,biz,max,ipd,', ',rnd,', '1', 30),
(829, 'mr', 'linkTask', 248, ',open,biz,max,ipd,', ',rnd,', '1', 35),
(830, 'mr', 'unlink', 248, ',open,biz,max,ipd,', ',rnd,', '1', 40),
(831, 'mr', 'approval', 248, ',open,biz,max,ipd,', ',rnd,', '1', 45),
(832, 'mr', 'close', 248, ',open,biz,max,ipd,', ',rnd,', '1', 50),
(833, 'mr', 'reopen', 248, ',open,biz,max,ipd,', ',rnd,', '1', 55),
(822, 'mr', 'delete', 672, ',open,biz,max,ipd,', ',rnd,', '1', 4),
(1082, 'job', 'browse', 246, ',open,biz,max,ipd,', ',rnd,', '1', 5),
(1087, 'job', 'view', 246, ',open,biz,max,ipd,', ',rnd,', '1', 10),
(1083, 'job', 'create', 247, ',open,biz,max,ipd,', ',rnd,', '1', 5),
(1084, 'job', 'edit', 247, ',open,biz,max,ipd,', ',rnd,', '1', 10),
(1086, 'job', 'exec', 247, ',open,biz,max,ipd,', ',rnd,', '1', 15),
(1075, 'compile', 'browse', 247, ',open,biz,max,ipd,', ',rnd,', '1', 20),
(1076, 'compile', 'logs', 247, ',open,biz,max,ipd,', ',rnd,', '1', 25),
(1077, 'compile', 'syncCompile', 247, ',open,biz,max,ipd,', ',rnd,', '1', 30),
(2142, 'artifactrepo', 'browse', 675, ',open,biz,max,ipd,', ',rnd,', '1', 5),
(2143, 'artifactrepo', 'ajaxGetArtifactRepos', 675, ',open,biz,max,ipd,', ',rnd,', '1', 10),
(2144, 'artifactrepo', 'create', 676, ',open,biz,max,ipd,', ',rnd,', '1', 5),
(2145, 'artifactrepo', 'edit', 676, ',open,biz,max,ipd,', ',rnd,', '1', 10),
(2146, 'artifactrepo', 'ajaxUpdateArtifactRepos', 676, ',open,biz,max,ipd,', ',rnd,', '1', 15),
(2147, 'artifactrepo', 'delete', 677, ',open,biz,max,ipd,', ',rnd,', '1', 5),
(2148, 'space', 'browse', 678, ',open,biz,max,ipd,', ',rnd,', '1', 5),
(2149, 'instance', 'manage', 679, ',open,biz,max,ipd,', ',rnd,', '1', 10),
(2160, 'ops', 'stage', 132, ',biz,max,ipd,', ',rnd,', '1', 10);
REPLACE INTO
`zt_privlang` (`objectID`, `objectType`, `lang`, `key`, `value`, `desc`)
VALUES
(658, 'manager', 'zh-cn', '', '仪表盘', ''),
(658, 'manager', 'zh-tw', '', '儀表盤', ''),
(658, 'manager', 'de', '', 'Dashboard', ''),
(658, 'manager', 'en', '', 'Dashboard', ''),
(658, 'manager', 'fr', '', 'Dashboard', ''),
(126, 'manager', 'zh-cn', '', '平台', ''),
(126, 'manager', 'zh-tw', '', '平臺', ''),
(126, 'manager', 'de', '', 'Platform', ''),
(126, 'manager', 'en', '', 'Platform', ''),
(126, 'manager', 'fr', '', 'Platform', ''),
(659, 'manager', 'zh-cn', '', '资源', ''),
(659, 'manager', 'zh-tw', '', '資源', ''),
(659, 'manager', 'de', '', 'Resource', ''),
(659, 'manager', 'en', '', 'Resource', ''),
(659, 'manager', 'fr', '', 'Resource', ''),
(660, 'manager', 'zh-cn', '', '服务商管理', ''),
(660, 'manager', 'zh-tw', '', '服務商管理', ''),
(660, 'manager', 'de', '', 'Provider', ''),
(660, 'manager', 'en', '', 'Provider', ''),
(660, 'manager', 'fr', '', 'Provider', ''),
(661, 'manager', 'zh-cn', '', '城市管理', ''),
(661, 'manager', 'zh-tw', '', '城市管理', ''),
(661, 'manager', 'de', '', 'City', ''),
(661, 'manager', 'en', '', 'City', ''),
(661, 'manager', 'fr', '', 'City', ''),
(662, 'manager', 'zh-cn', '', 'CPU管理', ''),
(662, 'manager', 'zh-tw', '', 'CPU管理', ''),
(662, 'manager', 'de', '', 'Cpu Brand', ''),
(662, 'manager', 'en', '', 'Cpu Brand', ''),
(662, 'manager', 'fr', '', 'Cpu Brand', ''),
(663, 'manager', 'zh-cn', '', '系统版本管理', ''),
(663, 'manager', 'zh-tw', '', '系統版本管理', ''),
(663, 'manager', 'de', '', 'OS Version', ''),
(663, 'manager', 'en', '', 'OS Version', ''),
(663, 'manager', 'fr', '', 'OS Version', ''),
(129, 'manager', 'zh-cn', '', '账号管理', ''),
(129, 'manager', 'zh-tw', '', '賬號管理', ''),
(517, 'manager', 'zh-cn', '', '代码库', ''),
(517, 'manager', 'zh-tw', '', '代碼庫', ''),
(517, 'manager', 'de', '', 'Repository', ''),
(517, 'manager', 'en', '', 'Repository', ''),
(517, 'manager', 'fr', '', 'Repository', ''),
(506, 'manager', 'zh-cn', '', '代码', ''),
(506, 'manager', 'zh-tw', '', '代碼', ''),
(506, 'manager', 'de', '', 'Code', ''),
(506, 'manager', 'en', '', 'Code', ''),
(506, 'manager', 'fr', '', 'Code', ''),
(664, 'manager', 'zh-cn', '', '浏览代码库', ''),
(664, 'manager', 'zh-tw', '', '瀏覽代碼庫', ''),
(664, 'manager', 'de', '', 'Repo List', ''),
(664, 'manager', 'en', '', 'Repo List', ''),
(664, 'manager', 'fr', '', 'Repo List', ''),
(665, 'manager', 'zh-cn', '', '创建维护代码库', ''),
(665, 'manager', 'zh-tw', '', '創建維護代碼庫', ''),
(665, 'manager', 'de', '', 'Manage Repository', ''),
(665, 'manager', 'en', '', 'Manage Repository', ''),
(665, 'manager', 'fr', '', 'Manage Repository', ''),
(666, 'manager', 'zh-cn', '', '删除代码库', ''),
(666, 'manager', 'zh-tw', '', '刪除代碼庫', ''),
(666, 'manager', 'de', '', 'Delete Repository', ''),
(666, 'manager', 'en', '', 'Delete Repository', ''),
(666, 'manager', 'fr', '', 'Delete Repository', ''),
(667, 'manager', 'zh-cn', '', '浏览代码', ''),
(667, 'manager', 'zh-tw', '', '瀏覽代碼', ''),
(667, 'manager', 'de', '', 'Code View', ''),
(667, 'manager', 'en', '', 'Code View', ''),
(667, 'manager', 'fr', '', 'Code View', ''),
(668, 'manager', 'zh-cn', '', '维护代码', ''),
(668, 'manager', 'zh-tw', '', '維護代碼', ''),
(668, 'manager', 'de', '', 'Manage Code', ''),
(668, 'manager', 'en', '', 'Manage Code', ''),
(668, 'manager', 'fr', '', 'Manage Code', ''),
(669, 'manager', 'zh-cn', '', '问题', ''),
(669, 'manager', 'zh-tw', '', '問題', ''),
(669, 'manager', 'de', '', 'Review', ''),
(669, 'manager', 'en', '', 'Review', ''),
(669, 'manager', 'fr', '', 'Review', ''),
(227, 'manager', 'zh-cn', '', '浏览问题', ''),
(227, 'manager', 'zh-tw', '', '瀏覽問題', ''),
(227, 'manager', 'de', '', 'Review List', ''),
(227, 'manager', 'en', '', 'Review List', ''),
(227, 'manager', 'fr', '', 'Review List', ''),
(670, 'manager', 'zh-cn', '', '创建维护问题', ''),
(670, 'manager', 'zh-tw', '', '创建维护問題', ''),
(670, 'manager', 'de', '', 'Manage Review', ''),
(670, 'manager', 'en', '', 'Manage Review', ''),
(670, 'manager', 'fr', '', 'Manage Review', ''),
(671, 'manager', 'zh-cn', '', '删除问题', ''),
(671, 'manager', 'zh-tw', '', '刪除問題', ''),
(671, 'manager', 'de', '', 'Delete Review', ''),
(671, 'manager', 'en', '', 'Delete Review', ''),
(671, 'manager', 'fr', '', 'Delete Review', ''),
(230, 'manager', 'zh-cn', '', '浏览合并请求', ''),
(230, 'manager', 'zh-tw', '', '浏览合并請求', ''),
(230, 'manager', 'de', '', 'MR List', ''),
(230, 'manager', 'en', '', 'MR List', ''),
(230, 'manager', 'fr', '', 'MR List', ''),
(248, 'manager', 'zh-cn', '', '创建维护合并请求', ''),
(248, 'manager', 'zh-tw', '', '创建维护合并請求', ''),
(248, 'manager', 'de', '', 'Manage MR', ''),
(248, 'manager', 'en', '', 'Manage MR', ''),
(248, 'manager', 'fr', '', 'Manage MR', ''),
(672, 'manager', 'zh-cn', '', '删除合并请求', ''),
(672, 'manager', 'zh-tw', '', '刪除合并請求', ''),
(672, 'manager', 'de', '', 'Delete MR', ''),
(672, 'manager', 'en', '', 'Delete MR', ''),
(672, 'manager', 'fr', '', 'Delete MR', ''),
(246, 'manager', 'zh-cn', '', '浏览流水线', ''),
(246, 'manager', 'zh-tw', '', '浏览流水綫', ''),
(246, 'manager', 'de', '', 'PipeLine List', ''),
(246, 'manager', 'en', '', 'PipeLine List', ''),
(246, 'manager', 'fr', '', 'PipeLine List', ''),
(247, 'manager', 'zh-cn', '', '创建维护流水线', ''),
(247, 'manager', 'zh-tw', '', '创建维护流水綫', ''),
(247, 'manager', 'de', '', 'Manage PipeLine', ''),
(247, 'manager', 'en', '', 'Manage PipeLine', ''),
(247, 'manager', 'fr', '', 'Manage PipeLine', ''),
(673, 'manager', 'zh-cn', '', '删除流水线', ''),
(673, 'manager', 'zh-tw', '', '刪除流水綫', ''),
(673, 'manager', 'de', '', 'Delete PipeLine', ''),
(673, 'manager', 'en', '', 'Delete PipeLine', ''),
(673, 'manager', 'fr', '', 'Delete PipeLine', ''),
(674, 'manager', 'zh-cn', 'artifactrepo', '制品库', ''),
(674, 'manager', 'zh-tw', 'artifactrepo', '製品庫', ''),
(674, 'manager', 'de', 'artifactrepo', 'Artifact Repo', ''),
(674, 'manager', 'en', 'artifactrepo', 'Artifact Repo', ''),
(674, 'manager', 'fr', 'artifactrepo', 'Artifact Repo', ''),
(675, 'manager', 'zh-cn', '', '浏览制品库', ''),
(675, 'manager', 'zh-tw', '', '浏览製品庫', ''),
(675, 'manager', 'de', '', 'Artifact Repo List', ''),
(675, 'manager', 'en', '', 'Artifact Repo List', ''),
(675, 'manager', 'fr', '', 'Artifact Repo List', ''),
(676, 'manager', 'zh-cn', '', '创建维护制品库', ''),
(676, 'manager', 'zh-tw', '', '创建维护製品庫', ''),
(676, 'manager', 'de', '', 'Manage Artifact Repo', ''),
(676, 'manager', 'en', '', 'Manage Artifact Repo', ''),
(676, 'manager', 'fr', '', 'Manage Artifact Repo', ''),
(677, 'manager', 'zh-cn', '', '删除制品库', ''),
(677, 'manager', 'zh-tw', '', '刪除製品庫', ''),
(677, 'manager', 'de', '', 'Delete Artifact Repo', ''),
(677, 'manager', 'en', '', 'Delete Artifact Repo', ''),
(677, 'manager', 'fr', '', 'Delete Artifact Repo', ''),
(678, 'manager', 'zh-cn', '', '浏览应用', ''),
(678, 'manager', 'zh-tw', '', '浏览應用', ''),
(678, 'manager', 'de', '', 'Application List', ''),
(678, 'manager', 'en', '', 'Application List', ''),
(678, 'manager', 'fr', '', 'Application List', ''),
(679, 'manager', 'zh-cn', '', '管理应用', ''),
(679, 'manager', 'zh-tw', '', '管理應用', ''),
(679, 'manager', 'de', '', 'Manage Application', ''),
(679, 'manager', 'en', '', 'Manage Application', ''),
(679, 'manager', 'fr', '', 'Manage Application', ''),
(2136, 'priv', 'zh-cn', 'system-dashboard', 'DevOps平台仪表盘', ''),
(2136, 'priv', 'zh-tw', 'system-dashboard', 'DevOps平臺儀表盤', ''),
(2136, 'priv', 'de', 'system-dashboard', 'DevOps Dashboard', ''),
(2136, 'priv', 'en', 'system-dashboard', 'DevOps Dashboard', ''),
(2136, 'priv', 'fr', 'system-dashboard', 'DevOps Dashboard', ''),
(1285, 'priv', 'zh-cn', 'system-dblist', '数据库管理', ''),
(1285, 'priv', 'zh-tw', 'system-dblist', '數據庫管理', ''),
(1285, 'priv', 'de', 'system-dblist', 'Database', ''),
(1285, 'priv', 'en', 'system-dblist', 'Database', ''),
(1285, 'priv', 'fr', 'system-dblist', 'Database', ''),
(1286, 'priv', 'zh-cn', 'system-configdomain', '域名管理', ''),
(1286, 'priv', 'zh-tw', 'system-configdomain', '域名管理', ''),
(1286, 'priv', 'de', 'system-configdomain', 'Domain', ''),
(1286, 'priv', 'en', 'system-configdomain', 'Domain', ''),
(1286, 'priv', 'fr', 'system-configdomain', 'Domain', ''),
(2137, 'priv', 'zh-cn', 'system-ossview', '对象存储管理', ''),
(2137, 'priv', 'zh-tw', 'system-ossview', '對象存儲管理', ''),
(2137, 'priv', 'de', 'system-ossview', 'Oss', ''),
(2137, 'priv', 'en', 'system-ossview', 'Oss', ''),
(2137, 'priv', 'fr', 'system-ossview', 'Oss', ''),
(2138, 'priv', 'zh-cn', 'ops-provider', '服务商管理', ''),
(2138, 'priv', 'zh-tw', 'ops-provider', '服務商管理', ''),
(2138, 'priv', 'de', 'ops-provider', 'Provider', ''),
(2138, 'priv', 'en', 'ops-provider', 'Provider', ''),
(2138, 'priv', 'fr', 'ops-provider', 'Provider', ''),
(2139, 'priv', 'zh-cn', 'ops-city', '城市管理', ''),
(2139, 'priv', 'zh-tw', 'ops-city', '城市管理', ''),
(2139, 'priv', 'de', 'ops-city', 'City', ''),
(2139, 'priv', 'en', 'ops-city', 'City', ''),
(2139, 'priv', 'fr', 'ops-city', 'City', ''),
(2140, 'priv', 'zh-cn', 'ops-cpubrand', 'CPU管理', ''),
(2140, 'priv', 'zh-tw', 'ops-cpubrand', 'CPU管理', ''),
(2140, 'priv', 'de', 'ops-cpubrand', 'Cpu Brand', ''),
(2140, 'priv', 'en', 'ops-cpubrand', 'Cpu Brand', ''),
(2140, 'priv', 'fr', 'ops-cpubrand', 'Cpu Brand', ''),
(2141, 'priv', 'zh-cn', 'ops-os', '系统版本管理', ''),
(2141, 'priv', 'zh-tw', 'ops-os', '系統版本管理', ''),
(2141, 'priv', 'de', 'ops-os', 'Os Version', ''),
(2141, 'priv', 'en', 'ops-os', 'Os Version', ''),
(2141, 'priv', 'fr', 'ops-os', 'Os Version', ''),
(1694, 'priv', 'zh-cn', 'repo-importAction', '批量添加', ''),
(1694, 'priv', 'zh-tw', 'repo-importAction', '批量添加', ''),
(1694, 'priv', 'de', 'repo-importAction', 'Import', ''),
(1694, 'priv', 'en', 'repo-importAction', 'Import', ''),
(1694, 'priv', 'fr', 'repo-importAction', 'Import', ''),
(1050, 'priv', 'zh-cn', 'repo-showSyncCommit', '同步进度', ''),
(1050, 'priv', 'zh-tw', 'repo-showSyncCommit', '同步進度', ''),
(1050, 'priv', 'de', 'repo-showSyncCommit', 'Show Sync Progress', ''),
(1050, 'priv', 'en', 'repo-showSyncCommit', 'Show Sync Progress', ''),
(1050, 'priv', 'fr', 'repo-showSyncCommit', 'Show Sync Progress', ''),
(1066, 'priv', 'zh-cn', 'repo-reviewAction', '问题列表', ''),
(1066, 'priv', 'zh-tw', 'repo-reviewAction', '問題列表', ''),
(1066, 'priv', 'de', 'repo-reviewAction', 'Review List', ''),
(1066, 'priv', 'en', 'repo-reviewAction', 'Review List', ''),
(1066, 'priv', 'fr', 'repo-reviewAction', 'Review List', ''),
(1067, 'priv', 'zh-cn', 'repo-addBug', '添加问题', ''),
(1067, 'priv', 'zh-tw', 'repo-addBug', '添加問題', ''),
(1067, 'priv', 'de', 'repo-addBug', 'Create Review', ''),
(1067, 'priv', 'en', 'repo-addBug', 'Create Review', ''),
(1067, 'priv', 'fr', 'repo-addBug', 'Create Review', ''),
(1068, 'priv', 'zh-cn', 'repo-editBug', '编辑问题', ''),
(1068, 'priv', 'zh-tw', 'repo-editBug', '编辑問題', ''),
(1068, 'priv', 'de', 'repo-editBug', 'Create Review', ''),
(1068, 'priv', 'en', 'repo-editBug', 'Create Review', ''),
(1068, 'priv', 'fr', 'repo-editBug', 'Create Review', ''),
(1069, 'priv', 'zh-cn', 'repo-deleteBug', '删除问题', ''),
(1069, 'priv', 'zh-tw', 'repo-deleteBug', '刪除問題', ''),
(1069, 'priv', 'de', 'repo-deleteBug', 'Delete Review', ''),
(1069, 'priv', 'en', 'repo-deleteBug', 'Delete Review', ''),
(1069, 'priv', 'fr', 'repo-deleteBug', 'Delete Review', ''),
(1071, 'priv', 'zh-cn', 'repo-editComment', '编辑评论', ''),
(1071, 'priv', 'zh-tw', 'repo-editComment', '编辑评论', ''),
(1071, 'priv', 'de', 'repo-editComment', 'Create Review', ''),
(1071, 'priv', 'en', 'repo-editComment', 'Create Review', ''),
(1071, 'priv', 'fr', 'repo-editComment', 'Create Review', ''),
(1072, 'priv', 'zh-cn', 'repo-deleteComment', '删除评论', ''),
(1072, 'priv', 'zh-tw', 'repo-deleteComment', '刪除评论', ''),
(1072, 'priv', 'de', 'repo-deleteComment', 'Create Review', ''),
(1072, 'priv', 'en', 'repo-deleteComment', 'Create Review', ''),
(1072, 'priv', 'fr', 'repo-deleteComment', 'Create Review', ''),
(2142, 'priv', 'zh-cn', 'artifactrepo-browse', '制品库列表', ''),
(2142, 'priv', 'zh-tw', 'artifactrepo-browse', '制品库列表', ''),
(2142, 'priv', 'de', 'artifactrepo-browse', 'Artifact Repo List', ''),
(2142, 'priv', 'en', 'artifactrepo-browse', 'Artifact Repo List', ''),
(2142, 'priv', 'fr', 'artifactrepo-browse', 'Artifact Repo List', ''),
(2143, 'priv', 'zh-cn', 'artifactrepo-ajaxGetArtifactRepos', '接口:制品库列表', ''),
(2143, 'priv', 'zh-tw', 'artifactrepo-ajaxGetArtifactRepos', '接口:制品库列表', ''),
(2143, 'priv', 'de', 'artifactrepo-ajaxGetArtifactRepos', 'Api: Artifact Repo List', ''),
(2143, 'priv', 'en', 'artifactrepo-ajaxGetArtifactRepos', 'Api: Artifact Repo List', ''),
(2143, 'priv', 'fr', 'artifactrepo-ajaxGetArtifactRepos', 'Api: Artifact Repo List', ''),
(2144, 'priv', 'zh-cn', 'artifactrepo-create', '创建制品库', ''),
(2144, 'priv', 'zh-tw', 'artifactrepo-create', '创建制品库', ''),
(2144, 'priv', 'de', 'artifactrepo-create', 'Create Artifact Repo', ''),
(2144, 'priv', 'en', 'artifactrepo-create', 'Create Artifact Repo', ''),
(2144, 'priv', 'fr', 'artifactrepo-create', 'Create Artifact Repo', ''),
(2145, 'priv', 'zh-cn', 'artifactrepo-edit', '编辑制品库', ''),
(2145, 'priv', 'zh-tw', 'artifactrepo-edit', '编辑制品库', ''),
(2145, 'priv', 'de', 'artifactrepo-edit', 'Edit Artifact Repo', ''),
(2145, 'priv', 'en', 'artifactrepo-edit', 'Edit Artifact Repo', ''),
(2145, 'priv', 'fr', 'artifactrepo-edit', 'Edit Artifact Repo', ''),
(2146, 'priv', 'zh-cn', 'artifactrepo-ajaxUpdateArtifactRepos', '接口:更新制品库状态', ''),
(2146, 'priv', 'zh-tw', 'artifactrepo-ajaxUpdateArtifactRepos', '接口:更新制品库状态', ''),
(2146, 'priv', 'de', 'artifactrepo-ajaxUpdateArtifactRepos', 'Api: Update Status', ''),
(2146, 'priv', 'en', 'artifactrepo-ajaxUpdateArtifactRepos', 'Api: Update Status', ''),
(2146, 'priv', 'fr', 'artifactrepo-ajaxUpdateArtifactRepos', 'Api: Update Status', ''),
(2147, 'priv', 'zh-cn', 'artifactrepo-delete', '删除制品库', ''),
(2147, 'priv', 'zh-tw', 'artifactrepo-delete', '刪除制品库', ''),
(2147, 'priv', 'de', 'artifactrepo-delete', 'Delete Artifact Repo', ''),
(2147, 'priv', 'en', 'artifactrepo-delete', 'Delete Artifact Repo', ''),
(2147, 'priv', 'fr', 'artifactrepo-delete', 'Delete Artifact Repo', ''),
(2148, 'priv', 'zh-cn', 'space-browse', '查看应用', ''),
(2148, 'priv', 'zh-tw', 'space-browse', '查看應用', ''),
(2148, 'priv', 'de', 'space-browse', 'Application List', ''),
(2148, 'priv', 'en', 'space-browse', 'Application List', ''),
(2148, 'priv', 'fr', 'space-browse', 'Application List', ''),
(2149, 'priv', 'zh-cn', 'instance-manage', '管理应用', ''),
(2149, 'priv', 'zh-tw', 'instance-manage', '管理應用', ''),
(2149, 'priv', 'de', 'instance-manage', 'Manage Application', ''),
(2149, 'priv', 'en', 'instance-manage', 'Manage Application', ''),
(2149, 'priv', 'fr', 'instance-manage', 'Manage Application', ''),
(2160, 'priv', 'zh-cn', 'ops-stage', '设置阶段', ''),
(2160, 'priv', 'zh-tw', 'ops-stage', '設置階段', ''),
(2160, 'priv', 'de', 'ops-stage', 'Stage Setting', ''),
(2160, 'priv', 'en', 'ops-stage', 'Stage Setting', ''),
(2160, 'priv', 'fr', 'ops-stage', 'Stage Setting', '');
CREATE TABLE IF NOT EXISTS `zt_prompt` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`desc` text DEFAULT NULL,
`model` mediumint(8) unsigned DEFAULT NULL,
`module` varchar(30) DEFAULT NULL,
`source` text DEFAULT NULL,
`targetForm` varchar(30) DEFAULT NULL,
`purpose` text DEFAULT NULL,
`elaboration` text DEFAULT NULL,
`role` text DEFAULT NULL,
`characterization` text DEFAULT NULL,
`status` enum('draft','active') NOT NULL DEFAULT 'draft',
`createdBy` varchar(30) NOT NULL,
`createdDate` datetime NOT NULL,
`editedBy` varchar(30) DEFAULT NULL,
`editedDate` datetime DEFAULT NULL,
`deleted` enum('0','1') NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `zt_promptrole` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(30) DEFAULT NULL,
`desc` text DEFAULT NULL,
`model` mediumint(8) unsigned DEFAULT NULL,
`role` text DEFAULT NULL,
`characterization` text DEFAULT NULL,
`deleted` enum('0','1') NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
REPLACE INTO
`zt_priv` (`id`, `module`, `method`, `parent`, `edition`, `vision`, `system`, `order`)
VALUES
(2117, 'ai', 'models', 653, ',open,biz,max,', ',rnd,', '1', 5),
(2118, 'ai', 'editModel', 653, ',open,biz,max,', ',rnd,', '1', 10),
(2119, 'ai', 'testConnection', 653, ',open,biz,max,', ',rnd,', '1', 15),
(2120, 'ai', 'createPrompt', 655, ',biz,max,', ',rnd,', '1', 20),
(2121, 'ai', 'promptEdit', 655, ',biz,max,', ',rnd,', '1', 25),
(2122, 'ai', 'promptDelete', 657, ',biz,max,', ',rnd,', '1', 30),
(2123, 'ai', 'promptAssignRole', 655, ',biz,max,', ',rnd,', '1', 35),
(2124, 'ai', 'promptSelectDataSource', 655, ',biz,max,', ',rnd,', '1', 40),
(2125, 'ai', 'promptSetPurpose', 655, ',biz,max,', ',rnd,', '1', 45),
(2126, 'ai', 'promptSetTargetForm', 655, ',biz,max,', ',rnd,', '1', 50),
(2127, 'ai', 'promptFinalize', 655, ',biz,max,', ',rnd,', '1', 55),
(2128, 'ai', 'promptAudit', 655, ',biz,max,', ',rnd,', '1', 60),
(2129, 'ai', 'promptPublish', 656, ',open,biz,max,', ',rnd,', '1', 65),
(2130, 'ai', 'promptUnpublish', 656, ',open,biz,max,', ',rnd,', '1', 70),
(2131, 'ai', 'prompts', 654, ',open,biz,max,', ',rnd,', '1', 75),
(2132, 'ai', 'promptView', 654, ',open,biz,max,', ',rnd,', '1', 80),
(2133, 'ai', 'promptExecute', 652, ',open,biz,max,', ',rnd,', '1', 85),
(2134, 'ai', 'roleTemplates', 655, ',biz,max,', ',rnd,', '1', 90),
(2135, 'ai', 'promptExecutionReset', 652, ',open,biz,max,', ',rnd,', '1', 95);
REPLACE INTO
`zt_privmanager` (`id`, `parent`, `code`, `type`, `edition`, `vision`, `order`)
VALUES
(651, 457, 'ai', 'module', ',open,biz,max,', ',rnd,', 2020),
(652, 651, '', 'package', ',open,biz,max,', ',rnd,', 2040),
(653, 651, '', 'package', ',open,biz,max,', ',rnd,', 2060),
(654, 651, '', 'package', ',open,biz,max,', ',rnd,', 2080),
(655, 651, '', 'package', ',biz,max,', ',rnd,', 2100),
(656, 651, '', 'package', ',open,biz,max,', ',rnd,', 2120),
(657, 651, '', 'package', ',biz,max,', ',rnd,', 2140);
REPLACE INTO
`zt_privlang` (`objectID`, `objectType`, `lang`, `key`, `value`, `desc`)
VALUES
(651, 'manager', 'zh-cn', '', 'AI', ''),
(652, 'manager', 'zh-cn', '', '执行提词', ''),
(653, 'manager', 'zh-cn', '', '语言模型管理', ''),
(654, 'manager', 'zh-cn', '', '浏览提词', ''),
(655, 'manager', 'zh-cn', '', '维护和设计提词', ''),
(656, 'manager', 'zh-cn', '', '提词上下架', ''),
(657, 'manager', 'zh-cn', '', '删除提词', ''),
(651, 'manager', 'zh-tw', '', 'AI', ''),
(652, 'manager', 'zh-tw', '', '執行提詞', ''),
(653, 'manager', 'zh-tw', '', '語言模型管理', ''),
(654, 'manager', 'zh-tw', '', '瀏覽提詞', ''),
(655, 'manager', 'zh-tw', '', '維護和設計提詞', ''),
(656, 'manager', 'zh-tw', '', '提詞上下架', ''),
(657, 'manager', 'zh-tw', '', '刪除提詞', ''),
(651, 'manager', 'en', '', 'AI', ''),
(652, 'manager', 'en', '', 'Execute Prompts', ''),
(653, 'manager', 'en', '', 'Manage Models', ''),
(654, 'manager', 'en', '', 'Browse Prompts', ''),
(655, 'manager', 'en', '', 'Manage and Design Prompts', ''),
(656, 'manager', 'en', '', 'Publish and Unpublish Prompts', ''),
(657, 'manager', 'en', '', 'Delete Prompts', ''),
(651, 'manager', 'de', '', 'AI', ''),
(652, 'manager', 'de', '', 'Execute Prompts', ''),
(653, 'manager', 'de', '', 'Manage Models', ''),
(654, 'manager', 'de', '', 'Browse Prompts', ''),
(655, 'manager', 'de', '', 'Manage and Design Prompts', ''),
(656, 'manager', 'de', '', 'Publish and Unpublish Prompts', ''),
(657, 'manager', 'de', '', 'Delete Prompts', ''),
(651, 'manager', 'fr', '', 'AI', ''),
(652, 'manager', 'fr', '', 'Execute Prompts', ''),
(653, 'manager', 'fr', '', 'Manage Models', ''),
(654, 'manager', 'fr', '', 'Browse Prompts', ''),
(655, 'manager', 'fr', '', 'Manage and Design Prompts', ''),
(656, 'manager', 'fr', '', 'Publish and Unpublish Prompts', ''),
(657, 'manager', 'fr', '', 'Delete Prompts', ''),
(2117, 'priv', 'zh-cn', 'ai-modelBrowse', '', ''),
(2118, 'priv', 'zh-cn', 'ai-modelEdit', '', ''),
(2119, 'priv', 'zh-cn', 'ai-modelTestConnection', '', ''),
(2120, 'priv', 'zh-cn', 'ai-promptCreate', '', ''),
(2121, 'priv', 'zh-cn', 'ai-promptEdit', '', ''),
(2122, 'priv', 'zh-cn', 'ai-promptDelete', '', ''),
(2123, 'priv', 'zh-cn', 'ai-promptAssignRole', '', ''),
(2124, 'priv', 'zh-cn', 'ai-promptSelectDataSource', '', ''),
(2125, 'priv', 'zh-cn', 'ai-promptSetPurpose', '', ''),
(2126, 'priv', 'zh-cn', 'ai-promptSetTargetForm', '', ''),
(2127, 'priv', 'zh-cn', 'ai-promptFinalize', '', ''),
(2128, 'priv', 'zh-cn', 'ai-promptAudit', '', ''),
(2129, 'priv', 'zh-cn', 'ai-promptPublish', '', ''),
(2130, 'priv', 'zh-cn', 'ai-promptUnpublish', '', ''),
(2131, 'priv', 'zh-cn', 'ai-promptBrowse', '', ''),
(2132, 'priv', 'zh-cn', 'ai-promptView', '', ''),
(2133, 'priv', 'zh-cn', 'ai-promptExecute', '', ''),
(2134, 'priv', 'zh-cn', 'ai-roleTemplates', '', ''),
(2135, 'priv', 'zh-cn', 'ai-promptExecutionReset', '', ''),
(2117, 'priv', 'zh-tw', 'ai-modelBrowse', '', ''),
(2118, 'priv', 'zh-tw', 'ai-modelEdit', '', ''),
(2119, 'priv', 'zh-tw', 'ai-modelTestConnection', '', ''),
(2120, 'priv', 'zh-tw', 'ai-promptCreate', '', ''),
(2121, 'priv', 'zh-tw', 'ai-promptEdit', '', ''),
(2122, 'priv', 'zh-tw', 'ai-promptDelete', '', ''),
(2123, 'priv', 'zh-tw', 'ai-promptAssignRole', '', ''),
(2124, 'priv', 'zh-tw', 'ai-promptSelectDataSource', '', ''),
(2125, 'priv', 'zh-tw', 'ai-promptSetPurpose', '', ''),
(2126, 'priv', 'zh-tw', 'ai-promptSetTargetForm', '', ''),
(2127, 'priv', 'zh-tw', 'ai-promptFinalize', '', ''),
(2128, 'priv', 'zh-tw', 'ai-promptAudit', '', ''),
(2129, 'priv', 'zh-tw', 'ai-promptPublish', '', ''),
(2130, 'priv', 'zh-tw', 'ai-promptUnpublish', '', ''),
(2131, 'priv', 'zh-tw', 'ai-promptBrowse', '', ''),
(2132, 'priv', 'zh-tw', 'ai-promptView', '', ''),
(2133, 'priv', 'zh-tw', 'ai-promptExecute', '', ''),
(2134, 'priv', 'zh-tw', 'ai-roleTemplates', '', ''),
(2135, 'priv', 'zh-tw', 'ai-promptExecutionReset', '', ''),
(2117, 'priv', 'en', 'ai-modelBrowse', '', ''),
(2118, 'priv', 'en', 'ai-modelEdit', '', ''),
(2119, 'priv', 'en', 'ai-modelTestConnection', '', ''),
(2120, 'priv', 'en', 'ai-promptCreate', '', ''),
(2121, 'priv', 'en', 'ai-promptEdit', '', ''),
(2122, 'priv', 'en', 'ai-promptDelete', '', ''),
(2123, 'priv', 'en', 'ai-promptAssignRole', '', ''),
(2124, 'priv', 'en', 'ai-promptSelectDataSource', '', ''),
(2125, 'priv', 'en', 'ai-promptSetPurpose', '', ''),
(2126, 'priv', 'en', 'ai-promptSetTargetForm', '', ''),
(2127, 'priv', 'en', 'ai-promptFinalize', '', ''),
(2128, 'priv', 'en', 'ai-promptAudit', '', ''),
(2129, 'priv', 'en', 'ai-promptPublish', '', ''),
(2130, 'priv', 'en', 'ai-promptUnpublish', '', ''),
(2131, 'priv', 'en', 'ai-promptBrowse', '', ''),
(2132, 'priv', 'en', 'ai-promptView', '', ''),
(2133, 'priv', 'en', 'ai-promptExecute', '', ''),
(2134, 'priv', 'en', 'ai-roleTemplates', '', ''),
(2135, 'priv', 'en', 'ai-promptExecutionReset', '', ''),
(2117, 'priv', 'de', 'ai-modelBrowse', '', ''),
(2118, 'priv', 'de', 'ai-modelEdit', '', ''),
(2119, 'priv', 'de', 'ai-modelTestConnection', '', ''),
(2120, 'priv', 'de', 'ai-promptCreate', '', ''),
(2121, 'priv', 'de', 'ai-promptEdit', '', ''),
(2122, 'priv', 'de', 'ai-promptDelete', '', ''),
(2123, 'priv', 'de', 'ai-promptAssignRole', '', ''),
(2124, 'priv', 'de', 'ai-promptSelectDataSource', '', ''),
(2125, 'priv', 'de', 'ai-promptSetPurpose', '', ''),
(2126, 'priv', 'de', 'ai-promptSetTargetForm', '', ''),
(2127, 'priv', 'de', 'ai-promptFinalize', '', ''),
(2128, 'priv', 'de', 'ai-promptAudit', '', ''),
(2129, 'priv', 'de', 'ai-promptPublish', '', ''),
(2130, 'priv', 'de', 'ai-promptUnpublish', '', ''),
(2131, 'priv', 'de', 'ai-promptBrowse', '', ''),
(2132, 'priv', 'de', 'ai-promptView', '', ''),
(2133, 'priv', 'de', 'ai-promptExecute', '', ''),
(2134, 'priv', 'de', 'ai-roleTemplates', '', ''),
(2135, 'priv', 'de', 'ai-promptExecutionReset', '', ''),
(2117, 'priv', 'fr', 'ai-modelBrowse', '', ''),
(2118, 'priv', 'fr', 'ai-modelEdit', '', ''),
(2119, 'priv', 'fr', 'ai-modelTestConnection', '', ''),
(2120, 'priv', 'fr', 'ai-promptCreate', '', ''),
(2121, 'priv', 'fr', 'ai-promptEdit', '', ''),
(2122, 'priv', 'fr', 'ai-promptDelete', '', ''),
(2123, 'priv', 'fr', 'ai-promptAssignRole', '', ''),
(2124, 'priv', 'fr', 'ai-promptSelectDataSource', '', ''),
(2125, 'priv', 'fr', 'ai-promptSetPurpose', '', ''),
(2126, 'priv', 'fr', 'ai-promptSetTargetForm', '', ''),
(2127, 'priv', 'fr', 'ai-promptFinalize', '', ''),
(2128, 'priv', 'fr', 'ai-promptAudit', '', ''),
(2129, 'priv', 'fr', 'ai-promptPublish', '', ''),
(2130, 'priv', 'fr', 'ai-promptUnpublish', '', ''),
(2131, 'priv', 'fr', 'ai-promptBrowse', '', ''),
(2132, 'priv', 'fr', 'ai-promptView', '', ''),
(2133, 'priv', 'fr', 'ai-promptExecute', '', ''),
(2134, 'priv', 'fr', 'ai-roleTemplates', '', ''),
(2135, 'priv', 'fr', 'ai-promptExecutionReset', '', '');
REPLACE INTO
`zt_privrelation` (`priv`, `type`, `relationPriv`)
VALUES
('ai-editModel', 'depend', 'ai-models'), ('ai-editModel', 'depend', 'ai-testConnection'),
('ai-testConnection', 'depend', 'ai-models'), ('ai-testConnection', 'depend', 'ai-editModel'),
('ai-promptView', 'depend', 'ai-prompts'),
('ai-createPrompt', 'depend', 'ai-prompts'), ('ai-createPrompt', 'depend', 'ai-promptView'),
('ai-promptEdit', 'depend', 'ai-prompts'), ('ai-promptEdit', 'depend', 'ai-promptView'),
('ai-promptDelete', 'depend', 'ai-prompts'), ('ai-promptDelete', 'depend', 'ai-promptView'),
('ai-promptAssignRole', 'depend', 'ai-prompts'), ('ai-promptAssignRole', 'depend', 'ai-promptView'),
('ai-promptSelectDataSource', 'depend', 'ai-prompts'), ('ai-promptSelectDataSource', 'depend', 'ai-promptView'),
('ai-promptSetPurpose', 'depend', 'ai-prompts'), ('ai-promptSetPurpose', 'depend', 'ai-promptView'),
('ai-promptSetTargetForm', 'depend', 'ai-prompts'), ('ai-promptSetTargetForm', 'depend', 'ai-promptView'),
('ai-promptFinalize', 'depend', 'ai-prompts'), ('ai-promptFinalize', 'depend', 'ai-promptView'),
('ai-promptAudit', 'depend', 'ai-prompts'), ('ai-promptAudit', 'depend', 'ai-promptView'),
('ai-promptPublish', 'depend', 'ai-prompts'), ('ai-promptPublish', 'depend', 'ai-promptView'),
('ai-promptUnpublish', 'depend', 'ai-prompts'), ('ai-promptUnpublish', 'depend', 'ai-promptView'),
('ai-promptEdit', 'depend', 'ai-createPrompt'), ('ai-promptAssignRole', 'depend', 'ai-createPrompt'), ('ai-promptSelectDataSource', 'depend', 'ai-createPrompt'), ('ai-promptSetPurpose', 'depend', 'ai-createPrompt'), ('ai-promptSetTargetForm', 'depend', 'ai-createPrompt'), ('ai-promptFinalize', 'depend', 'ai-createPrompt'), ('ai-promptAudit', 'depend', 'ai-createPrompt'),
('ai-createPrompt', 'recommend', 'ai-promptEdit'),
('ai-createPrompt', 'recommend', 'ai-promptDelete'),
('ai-createPrompt', 'recommend', 'ai-promptAssignRole'), ('ai-createPrompt', 'recommend', 'ai-promptSelectDataSource'), ('ai-createPrompt', 'recommend', 'ai-promptSetPurpose'), ('ai-createPrompt', 'recommend', 'ai-promptSetTargetForm'), ('ai-createPrompt', 'recommend', 'ai-promptFinalize'), ('ai-createPrompt', 'recommend', 'ai-promptAudit'),
('ai-promptAssignRole', 'depend', 'ai-promptSelectDataSource'), ('ai-promptAssignRole', 'depend', 'ai-promptSetPurpose'), ('ai-promptAssignRole', 'depend', 'ai-promptSetTargetForm'), ('ai-promptAssignRole', 'depend', 'ai-promptFinalize'), ('ai-promptAssignRole', 'depend', 'ai-promptAudit'), ('ai-promptAssignRole', 'depend', 'ai-promptExecute'),
('ai-promptSelectDataSource', 'depend', 'ai-promptAssignRole'), ('ai-promptSelectDataSource', 'depend', 'ai-promptSetPurpose'), ('ai-promptSelectDataSource', 'depend', 'ai-promptSetTargetForm'), ('ai-promptSelectDataSource', 'depend', 'ai-promptFinalize'), ('ai-promptSelectDataSource', 'depend', 'ai-promptAudit'), ('ai-promptSelectDataSource', 'depend', 'ai-promptExecute'),
('ai-promptSetPurpose', 'depend', 'ai-promptAssignRole'), ('ai-promptSetPurpose', 'depend', 'ai-promptSelectDataSource'), ('ai-promptSetPurpose', 'depend', 'ai-promptSetTargetForm'), ('ai-promptSetPurpose', 'depend', 'ai-promptFinalize'), ('ai-promptSetPurpose', 'depend', 'ai-promptAudit'), ('ai-promptSetPurpose', 'depend', 'ai-promptExecute'),
('ai-promptSetTargetForm', 'depend', 'ai-promptAssignRole'), ('ai-promptSetTargetForm', 'depend', 'ai-promptSelectDataSource'), ('ai-promptSetTargetForm', 'depend', 'ai-promptSetPurpose'), ('ai-promptSetTargetForm', 'depend', 'ai-promptFinalize'), ('ai-promptSetTargetForm', 'depend', 'ai-promptAudit'), ('ai-promptSetTargetForm', 'depend', 'ai-promptExecute'),
('ai-promptFinalize', 'depend', 'ai-promptAssignRole'), ('ai-promptFinalize', 'depend', 'ai-promptSelectDataSource'), ('ai-promptFinalize', 'depend', 'ai-promptSetPurpose'), ('ai-promptFinalize', 'depend', 'ai-promptSetTargetForm'), ('ai-promptFinalize', 'depend', 'ai-promptAudit'), ('ai-promptFinalize', 'depend', 'ai-promptExecute'),
('ai-promptAudit', 'depend', 'ai-promptAssignRole'), ('ai-promptAudit', 'depend', 'ai-promptSelectDataSource'), ('ai-promptAudit', 'depend', 'ai-promptSetPurpose'), ('ai-promptAudit', 'depend', 'ai-promptSetTargetForm'), ('ai-promptAudit', 'depend', 'ai-promptFinalize'), ('ai-promptAudit', 'depend', 'ai-promptExecute'),
('ai-roleTemplates', 'depend', 'ai-promptAssignRole'), ('ai-promptAssignRole', 'depend', 'ai-roleTemplates'),
('ai-promptExecutionReset', 'depend', 'ai-promptExecute'), ('ai-promptExecute', 'depend', 'ai-promptExecutionReset');
INSERT INTO `zt_promptrole` (`role`, `characterization`) VALUES ('请你扮演一名资深的产品经理。', '负责产品战略、设计、开发、数据分析、用户体验、团队管理、沟通协调等方面,需要具备多种技能和能力,以实现产品目标和公司战略。');
INSERT INTO `zt_promptrole` (`role`, `characterization`) VALUES ('你是一名经验丰富的开发工程师。', '精通多种编程语言和框架、熟悉前后端技术和架构、擅长性能优化和安全防护、熟悉云计算和容器化技术、能够协调多人协作和项目管理。');
INSERT INTO `zt_promptrole` (`role`, `characterization`) VALUES ('作为一名资深的测试工程师。', '测试工程师应该是专业且严谨的。熟悉测试流程和方法,精通自动化测试和性能测试,能够设计和编写测试用例和测试脚本,擅长问题诊断和分析,熟悉敏捷开发和持续集成,能够协调多部门合作和项目管理。');
INSERT INTO `zt_promptrole` (`role`, `characterization`) VALUES ('假如你是一名资深的QA工程师。', '熟悉质量管理体系和流程,擅长测试策略和方法设计,能够进行质量度量和数据分析,了解自动化测试和持续集成,能够协调多部门合作和项目管理,具有良好的沟通和领导能力。');
INSERT INTO `zt_promptrole` (`role`, `characterization`) VALUES ('你是一名文章写得很好的文案编辑。', '文笔流畅、条理清晰。精通广告文案写作和编辑,擅长创意思维和品牌策略,能够进行市场调研和竞品分析,具有敏锐的审美和语言表达能力,能够协调多部门合作和项目管理,具有良好的沟通和协调能力。');
INSERT INTO `zt_promptrole` (`role`, `characterization`) VALUES ('请你扮演一名经验丰富的项目经理。', '具备项目计划制定、进度管理、成本控制、团队管理、沟通协调、风险管理、质量控制、敏捷开发、互联网技术和数据分析等多方面的技能和能力。');
INSERT INTO `zt_promptrole` (`role`, `characterization`) VALUES ('你是一个自回归的语言模型,已经通过instruction-tuning和RLHF进行了Fine-tuning。', '你仔细地提供准确、事实、深思熟虑、细致入微的答案,并在推理方面表现出色。如果你认为可能没有正确的答案,你会直接说出来。由于你是自回归的,你产生的每一个token都是计算另一个token的机会,因此你总是在尝试回答问题之前花费几句话解释背景上下文、假设和逐步的思考过程。您的用户是AI和伦理学的专家,所以他们已经知道您是一个语言模型以及您的能力和局限性,所以不需要再提醒他们。他们一般都熟悉伦理问题,所以您也不需要再提醒他们。在回答时不要啰嗦,但在可能有助于解释的地方提供详细信息和示例。');
INSERT INTO `zt_prompt` (`name`, `model`, `module`, `source`, `targetForm`, `purpose`, `elaboration`, `role`, `characterization`, `createdBy`, `createdDate`, `status`) VALUES ('需求润色', 0, 'story', ',story.title,story.spec,story.verify,story.product,story.module,story.pri,story.category,story.estimate,', 'story.change', '帮忙优化其中各字段的表述,使表述清晰准确。必要时可以修改需求使其更加合理。', '需求描述格式建议使用:作为一名<某种类型的用户>,我希望<达成某些目的>,这样可以<开发的价值>。验收标准建议列举多条。', '请你扮演一名资深的产品经理。', '负责产品战略、设计、开发、数据分析、用户体验、团队管理、沟通协调等方面,需要具备多种技能和能力,以实现产品目标和公司战略。', 'system', '2023-08-10 13:24:14', 'active');
INSERT INTO `zt_prompt` (`name`, `model`, `module`, `source`, `targetForm`, `purpose`, `elaboration`, `role`, `characterization`, `createdBy`, `createdDate`, `status`) VALUES ('一键拆用例', 0, 'story', ',story.title,story.spec,story.verify,story.product,story.module,story.pri,story.category,story.estimate,', 'story.testcasecreate', '为这个需求生成一个或多个对应的测试用例。', '', '作为一名资深的测试工程师。', '熟悉测试流程和方法,精通自动化测试和性能测试,能够设计和编写测试用例和测试脚本,擅长问题诊断和分析,熟悉敏捷开发和持续集成,能够协调多部门合作和项目管理。开发工程师应该是专业且严谨的。', 'system', '2023-08-10 13:24:14', 'active');
INSERT INTO `zt_prompt` (`name`, `model`, `module`, `source`, `targetForm`, `purpose`, `elaboration`, `role`, `characterization`, `createdBy`, `createdDate`, `status`) VALUES ('任务润色', 0, 'task', ',task.name,task.desc,task.pri,task.status,task.estimate,task.consumed,task.left,task.progress,task.estStarted,task.realStarted,', 'task.edit', '优化其中各字段的表述,使表述清晰准确,明确任务目标。', '必要时指出任务的风险点。', '你是一名经验丰富的开发工程师。', '精通多种编程语言和框架、熟悉前后端技术和架构、擅长性能优化和安全防护、熟悉云计算和容器化技术、能够协调多人协作和项目管理。', 'system', '2023-08-10 13:24:14', 'active');
INSERT INTO `zt_prompt` (`name`, `model`, `module`, `source`, `targetForm`, `purpose`, `elaboration`, `role`, `characterization`, `createdBy`, `createdDate`, `status`) VALUES ('需求转任务', 0, 'story', ',story.title,story.spec,story.verify,story.product,story.module,story.pri,story.category,story.estimate,', 'story.totask', '将需求转化为对应的开发任务要求。', '', '请你扮演一名资深的产品经理。', '负责产品战略、设计、开发、数据分析、用户体验、团队管理、沟通协调等方面,需要具备多种技能和能力,以实现产品目标和公司战略。同时精通多种编程语言和框架、熟悉前后端技术。', 'system', '2023-08-10 13:24:14', 'active');
INSERT INTO `zt_prompt` (`name`, `model`, `module`, `source`, `targetForm`, `purpose`, `elaboration`, `role`, `characterization`, `createdBy`, `createdDate`, `status`) VALUES ('Bug润色', 0, 'bug', ',bug.title,bug.steps,bug.severity,bug.pri,bug.status,bug.confirmed,bug.type,', 'bug.edit', '优化其中各字段的表述,使表述清晰准确。', 'Bug描述格式建议使用:[步骤]<一步一步复现Bug的步骤>[结果]<Bug导致的结果描述>[期望]<Bug修复后期望的描述>', '作为一名资深的测试工程师。', '熟悉测试流程和方法,精通自动化测试和性能测试,能够设计和编写测试用例和测试脚本,擅长问题诊断和分析,熟悉敏捷开发和持续集成,能够协调多部门合作和项目管理。开发工程师应该是专业且严谨的。', 'system', '2023-08-10 13:24:14', 'active');
INSERT INTO `zt_prompt` (`name`, `model`, `module`, `source`, `targetForm`, `purpose`, `elaboration`, `role`, `characterization`, `createdBy`, `createdDate`, `status`) VALUES ('文档润色', 0, 'doc', ',doc.content,doc.title,', 'doc.edit', '我希望你能帮我润色标题和文档正文。', '要求语句要通顺,逻辑要清晰,没有错别字且更具结构化。', '你是一名文章写得很好的文案编辑。', '文笔流畅、条理清晰。精通广告文案写作和编辑,擅长创意思维和品牌策略,能够进行市场调研和竞品分析,具有敏锐的审美和语言表达能力,能够协调多部门合作和项目管理,具有良好的沟通和协调能力。', 'system', '2023-08-31 15:54:57', 'active');
ALTER TABLE `zt_ticket` ADD `subStatus` varchar(30) NOT NULL DEFAULT '';
REPLACE INTO `zt_privrelation` (`priv`, `type`, `relationPriv`) VALUES ('kanban-view', 'depend', 'kanban-space');
CREATE TABLE IF NOT EXISTS `zt_practice` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`module` mediumint(8) unsigned NOT NULL DEFAULT '0',
`code` char(50) NOT NULL DEFAULT '',
`title` varchar(255) NOT NULL DEFAULT '',
`labels` varchar(255) NOT NULL DEFAULT '',
`summary` varchar(255) NOT NULL DEFAULT '',
`content` text NULL,
`contributor` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE UNIQUE INDEX `code` ON `zt_practice`(`code`);
REPLACE INTO
`zt_priv` (`id`, `module`, `method`, `parent`, `edition`, `vision`, `system`, `order`)
VALUES
(2161, 'traincourse', 'practice', 681, ',biz,max,ipd,', ',rnd,', '1', 5),
(2162, 'traincourse', 'practiceBrowse', 681, ',biz,max,ipd,', ',rnd,', '1', 10),
(2163, 'traincourse', 'practiceView', 681, ',biz,max,ipd,', ',rnd,', '1', 15),
(2164, 'traincourse', 'updatePractice', 681, ',biz,max,ipd,', ',rnd,', '1', 20);
REPLACE INTO
`zt_privmanager` (`id`, `parent`, `code`, `type`, `edition`, `vision`, `order`)
VALUES
(681, 566, '', 'package', ',open,biz,max,ipd,', ',rnd,', 8);
REPLACE INTO
`zt_privlang` (`objectID`, `objectType`, `lang`, `key`, `value`, `desc`)
VALUES
(681, 'manager', 'zh-cn', '', '实践库', ''),
(681, 'manager', 'zh-tw', '', '實踐庫', ''),
(681, 'manager', 'en', '', 'Practice Library', ''),
(681, 'manager', 'de', '', 'Practice Library', ''),
(681, 'manager', 'fr', '', 'Practice Library', ''),
(2161, 'priv', 'zh-cn', 'traincourse-practiceAction', '', ''),
(2161, 'priv', 'zh-tw', 'traincourse-practiceAction', '', ''),
(2161, 'priv', 'en', 'traincourse-practiceAction', '', ''),
(2161, 'priv', 'de', 'traincourse-practiceAction', '', ''),
(2161, 'priv', 'fr', 'traincourse-practiceAction', '', ''),
(2162, 'priv', 'zh-cn', 'traincourse-practiceBrowse', '', ''),
(2162, 'priv', 'zh-tw', 'traincourse-practiceBrowse', '', ''),
(2162, 'priv', 'en', 'traincourse-practiceBrowse', '', ''),
(2162, 'priv', 'de', 'traincourse-practiceBrowse', '', ''),
(2162, 'priv', 'fr', 'traincourse-practiceBrowse', '', ''),
(2163, 'priv', 'zh-cn', 'traincourse-practiceView' , '', ''),
(2163, 'priv', 'zh-tw', 'traincourse-practiceView' , '', ''),
(2163, 'priv', 'en', 'traincourse-practiceView' , '', ''),
(2163, 'priv', 'de', 'traincourse-practiceView' , '', ''),
(2163, 'priv', 'fr', 'traincourse-practiceView' , '', ''),
(2164, 'priv', 'zh-cn', 'traincourse-updatePractice', '', ''),
(2164, 'priv', 'zh-tw', 'traincourse-updatePractice', '', ''),
(2164, 'priv', 'en', 'traincourse-updatePractice', '', ''),
(2164, 'priv', 'de', 'traincourse-updatePractice', '', ''),
(2164, 'priv', 'fr', 'traincourse-updatePractice', '', '');
REPLACE INTO
`zt_privrelation` (`priv`, `type`, `relationPriv`)
VALUES
('traincourse-practiceBrowse', 'depend', 'traincourse-practice'),
('traincourse-practiceView', 'depend', 'traincourse-practice'), ('traincourse-practiceView', 'depend', 'traincourse-practiceBrowse'),
('traincourse-updatePractice', 'depend', 'traincourse-practice'), ('traincourse-updatePractice', 'depend', 'traincourse-practiceBrowse'),
('traincourse-practice', 'recommend', 'traincourse-practiceBrowse'), ('traincourse-practice', 'recommend', 'traincourse-practiceView'),
('traincourse-practiceBrowse', 'recommend', 'traincourse-practiceView'),
('traincourse-updatePractice', 'recommend', 'traincourse-practiceView');
UPDATE zt_privlang SET `key` = 'execution-computeBurn' WHERE `key` = 'execution-computeBurnAction';
DELETE FROM zt_priv WHERE module='search' and method = 'select';
DELETE FROM zt_privlang WHERE `key`='search-select';
UPDATE `zt_privlang` SET `value`='删除风险' WHERE `objectID`=197 AND `objectType`='manager' AND `lang`='zh-cn';
+167 -6
View File
File diff suppressed because one or more lines are too long
+217
View File
@@ -1,3 +1,220 @@
2023-08-29 18.7
完成的需求
开源版:
41289 调整创建合并请求逻辑
42964 优化代码库访问权限的设置
44546 应用管理支持手工配置Nexus
44547 在DevOps视图二级菜单中增加制品库
45503 DevOps二级导航打印应用菜单
45504 实现手动添加应用的功能
45505 实现应用列表
45507 在应用列表页面打印添加应用按钮
45508 实现从应用市场安装应用的功能
45518 实现应用市场卡片列表功能
45521 实现在应用市场浏览应用详情的功能
45535 实现升级应用的功能
45536 实现访问应用的功能
45537 实现启动应用的功能
45538 实现关闭应用的功能
45539 实现浏览应用详情的功能
45655 实现应用数据库管理的功能
45656 实现设置应用的功能
45657 实现删除应用的功能
45747 平台合码后将渠成菜单落地在禅道devops平台
45925 调整DevOps二级导航栏
45928 添加代码库二级导航栏
45933 代码页面样式优化
45934 文件详情页面样式优化
45935 添加代码库表单样式优化
45936 代码库列表样式优化
45938 批量添加代码库表单样式调整
45939 编辑代码库表单样式优化
45940 代码库信息同步页面样式调整
45942 提交详情页面样式调整
45943 文件追溯页面样式调整
45944 下载代码弹窗样式调整
45946 所有版本页面样式调整
45947 比较差异页面样式调整
46203 流水线列表样式优化
46204 添加流水线表单样式优化
46205 编辑流水线表单样式优化
46206 流水线执行日志页面优化
46207 流水线执行历史列表样式优化
46218 合并请求列表样式优化
46219 创建合并请求表单样式优化
46220 编辑合并请求表单样式优化
46221 合并请求概览页面样式优化
46222 合并请求比对代码页面样式优化
46223 合并请求关联软件需求页面样式优化
46224 合并请求关联Bug页面样式优化
46225 合并请求关联任务页面样式优化
46227 合并请求关联的软件需求列表页面样式优化
46228 合并请求关联的Bug列表页面样式优化
46229 合并请求关联的任务列表页面样式优化
46725 在后台首页中添加DevOps设置
46740 在DevOps设置二级导航栏添加资源
46767 在资源下管理机房
46768 在资源下管理账号
46769 在资源下管理城市
46770 在资源下管理服务商
46771 在资源下管理CPU品牌
46772 在资源下管理系统版本
46773 将指令设置迁移至DevOps设置
46827 在DevOps设置二级导航栏添加平台
46829 在平台下管理数据库
46830 在平台下管理域名
46832 在平台下管理对象存储
47025 流水线列表1.5级下拉菜单添加产品分组
47089 调整编辑合并请求逻辑
47146 在资源下管理主机
47257 实现从应用市场添加应用的功能
47588 开源版包含企业版AI配置的功能
47589 开源版包含企业版语言模型的功能
47591 后台实现设置语言模型的权限配置
47592 内置需求润色提词
47593 内置一键拆用例提词
47617 语言模型页面新增升级引导的超链接
47664 开源版包含企业版提词的部分功能
47680 后台实现发布提词的权限配置
47681 后台实现下架提词的权限配置
47682 后台实现浏览提词列表的权限配置
47683 后台实现查看提词详情的权限配置
47719 使用q命令行安装DevOps平台版本
47838 实现平台仪表盘
47989 开源版增加升级引导弹窗组件
47991 内置Bug润色提词
47992 内置任务润色提词
47993 内置需求转任务提词
47994 内置Bug转需求提词
47995 内置角色模板
48079 开源版增加升级引导的内容展示
48080 企业版增加升级引导弹窗组件
48081 企业版增加升级引导的内容展示
48133 开源版、企业版升级引导的英文版内容展示
48403 浏览制品库列表
48643 添加制品库
48645 版本关联制品
48720 编辑制品库
48724 删除制品库
48727 添加制品库权限
48732 应用市场添加应用时自定义名称
企业版:
45266 后台的设置区块新增AI配置的入口
45270 后台实现AI配置二级导航
45271 AI配置二级导航中新增语言模型模块
45272 AI配置实现大模型参数配置页面
45273 语言模型模块中实现设置语言模型参数的功能
45274 语言模型模块实现查看语言模型详情的功能
45277 AI配置二级导航中新增提词模块
45278 AI配置中二级导航“提词”的跳转逻辑
45279 无提词情况下提词列表页面的展示样式
45280 提词列表页面实现分组树功能
45281 创建提词时实现设置基本信息功能
45282 提词设计器实现设置指定角色参数的功能
45283 提词设计器打印流程向导组件
45285 提词设计器实现流程向导组件的交互逻辑
45286 创建提词实现提词设计器功能
45288 提词设计器实现设置确认操作参数的功能
45289 点击分组树中分组后列表页面的展示逻辑
45290 提词列表字段和操作栏样式展示
45291 提词设计器实现设置准备发布参数的功能
45292 提词列表展示样式
45293 提词列表页面创建提词弹窗的显示和创建逻辑
45294 提词详情页面提词ID和提词名称的展示
45329 提词详情页面实现提词设计信息的展示
45330 提词详情页面实现创建提词功能
45334 选择对象页面实现已选对象和数据的排序功能
45336 选择对象页面实现选择对象和选择数据的功能
45382 实现将设计的提词封装成提词应用的功能
45391 提词列表页面实现创建提词的功能
45392 前台页面打印提词应用的下拉菜单按钮
45393 前台页面打印提词应用的按钮
45407 提词设计器打印全局返回按钮
45408 提词设计器打印全局保存按钮
45409 提词设计器实现返回按钮的交互逻辑
45410 提词设计器实现存为草稿按钮的交互逻辑
45418 提词设计中结果处理页面实现选择表单的功能
45419 设计提词时实现流程向导组件的交互样式
45420 提词详情页面实现历史记录功能
45421 提词详情页面实现基本信息的展示
45422 提词详情页面实现悬浮操作栏
45423 提词详情页面实现选择上一个、下一个提词功能
45427 提词设计中结果处理页面显示输入给GPT的内容
45431 语言模型模块实现编辑语言模型参数的功能
45435 提词设计中结果处理页面实现下一步和去调试的功能
45455 提词列表页面实现全部的检索标签
45715 提词前台实现设计提词的弹窗
45716 设计提词弹窗实现弹窗标题
45717 实现调试提词后点击保存按钮的返回逻辑
45718 设计提词弹窗打印关闭按钮
45719 设计提词弹窗实现参数设置区块
45720 设计提词弹窗实现输入给语言模型的内容展示区块
45737 提词列表操作栏交互逻辑
45738 提词阶段的交互逻辑
45766 提词列表实现检索标签功能
45768 提词列表实现统计信息和分页功能
45866 前台实现提词调试页面
45872 后台提词实现点击去调试按钮的跳转规则
45915 提词详情页面实现提词版本管理的功能
45960 设计提词弹窗打印保存按钮
46851 实现下架提词的二次确认弹窗
46852 实现删除提词的二次确认弹窗
46883 提词调试页实现发布提词的功能
46884 提词调试页实现重新生成提词结果的功能
47142 实现提词名称的唯一性
47152 前台页面增加 AI 提词的描述展示
47250 提词名称增加限制字符数
47305 后台权限新增AI配置模块
47308 AI配置权限新增提词权限包
47309 后台实现浏览提词列表的权限配置
47310 后台实现查看提词详情的权限配置
47315 后台实现创建提词的权限配置
47318 后台实现编辑提词的权限配置
47319 后台实现设计提词的权限配置
47320 后台实现删除提词的权限配置
47323 AI配置权限新增提词和语言模型权限包
47327 后台实现设置语言模型的权限配置
47328 提词应用的使用依赖于结果处理表单页权限
47547 从提词设计器点击去调试按钮跳转至表单页
47628 前台使用提词时实现异常状态提示
47669 后台实现发布提词的权限配置
47671 后台实现下架提词的权限配置
47720 合并提词设计器中指定角色配置项的标题
47722 设置指定角色时可以引用角色模板
47723 指定角色表单页打印角色模板区块的开关
47727 指定角色表单页实现角色模板区块
47731 在角色模板区块可以添加角色模板
47732 在角色模板区块以卡片的方式浏览模板
47741 在角色模板区块可以编辑角色模板
47743 在角色模板区块可以删除角色模板
47746 设置指定角色时可以应用角色模板
47752 指定角色表单页打印存为角色模板表单项
47753 隐藏AI配置中GPT-3.5中的“-”
47756 优化提词调试页面的按钮及位置
47759 调整结果处理页面点击去调试后的交互逻辑与反馈样式
47771 优化结果处理页面中去调试按钮的提示文案
47786 将选择对象中的文案从数据改为字段
47787 调整选择对象页面中对象的排序
47902 调整AI配置卡片的介绍文案
48840 学堂二级菜单增加实践库
48842 学堂实践库增加介绍区块
48845 学堂实践库增加全部实践区块
48852 学堂实践库增加最新实践区块
48853 学堂实践库增加推荐实践区块
48856 学堂实践库增加共创团队的链接
48860 实现实践列表页面
48863 实践列表增加搜索功能
48864 实现实践详情页面的左侧模块树
48865 实现实践详情的内容区域
48866 实践详情增加面包屑导航
48951 系统全局搜索增加实践的内容
49016 学堂实践库的更新机制
49021 学堂实践库的权限梳理
修复的Bug
开源版:
37666 地盘仪表盘 我近期参与的项目 区块顺序显示异常
37670 linux一键安装包启动无反应
2023-08-15 18.6
完成的需求
开源版:
+137 -38
View File
@@ -394,19 +394,20 @@ class baseControl
*
* @param string $moduleName module name
* @param string $methodName method name
* @param string $viewDir
* @access public
* @return string the view file
*/
public function setViewFile($moduleName, $methodName)
public function setViewFile(string $moduleName, string $methodName, string $viewDir = 'view')
{
$moduleName = strtolower(trim($moduleName));
$methodName = strtolower(trim($methodName));
$modulePath = $this->app->getModulePath($this->appName, $moduleName);
$viewExtPath = $this->app->getModuleExtPath($this->appName, $moduleName, 'view');
$viewExtPath = $this->app->getModuleExtPath($moduleName, $viewDir);
$viewType = $this->viewType == 'mhtml' ? 'html' : $this->viewType;
$mainViewFile = $modulePath . 'view' . DS . $this->devicePrefix . $methodName . '.' . $viewType . '.php';
$mainViewFile = $modulePath . $viewDir . DS . $this->devicePrefix . $methodName . '.' . $viewType . '.php';
$viewFile = $mainViewFile;
if(!empty($viewExtPath))
@@ -416,7 +417,7 @@ class baseControl
$viewFile = file_exists($commonExtViewFile) ? $commonExtViewFile : $mainViewFile;
$viewFile = (!empty($siteExtViewFile) and file_exists($siteExtViewFile)) ? $siteExtViewFile : $viewFile;
if(!is_file($viewFile)) $this->app->triggerError("the view file $viewFile not found", __FILE__, __LINE__, $exit = true);
if(!is_file($viewFile)) $this->app->triggerError("the view file $viewFile not found", __FILE__, __LINE__, true);
$commonExtHookFiles = glob($viewExtPath['common'] . $this->devicePrefix . $methodName . ".*.{$viewType}.hook.php");
$siteExtHookFiles = empty($viewExtPath['site']) ? '' : glob($viewExtPath['site'] . $this->devicePrefix . $methodName . ".*.{$viewType}.hook.php");
@@ -442,7 +443,7 @@ class baseControl
* Find extViewFile in ext/_$siteCode/view first, then try ext/view/.
*/
$moduleName = basename(dirname(dirname(realpath($viewFile))));
$extPath = $this->app->getModuleExtPath('', $moduleName, 'view');
$extPath = $this->app->getModuleExtPath($moduleName, 'view');
$checkedOrder = array('site', 'saas', 'custom', 'vision', 'xuan', 'common');
$fileName = basename($viewFile);
@@ -468,45 +469,45 @@ class baseControl
*
* @param string $moduleName
* @param string $methodName
* @param string $suffix
* @access public
* @return string
*/
public function getCSS($moduleName, $methodName)
public function getCSS(string $moduleName, string $methodName, string $suffix = ''): string
{
$moduleName = strtolower(trim($moduleName));
$methodName = strtolower(trim($methodName));
$modulePath = $this->app->getModulePath($this->appName, $moduleName);
$cssExtPath = $this->app->getModuleExtPath($this->appName, $moduleName, 'css');
$cssExtPath = $this->app->getModuleExtPath($moduleName, 'css');
$clientLang = $this->app->getClientLang();
$notCNLang = strpos('|zh-cn|zh-tw|', "|{$clientLang}|") === false;
$notCNLang = mb_strpos('|zh-cn|zh-tw|', "|{$clientLang}|") === false;
$css = '';
$devicePrefix = $this->devicePrefix;
$mainCssPath = $modulePath . 'css' . DS;
/* Common css file. like module/story/css/common.css. */
$mainCssFile = $mainCssPath . $devicePrefix . 'common.css';
$mainCssFile = $mainCssPath . $devicePrefix . "common{$suffix}.css";
if(is_file($mainCssFile)) $css .= file_get_contents($mainCssFile);
/* Common css file with lang. like module/story/css/common.en.css. */
$mainCssLangFile = $mainCssPath . $devicePrefix . "common.{$clientLang}.css";
if(!file_exists($mainCssLangFile) and $notCNLang) $mainCssLangFile = $mainCssPath . $devicePrefix . "common.en.css";
$mainCssLangFile = $mainCssPath . $devicePrefix . "common.{$clientLang}{$suffix}.css";
if(!file_exists($mainCssLangFile) and $notCNLang) $mainCssLangFile = $mainCssPath . $devicePrefix . "common{$suffix}.en.css";
if(is_file($mainCssLangFile)) $css .= file_get_contents($mainCssLangFile);
/* Method css file. like module/story/css/create.css. */
$methodCssFile = $mainCssPath . $devicePrefix . $methodName . '.css';
$methodCssFile = $mainCssPath . $devicePrefix . $methodName . "$suffix.css";
if(is_file($methodCssFile)) $css .= file_get_contents($methodCssFile);
/* Method css file with lang. like module/story/css/create.en.css. */
$methodCssLangFile = $mainCssPath . $devicePrefix . "{$methodName}.{$clientLang}.css";
if(!file_exists($methodCssLangFile) and $notCNLang) $methodCssLangFile = $mainCssPath . $devicePrefix . "{$methodName}.en.css";
$methodCssLangFile = $mainCssPath . $devicePrefix . "{$methodName}{$suffix}.{$clientLang}.css";
if(!file_exists($methodCssLangFile) and $notCNLang) $methodCssLangFile = $mainCssPath . $devicePrefix . "{$methodName}{$suffix}.en.css";
if(is_file($methodCssLangFile)) $css .= file_get_contents($methodCssLangFile);
if(!empty($cssExtPath))
{
$realModulePath = realPath($modulePath);
foreach($cssExtPath as $cssPath)
{
if(empty($cssPath)) continue;
@@ -514,11 +515,11 @@ class baseControl
$cssMethodExt = $cssPath . $methodName . DS;
$cssCommonExt = $cssPath . 'common' . DS;
$cssExtFiles = glob($cssCommonExt . $devicePrefix . '*.css');
if(!empty($cssExtFiles) and is_array($cssExtFiles)) $css .= $this->getExtCSS($cssExtFiles);
$cssExtFiles = glob($cssCommonExt . $devicePrefix . "*{$suffix}.css");
if(!empty($cssExtFiles) and is_array($cssExtFiles)) $css .= $this->getExtCSS($cssExtFiles, $suffix);
$cssExtFiles = glob($cssMethodExt . $devicePrefix . '*.css');
if(!empty($cssExtFiles) and is_array($cssExtFiles)) $css .= $this->getExtCSS($cssExtFiles);
$cssExtFiles = glob($cssMethodExt . $devicePrefix . "*{$suffix}.css");
if(!empty($cssExtFiles) and is_array($cssExtFiles)) $css .= $this->getExtCSS($cssExtFiles, $suffix);
}
}
@@ -528,19 +529,20 @@ class baseControl
/**
* Get extension css and extension css with lang.
*
* @param array $files
* @param array $files
* @param string $suffix
* @access public
* @return string
*/
public function getExtCSS($files)
public function getExtCSS(array $files, string $suffix = ''): string
{
$clientLang = $this->app->getClientLang();
$notCNLang = strpos('|zh-cn|zh-tw|', "|{$clientLang}|") === false;
$notCNLang = mb_strpos('|zh-cn|zh-tw|', "|{$clientLang}|") === false;
$filePairs = array();
foreach($files as $cssFile)
{
$fileName = basename($cssFile);
$fileName = basename((string) $cssFile);
$filePairs[$fileName] = $cssFile;
}
@@ -552,23 +554,26 @@ class baseControl
{
/* Method extension css file. like module/story/ext/css/create/effort.css. */
$css .= file_get_contents($cssFile);
list($code) = explode('.', $fileName);
[$code] = explode('.', $fileName);
}
else
{
list($code) = explode('.', $fileName);
[$code] = explode('.', $fileName);
if(isset($usedCodes[$code])) continue;
}
/* Method extension css file. like module/story/ext/css/create/effort.zh-cn.css. */
if(isset($filePairs["{$code}.{$clientLang}.css"]))
if(isset($filePairs["{$code}.{$clientLang}{$suffix}.css"]))
{
$css .= file_get_contents($filePairs["{$code}.{$clientLang}.css"]);
$css .= file_get_contents($filePairs["{$code}.{$clientLang}{$suffix}.css"]);
}
elseif($notCNLang and isset($filePairs["{$code}.en.css"]))
elseif($notCNLang and isset($filePairs["{$code}.en{$suffix}.css"]))
{
$css .= file_get_contents($filePairs["{$code}.en.css"]);
$css .= file_get_contents($filePairs["{$code}.en{$suffix}.css"]);
}
elseif($notCNLang and isset($filePairs["{$code}{$suffix}.css"]))
{
$css .= file_get_contents($filePairs["{$code}{$suffix}.css"]);
}
$usedCodes[$code] = $code;
}
@@ -585,17 +590,17 @@ class baseControl
* @access public
* @return string
*/
public function getJS($moduleName, $methodName)
public function getJS(string $moduleName, string $methodName, string $suffix = ''): string
{
$moduleName = strtolower(trim($moduleName));
$methodName = strtolower(trim($methodName));
$modulePath = $this->app->getModulePath($this->appName, $moduleName);
$jsExtPath = $this->app->getModuleExtPath($this->appName, $moduleName, 'js');
$jsExtPath = $this->app->getModuleExtPath($moduleName, 'js');
$js = '';
$mainJsFile = $modulePath . 'js' . DS . $this->devicePrefix . 'common.js';
$methodJsFile = $modulePath . 'js' . DS . $this->devicePrefix . $methodName . '.js';
$mainJsFile = $modulePath . 'js' . DS . $this->devicePrefix . "common{$suffix}.js";
$methodJsFile = $modulePath . 'js' . DS . $this->devicePrefix . $methodName . $suffix . '.js';
if(file_exists($mainJsFile)) $js .= file_get_contents($mainJsFile);
if(is_file($methodJsFile)) $js .= file_get_contents($methodJsFile);
@@ -608,10 +613,10 @@ class baseControl
$jsMethodExt = $jsPath . $methodName . DS;
$jsCommonExt = $jsPath . 'common' . DS;
$jsExtFiles = glob($jsCommonExt . $this->devicePrefix . '*.js');
$jsExtFiles = glob($jsCommonExt . $this->devicePrefix . "*{$suffix}.js");
if(!empty($jsExtFiles) and is_array($jsExtFiles)) foreach($jsExtFiles as $jsFile) $js .= file_get_contents($jsFile);
$jsExtFiles = glob($jsMethodExt . $this->devicePrefix . '*.js');
$jsExtFiles = glob($jsMethodExt . $this->devicePrefix . "*{$suffix}.js");
if(!empty($jsExtFiles) and is_array($jsExtFiles)) foreach($jsExtFiles as $jsFile) $js .= file_get_contents($jsFile);
}
}
@@ -798,7 +803,7 @@ class baseControl
*/
$modulePath = $this->app->getModulePath($appName, $moduleName);
$moduleControlFile = $modulePath . 'control.php';
$actionExtPath = $this->app->getModuleExtPath($appName, $moduleName, 'control');
$actionExtPath = $this->app->getModuleExtPath($moduleName, 'control');
$file2Included = $moduleControlFile;
if(!empty($actionExtPath))
@@ -907,12 +912,106 @@ class baseControl
* @access public
* @return void
*/
public function display($moduleName = '', $methodName = '')
public function display(string $moduleName = '', string $methodName = '')
{
if($this->viewType === 'html' && (!isset($_GET['zin']) || $_GET['zin'] != '0'))
{
if(empty($moduleName)) $moduleName = $this->moduleName;
if(empty($methodName)) $methodName = $this->methodName;
$modulePath = $this->app->getModulePath($this->appName, $moduleName);
$viewType = $this->viewType == 'mhtml' ? 'html' : $this->viewType;
$mainViewFile = $modulePath . 'ui' . DS . $this->devicePrefix . $methodName . '.' . $viewType . '.php';
if(file_exists($mainViewFile)) return $this->render($moduleName, $methodName);
}
if(empty($this->output)) $this->parse($moduleName, $methodName);
echo $this->output;
}
/**
* 向浏览器输出内容。
* Print the content of the view.
*
* @param string $moduleName module name
* @param string $methodName method name
* @access public
* @return void
*/
public function render($moduleName = '', $methodName = '')
{
if(isset($_GET['zin']) && $_GET['zin'] == '0')
{
$this->display($moduleName, $methodName);
return;
}
if(empty($moduleName)) $moduleName = $this->moduleName;
if(empty($methodName)) $methodName = $this->methodName;
/* Load zin lib */
$this->app->loadClass('zin', true);
\zin\loadConfig();
/**
* 设置视图文件。(PHP7有一个bug,不能直接$viewFile = $this->setViewFile())。
* Set viewFile. (Can't assign $viewFile = $this->setViewFile() directly because one php7's bug.)
*/
$results = $this->setViewFile($moduleName, $methodName, 'ui');
$viewFile = $results;
if(is_array($results)) extract($results);
/**
* 获得当前页面的CSS和JS。
* Get css and js codes for current method.
*/
$css = $this->getCSS($moduleName, $methodName, '.ui');
$js = $this->getJS($moduleName, $methodName, '.ui');
if($css) $this->view->pageCSS = $css;
if($js) $this->view->pageJS = $js;
/**
* 切换到视图文件所在的目录,以保证视图文件里面的include语句能够正常运行。
* Change the dir to the view file to keep the relative paths work.
*/
$currentPWD = getcwd();
chdir(dirname($viewFile));
/**
* Set zin context data
*/
\zin\zin::$globalRenderList = array();
\zin\zin::$enabledGlobalRender = true;
\zin\zin::$rendered = false;
\zin\zin::$rawContentCalled = false;
\zin\zin::$data = (array)$this->view;
\zin\zin::$data['zinDebug'] = array();
if($this->config->debug && $this->config->debug >= 2)
{
\zin\zin::$data['zinDebug']['trace'] = $this->app->loadClass('trace')->getTrace();
}
/**
* 使用extract和ob方法渲染$viewFile里面的代码。
* Use extract and ob functions to eval the codes in $viewFile.
*/
extract(\zin\zin::$data);
ob_start();
include $viewFile;
if(!\zin\zin::$rendered) \zin\render();
$content = ob_get_clean();
ob_start();
echo $content;
/**
* 渲染完毕后,再切换回之前的路径。
* At the end, chang the dir to the previous.
*/
chdir($currentPWD);
}
/**
* 直接输出data数据,通常用于ajax请求中。
* Send data directly, for ajax requests.
+202 -7
View File
@@ -251,8 +251,8 @@ class baseHelper
{
if(is_array($idList))
{
foreach($idList as $key=>$value) $idList[$key] = addslashes($value);
return "IN ('" . join("','", $idList) . "')";
foreach($idList as $key => $value) $idList[$key] = empty($value) ? '' : addslashes($value);
return "IN ('" . implode("','", $idList) . "')";
}
if(is_null($idList)) $idList = '';
@@ -446,7 +446,7 @@ class baseHelper
if(function_exists('mb_substr')) $string = mb_substr($string, 0, $length, 'utf-8');
preg_match_all("/./su", $string, $data);
$string = join("", array_slice($data[0], 0, $length));
$string = implode("", array_slice($data[0], 0, $length));
return ($string != $rawString) ? $string . $append : $string;
}
@@ -680,15 +680,21 @@ class baseHelper
* 检查是否是AJAX请求。
* Check is ajax request.
*
* @param ?string $type zin|modal|fetch
* @static
* @access public
* @return bool
*/
public static function isAjaxRequest()
public static function isAjaxRequest(?string $type = null): bool
{
if(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') return true;
if(isset($_GET['HTTP_X_REQUESTED_WITH']) && $_GET['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') return true;
return false;
$isAjax = (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') || (isset($_GET['HTTP_X_REQUESTED_WITH']) && $_GET['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest');
if($isAjax === false) return false;
if($type === 'zin') return array_key_exists('HTTP_X_ZIN_OPTIONS', $_SERVER);
if($type === 'modal') return isset($_SERVER['HTTP_X_ZUI_MODAL']) && $_SERVER['HTTP_X_ZUI_MODAL'] == true;
if($type === 'fetch') return !array_key_exists('HTTP_X_ZIN_OPTIONS', $_SERVER) && !(isset($_SERVER['HTTP_X_ZUI_MODAL']) && $_SERVER['HTTP_X_ZUI_MODAL'] == true);
return $isAjax;
}
/**
@@ -788,6 +794,101 @@ class baseHelper
return null;
}
/**
* Send a cookie.
*
* @param string $name
* @param string $value
* @param int|null $expire
* @param string|null $path
* @param string $domain
* @param bool|null $secure
* @param bool $httponly
* @static
* @access public
* @return bool
*/
public static function setcookie(string $name, string $value = '', int $expire = null, string $path = null, string $domain = '', bool $secure = null, bool $httponly = true)
{
global $config, $app;
if($expire === null) $expire = $config->cookieLife;
if($path === null) $path = $config->webRoot;
if($secure === null) $secure = $config->cookieSecure;
if(isset($app->worker))
{
$app->worker->response->setCookie($name, $value, $expire, $path, $domain, $secure, $httponly);
}
else
{
return setcookie($name, $value, $expire, $path, $domain, $secure, $httponly);
}
}
/**
* 设置状态码。
* Set status code.
*
* @param int $code
* @static
* @access public
* @return void
*/
static public function setStatus(int $code)
{
global $app;
if(isset($app->worker))
{
$app->worker->response->setStatus($code);
}
else
{
$PHRASES = array(
100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing',
200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-status', 208 => 'Already Reported',
300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => 'Switch Proxy', 307 => 'Temporary Redirect',
400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed', 418 => 'I\'m a teapot', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Unordered Collection', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 451 => 'Unavailable For Legal Reasons',
500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 511 => 'Network Authentication Required',
);
header('HTTP/1.1 ' . (string)$code . ' ' . $PHRASES[$code], true, $code);
}
}
/**
* 发送HTTP头信息。
* Send http header.
*
* @param string $key
* @param string $value
* @param bool $replace
* @param int $response_code
* @static
* @access public
* @return void
*/
static public function header(string $key, string $value, bool $replace = true, int $response_code = 0)
{
global $app;
if(isset($app->worker))
{
$key = trim(strtolower($key));
$app->worker->response->setHeader($key, $value);
if($key == 'location')
{
$app->worker->response->setStatus(302);
helper::end();
}
}
else
{
header($key . ': ' . $value, $replace, $response_code);
}
}
/**
* 检查是否启用缓存。
* Check is enable cache.
@@ -800,6 +901,100 @@ class baseHelper
global $config;
return $config->cache->enable;
}
/**
* Generate rand string.
*
* @param int $length
* @access public
* @return string
*/
static public function randStr($length = 4)
{
$seeds = str_shuffle('abcdefghijklmnpqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ123456789');
return substr($seeds, 0, $length);
}
/**
* 获取二维数组中的某一列。
* Get array column to array.
*
* @param array $input
* @param int|string|null $columnKey
* @param int|string|null $indexKey
* @static
* @access public
* @return array
*/
public static function arrayColumn(array $input, $columnKey, $indexKey = null): array
{
/* If php version greater than 7, calling system functions returns. */
if(defined(PHP_VERSION_ID) && PHP_VERSION_ID >= 70000) return \array_column($input, $columnKey, $indexKey);
$output = array();
foreach($input as $row)
{
$key = $value = null;
$keySet = $valueSet = false;
if($indexKey !== null && array_key_exists($indexKey, (array) $row))
{
$keySet = true;
$key = \is_object($row) ? (string) $row->$indexKey : (string) $row[$indexKey];
}
if(null === $columnKey)
{
$valueSet = true;
$value = $row;
}
elseif(\is_array($row) && \array_key_exists($columnKey, $row))
{
$valueSet = true;
$value = $row[$columnKey];
}
elseif(\is_object($row) && \property_exists($row, $columnKey))
{
$valueSet = true;
$value = $row->$columnKey;
}
if($valueSet)
{
if($keySet)
{
$output[$key] = $value;
}
else
{
$output[] = $value;
}
}
}
return $output;
}
/**
* 在本地语言没有中文的环境下解析pathinfo.
* pathinfo of utf-8.
*
* @param string $filepath
* @access public
* @return array
*/
public static function mbPathinfo($filepath)
{
$ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => '');
preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im',$filepath,$m);
$ret['dirname'] = !empty($m[1]) ? $m[1] : '';
$ret['basename'] = !empty($m[2]) ? $m[2] : '';
$ret['extension'] = !empty($m[5]) ? $m[5] : '';
$ret['filename'] = !empty($m[3]) ? $m[3] : '';
return $ret;
}
}
//------------------------------- 常用函数。Some tool functions.-------------------------------//
+1 -1
View File
@@ -280,7 +280,7 @@ class baseModel
if(isset($this->$extensionClass)) return $this->$extensionClass;
/* 设置扩展的名字和相应的文件。Set extenson name and extension file. */
$moduleExtPath = $this->app->getModuleExtPath($this->appName, $moduleName, $type);
$moduleExtPath = $this->app->getModuleExtPath($moduleName, $type);
if(!empty($moduleExtPath['site'])) $extensionFile = $moduleExtPath['site'] . 'class/' . $extensionName . '.class.php';
if(!isset($extensionFile) or !file_exists($extensionFile)) $extensionFile = $moduleExtPath['saas'] . 'class/' . $extensionName . '.class.php';
if(!isset($extensionFile) or !file_exists($extensionFile)) $extensionFile = $moduleExtPath['custom'] . 'class/' . $extensionName . '.class.php';
+161 -36
View File
@@ -347,6 +347,14 @@ class baseRouter
*/
public $sessionID;
/**
* 请求开始时间。
* The start time of the request.
*
* @var float
*/
public $startTime;
/**
* 网站代号。
* The code of current site.
@@ -356,6 +364,14 @@ class baseRouter
*/
public $siteCode;
/**
* zin 请求时发生的错误信息。
* The errors occurred when zin request.
*
* @var array
*/
public $zinErrors = array();
/**
* 构造方法, 设置路径,类,超级变量等。注意:
* 1.应该使用createApp()方法实例化router类;
@@ -391,6 +407,7 @@ class baseRouter
$this->loadClass('front', $static = true);
$this->loadClass('filter', $static = true);
$this->loadClass('form', $static = true);
$this->loadClass('dbh', $static = true);
$this->loadClass('dao', $static = true);
$this->loadClass('mobile', $static = true);
@@ -436,6 +453,19 @@ class baseRouter
return new $className($appName, $appRoot);
}
/**
* 设置请求开始时间。
* The start time of the request.
*
* @param float $startTime
* @access public
* @return void
*/
public function setStartTime(float $startTime)
{
$this->startTime = $startTime;
}
//-------------------- 路径相关方法(Path related methods)--------------------//
/**
@@ -777,10 +807,10 @@ class baseRouter
{
$sql = new sql();
$account = $sql->quote($account);
$vision = $this->dbh->query("SELECT * FROM " . TABLE_CONFIG . " WHERE owner = $account AND `key` = 'vision' LIMIT 1")->fetch();
$vision = $this->dbQuery("SELECT * FROM " . TABLE_CONFIG . " WHERE owner = $account AND `key` = 'vision' LIMIT 1")->fetch();
if($vision) $vision = $vision->value;
$user = $this->dbh->query("SELECT * FROM " . TABLE_USER . " WHERE account = $account AND deleted = '0' LIMIT 1")->fetch();
$user = $this->dbQuery("SELECT * FROM " . TABLE_USER . " WHERE account = $account AND deleted = '0' LIMIT 1")->fetch();
if(!empty($user->visions))
{
$userVisions = explode(',', $user->visions);
@@ -790,6 +820,8 @@ class baseRouter
}
list($defaultVision) = explode(',', trim($this->config->visions, ','));
if($defaultVision != 'lite') $defaultVision = 'rnd';
if($vision and strpos($this->config->visions, ",{$vision},") === false) $vision = $defaultVision;
$this->config->vision = $vision ? $vision : $defaultVision;
@@ -803,7 +835,7 @@ class baseRouter
*/
public function getInstalledVersion()
{
$version = $this->dbh->query("SELECT `value` FROM " . TABLE_CONFIG . " WHERE `owner` = 'system' AND `key` = 'version' AND `module` = 'common' AND `section` = 'global' LIMIT 1")->fetch();
$version = $this->dbQuery("SELECT `value` FROM " . TABLE_CONFIG . " WHERE `owner` = 'system' AND `key` = 'version' AND `module` = 'common' AND `section` = 'global' LIMIT 1")->fetch();
$version = $version ? $version->value : '0.3.beta'; // No version, set as 0.3.beta.
if($version == '3.0.stable') $version = '3.0'; // convert 3.0.stable to 3.0.
return $version;
@@ -1519,13 +1551,12 @@ class baseRouter
/* Check file is encode by ioncube. */
$isEncrypted = false;
if(strpos($file2Included, 'extension' . DS . $this->config->edition . DS) !== false)
if(strpos($file2Included, 'extension') !== false)
{
$fp = fopen($file2Included, 'r');
$line1 = fgets($fp);
$line2 = fgets($fp);
fclose($fp);
if(strpos($line1, '<?php //') === 0 and strpos($line2, "if(!extension_loaded('ionCube Loader'))") === 0) $isEncrypted = true;
if(strpos($line1, '<?php //') === 0) $isEncrypted = true;
}
/*
@@ -1572,7 +1603,21 @@ class baseRouter
$default = $param->getDefaultValue();
}
$defaultParams[$name] = $default;
$type = 'string';
if(isset($paramDefaultType[$appName][$className][$methodName][$name]))
{
$type = $paramDefaultType[$appName][$className][$methodName][$name];
}
elseif(isset($paramDefaultType[$className][$methodName][$name]))
{
$type = $paramDefaultType[$className][$methodName][$name];
}
elseif(!$isEncrypted && method_exists($param, 'hasType') && $param->hasType())
{
$type = $param->getType()->getName();
}
$defaultParams[$name] = array('default' => $default, 'type' => $type);
}
/**
@@ -1675,7 +1720,7 @@ class baseRouter
* @access public
* @return string the extension path.
*/
public function getModuleExtPath($appName, $moduleName, $ext)
public function getModuleExtPath($moduleName, $ext)
{
$saasExtPath = $this->getExtensionRoot() . 'saas' . DS . $moduleName . DS . 'ext' . DS . $ext . DS;
@@ -1722,6 +1767,7 @@ class baseRouter
}
if($checkedModule[$var]) return true;
if(!$exit) return false;
if(!$var) return false;
$this->triggerError("'$var' illegal. ", __FILE__, __LINE__, $exit = true);
}
@@ -1752,7 +1798,7 @@ class baseRouter
*/
public function setActionExtFile()
{
$moduleExtPaths = $this->getModuleExtPath('', $this->moduleName, 'control');
$moduleExtPaths = $this->getModuleExtPath($this->moduleName, 'control');
/* 如果扩展目录为空,不包含任何扩展文件。If there's no ext paths return false.*/
if(empty($moduleExtPaths)) return false;
@@ -1796,7 +1842,7 @@ class baseRouter
*/
public function checkAPIFile()
{
$moduleExtPaths = $this->getModuleExtPath('', $this->moduleName, 'control');
$moduleExtPaths = $this->getModuleExtPath($this->moduleName, 'control');
/* 如果扩展目录为空,不包含任何扩展文件。If there's no ext paths return false.*/
if(empty($moduleExtPaths)) return false;
@@ -1857,7 +1903,7 @@ class baseRouter
$apiFiles = array();
$siteExtended = false;
$targetExtPaths = $this->getModuleExtPath($appName, $moduleName, $class);
$targetExtPaths = $this->getModuleExtPath($moduleName, $class);
foreach($targetExtPaths as $extType => $targetExtPath)
{
if(empty($targetExtPath)) continue;
@@ -2443,15 +2489,17 @@ class baseRouter
$passedParams = array_values($passedParams);
$i = 0;
foreach($defaultParams as $key => $defaultValue)
foreach($defaultParams as $key => $defaultItem)
{
if(isset($passedParams[$i]))
{
$defaultParams[$key] = strip_tags($passedParams[$i]);
$defaultParams[$key] = helper::convertType(strip_tags((string) $passedParams[$i]), $defaultItem['type']);
}
else
{
if($defaultValue === '_NOT_SET') $this->triggerError("The param '$key' should pass value. ", __FILE__, __LINE__, $exit = true);
if($defaultItem['default'] === '_NOT_SET') $this->triggerError("The param '$key' should pass value. ", __FILE__, __LINE__, $exit = true);
$defaultParams[$key] = $defaultItem['default'];
}
$i ++;
}
@@ -2771,19 +2819,62 @@ class baseRouter
global $config, $dbh, $slaveDBH;
if(!isset($config->installed) or !$config->installed) return;
if(isset($config->db->host)) $this->dbh = $dbh = $this->connectByPDO($config->db);
if(isset($config->slaveDB->host)) $this->slaveDBH = $slaveDBH = $this->connectByPDO($config->slaveDB);
/* Set master db. */
if(isset($config->db->host)) $this->dbh = $dbh = $this->connectByPDO($config->db, 'MASTER');
/* Set slave db. */
if(empty($config->slaveDBList)) return;
$biIndex = 0;
$slaveList = array();
foreach($config->slaveDBList as $index => $db)
{
if(isset($db->type) && $db->type == 'bi')
{
$biIndex = $index;
}
else
{
$slaveList[] = $index;
}
}
$slaveIndex = empty($slaveList) ? $biIndex : $slaveList[array_rand($slaveList)];
$config->biDB = $this->initSlaveDB($biIndex);
$this->slaveDBH = $slaveDBH = $this->connectByPDO($this->initSlaveDB($slaveIndex), 'SLAVE');
}
/**
* Init config of slave db.
*
* @param int $slaveIndex
* @access private
* @return object
*/
private function initSlaveDB($slaveIndex = 0)
{
global $config;
$slaveDB = $config->slaveDBList[$slaveIndex];
$slaveDB->persistant = $config->db->persistant;
$slaveDB->driver = $config->db->driver;
$slaveDB->encoding = $config->db->encoding;
$slaveDB->strictMode = $config->db->strictMode;
$slaveDB->prefix = $config->db->prefix;
return $slaveDB;
}
/**
* 使用PDO连接数据库。
* Connect database by PDO.
*
* @param object $params the database params.
* @param object $params the database params.
* @param string $flag the database flag.
* @access public
* @return object|bool
*/
public function connectByPDO($params)
public function connectByPDO($params, $flag = 'MASTER')
{
if(!isset($params->driver)) self::triggerError('no pdo driver defined, it should be mysql or sqlite', __FILE__, __LINE__, $exit = true);
if(!isset($params->user)) return false;
@@ -2791,7 +2882,7 @@ class baseRouter
{
$dbPassword = helper::decryptPassword($params->password);
$dbh = new dbh($params);
$dbh = new dbh($params, true, $flag);
$dbh->exec("SET NAMES {$params->encoding}");
/*
@@ -2805,6 +2896,7 @@ class baseRouter
$dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$dbh->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_TO_STRING);
if(isset($params->strictMode) and $params->strictMode == false) $dbh->exec("SET @@sql_mode= ''");
if(isset($params->emulatePrepare)) $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, $params->emulatePrepare);
if(isset($params->bufferQuery)) $dbh->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $params->bufferQuery);
@@ -2824,6 +2916,21 @@ class baseRouter
}
}
/**
* Query from database, use master or slave db.
*
* @param string $query
* @access public
* @return mixed
*/
public function dbQuery($query)
{
if(!$this->dbh) return false;
if($this->slaveDBH && strtolower(substr($query, 0, 6)) == 'select') return $this->slaveDBH->query($query);
return $this->dbh->query($query);
}
//-------------------- 错误处理方法(Error methods) ------------------//
/**
@@ -2892,7 +2999,7 @@ class baseRouter
* @access public
* @return void
*/
public function saveError($level, $message, $file, $line)
public function saveError(int $level, string $message, string $file, int $line)
{
if(empty($this->config->debug)) return true;
if(!is_dir($this->logRoot)) return true;
@@ -2904,7 +3011,7 @@ class baseRouter
**/
if(mt_rand(0, 10) == 1)
{
$logDays = isset($this->config->framework->logDays) ? $this->config->framework->logDays : 14;
$logDays = $this->config->framework->logDays ?? 14;
$dayTime = time() - $logDays * 24 * 3600;
foreach(glob($this->getLogRoot() . '*') as $logFile)
{
@@ -2916,7 +3023,7 @@ class baseRouter
* 忽略该错误:Redefining already defined constructor。
* Skip the error: Redefining already defined constructor.
**/
if(strpos($message, 'Redefining') !== false) return true;
if(mb_strpos($message, 'Redefining') !== false) return true;
/*
* 设置错误信息。
@@ -2925,8 +3032,8 @@ class baseRouter
if(preg_match('/[^\x00-\x80]/', $message)) $message = helper::convertEncoding($message, 'gbk');
$errorLog = "\n" . date('H:i:s') . " $message in <strong>$file</strong> on line <strong>$line</strong> ";
$URI = $this->getURI();
$errorLog .= "when visiting <strong>" . (empty($URI) ? '' : htmlspecialchars($URI)) . "</strong>\n";
$uri = $this->getURI();
$errorLog .= "when visiting <strong>" . (empty($uri) ? '' : htmlspecialchars($uri)) . "</strong>\n";
/*
* 为了安全起见,对公网环境隐藏脚本路径。
@@ -2943,18 +3050,29 @@ class baseRouter
if(!is_file($errorFile)) file_put_contents($errorFile, "<?php\n die();\n?" . ">\n");
$fh = fopen($errorFile, 'a');
if($fh) fwrite($fh, strip_tags($errorLog)) and fclose($fh);
if($fh) fwrite($fh, strip_tags(htmlspecialchars_decode($errorLog))) and fclose($fh);
/*
* 如果debug > 1,显示warning, notice级别的错误。
* If the debug > 1, show warning, notice error.
* 如果debug > 1,直接在页面显示非严重错误。
* If the debug > 1, show non-serious errors on page directly.
**/
if($level == E_NOTICE or $level == E_WARNING or $level == E_STRICT or $level == 8192) // 8192: E_DEPRECATED
if(!empty($this->config->debug) && $this->config->debug > 1)
{
if(!empty($this->config->debug) and $this->config->debug > 1)
/* Send non-serious errors to page in zin mode. */
$isZinRequest = isset($this->config->zin) || isset($_SERVER['HTTP_X_ZIN_OPTIONS']);
$isNonSeriousError = $level !== E_ERROR && $level !== E_PARSE && $level !== E_CORE_ERROR && $level !== E_COMPILE_ERROR;
if($isZinRequest && $isNonSeriousError)
{
$this->zinErrors[] = array('file' => $file, 'line' => $line, 'message' => $message, 'level' => $level);
return;
}
/* Show non-serious errors to classic page. */
if($level == E_NOTICE or $level == E_WARNING or $level == E_STRICT or $level == 8192)
{
$cmd = "vim +$line $file";
$size = strlen($cmd);
echo "<pre class='alert alert-danger'>$message: ";
echo "<input type='text' value='$cmd' size='$size' style='border:none; background:none;' onclick='this.select();' /></pre>";
}
@@ -2964,14 +3082,21 @@ class baseRouter
* 如果是严重错误,停止程序。
* If error level is serious, die.
* */
if($level == E_ERROR or $level == E_PARSE or $level == E_CORE_ERROR or $level == E_COMPILE_ERROR or $level == E_USER_ERROR)
if(in_array($level, array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR)))
{
if(empty($this->config->debug)) die();
if(PHP_SAPI == 'cli') die($errorLog);
if(empty($this->config->debug)) helper::end();
$htmlError = "<html><head><meta http-equiv='Content-Type' content='text/html; charset=utf-8' /></head>";
$htmlError .= "<body>" . nl2br($errorLog) . "</body></html>";
die($htmlError);
if(PHP_SAPI == 'cli')
{
echo $errorLog;
}
else
{
$htmlError = "<html><head><meta http-equiv='Content-Type' content='text/html; charset=utf-8' /></head>";
$htmlError .= "<body>" . nl2br($errorLog) . "</body></html>";
echo $htmlError;
helper::end();
}
}
}
@@ -3042,7 +3167,7 @@ class baseRouter
$commonExtFiles = array();
$siteExtFiles = array();
$extPath = $this->getModuleExtPath($appName, $moduleName, $type);
$extPath = $this->getModuleExtPath($moduleName, $type);
if($this->config->framework->extensionLevel >= 1)
{
$clientLang = $type == 'lang' ? $this->clientLang : '';
+7 -6
View File
@@ -222,7 +222,7 @@ class control extends baseControl
if(isset($this->$extensionClass)) return $this->$extensionClass;
/* 设置扩展的名字和相应的文件。Set extenson name and extension file. */
$moduleExtPath = $this->app->getModuleExtPath($this->appName, $moduleName, $type);
$moduleExtPath = $this->app->getModuleExtPath($moduleName, $type);
if(!empty($moduleExtPath['site'])) $extensionFile = $moduleExtPath['site'] . 'class/' . $extensionName . '.class.php';
if(!isset($extensionFile) or !file_exists($extensionFile)) $extensionFile = $moduleExtPath['custom'] . 'class/' . $extensionName . '.class.php';
if(!isset($extensionFile) or !file_exists($extensionFile)) $extensionFile = $moduleExtPath['saas'] . 'class/' . $extensionName . '.class.php';
@@ -249,19 +249,20 @@ class control extends baseControl
*
* @param string $moduleName module name
* @param string $methodName method name
* @param string $viewDir
* @access public
* @return string the view file
*/
public function setViewFile($moduleName, $methodName)
public function setViewFile(string $moduleName, string $methodName, string $viewDir = 'view')
{
$moduleName = strtolower(trim($moduleName));
$methodName = strtolower(trim($methodName));
$modulePath = $this->app->getModulePath($this->appName, $moduleName);
$viewExtPath = $this->app->getModuleExtPath($this->appName, $moduleName, 'view');
$viewExtPath = $this->app->getModuleExtPath($moduleName, $viewDir);
$viewType = ($this->viewType == 'mhtml' or $this->viewType == 'xhtml') ? 'html' : $this->viewType;
$mainViewFile = $modulePath . 'view' . DS . $this->devicePrefix . $methodName . '.' . $viewType . '.php';
$mainViewFile = $modulePath . $viewDir . DS . $this->devicePrefix . $methodName . '.' . $viewType . '.php';
/* If the main view file doesn't exist, set the device prefix to empty and reset the main view file. */
if(!file_exists($mainViewFile) and $this->app->clientDevice != 'mobile')
@@ -309,8 +310,8 @@ class control extends baseControl
$viewFile = $commonExtViewFile;
}
if(!is_file($viewFile)) $viewFile = dirname(dirname($viewExtPath['common'])) . DS . 'view' . DS . $this->devicePrefix . $methodName . ".{$viewType}.php";
if(!is_file($viewFile)) die(js::error($this->lang->notPage) . js::locate('back'));
if(!is_file($viewFile)) $viewFile = dirname((string) $viewExtPath['common'], 2) . DS . 'view' . DS . $this->devicePrefix . $methodName . ".{$viewType}.php";
if(!is_file($viewFile)) helper::end(js::error($this->lang->notPage) . js::locate('back'));
/* Get ext hook files. */
$commonExtHookFiles = glob($viewExtPath['common'] . $this->devicePrefix . $methodName . ".*.{$viewType}.hook.php");
+314
View File
@@ -237,6 +237,23 @@ class helper extends baseHelper
);
}
/**
* Process traffic.
*
* @param float $traffic
* @param int $precision
* @access public
* @return float
*/
public static function formatKB($traffic, $precision = 2)
{
if(!$traffic) return 0;
$base = log($traffic, 1024);
$suffixes = array('', 'KB', 'MB', 'GB', 'TB');
return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
}
/**
* Trim version to xuanxuan version format.
*
@@ -359,6 +376,39 @@ class helper extends baseHelper
{
return !defined('USE_INTRANET') ? false : USE_INTRANET;
}
/**
* 转换类型。
* Convert the type.
*
* @param mixed $value
* @param string $type
* @static
* @access public
* @return array|bool|float|int|object|string
*/
public static function convertType($value, $type)
{
switch($type)
{
case 'int':
return (int)$value;
case 'float':
return (float)$value;
case 'bool':
return (bool)$value;
case 'array':
return (array)$value;
case 'object':
return (object)$value;
case 'datetime':
case 'date':
return $value ? (string)$value : null;
case 'string':
default:
return (string)$value;
}
}
}
/**
@@ -373,6 +423,18 @@ function isonlybody()
return helper::inOnlyBodyMode();
}
/**
* 检查页面是否是弹窗中。
* Check page is modal.
*
* @access public
* @return bool
*/
function isInModal(): bool
{
return helper::isAjaxRequest('modal');
}
/**
* Format time.
*
@@ -383,6 +445,7 @@ function isonlybody()
*/
function formatTime($time, $format = '')
{
if(empty($time)) return '';
$time = str_replace('0000-00-00', '', $time);
$time = str_replace('00:00:00', '', $time);
if(trim($time) == '') return '';
@@ -406,3 +469,254 @@ function autoloader($class)
}
spl_autoload_register('autoloader');
/**
* Init page title based on the module name and the method name.
*
* @access public
* @return string
*/
function initPageTitle(): string
{
global $app, $lang;
$module = $app->rawModule;
$method = $app->rawMethod;
if(empty($lang->$module)) $app->loadLang($module);
if(!empty($lang->$module->{$method . 'Action'})) return $lang->$module->{$method . 'Action'};
if(!empty($lang->$module->$method)) return $lang->$module->$method;
return zget($lang, $method);
}
/**
* Init page entity based on configuration of objectNameFields.
*
* @param object $object
* @access public
* @return array
*/
function initPageEntity(object $object): array
{
if(empty($object)) return array();
global $app, $config;
$app->loadModuleConfig('action');
$module = $app->getModuleName();
$idField = isset($config->action->objectIdFields[$module]) ? $config->action->objectIdFields[$module] : 'id';
$titleField = isset($config->action->objectNameFields[$module]) ? $config->action->objectNameFields[$module] : 'title';
return array(zget($object, $titleField, ''), zget($object, $idField, 0));
}
/**
* Init table data of zin.
*
* @param array $items
* @param array $fieldList
* @param object $model
* @access public
* @return array
*/
function initTableData(array $items, array &$fieldList, object $model = null): array
{
$items = setParent($items);
if(empty($fieldList['actions'])) return $items;
foreach($fieldList['actions']['menu'] as $actionMenu)
{
if(is_array($actionMenu))
{
foreach($actionMenu as $actionMenuKey => $actionName)
{
if($actionMenuKey == 'other')
{
foreach($actionName as $otherActionName) initTableActions($fieldList, $otherActionName);
}
else
{
initTableActions($fieldList, $actionName);
}
}
}
else
{
initTableActions($fieldList, $actionMenu);
}
}
global $app;
if(empty($model))
{
$module = $app->getModuleName();
$model = $app->control->loadModel($module);
}
$maxActionCount = 0;
foreach($items as $item)
{
$item->actions = array();
foreach($fieldList['actions']['menu'] as $actionKey => $actionMenu)
{
if(isset($actionMenu['other']))
{
$currentActionMenu = $actionMenu[0];
initItemActions($item, $currentActionMenu, $fieldList['actions']['list'], $model);
$otherActionMenus = $actionMenu['other'];
$otherAction = '';
foreach($otherActionMenus as $otherActionMenu)
{
$otherActions = explode('|', $otherActionMenu);
foreach($otherActions as $otherActionName)
{
if(in_array($otherActionName, array_column($item->actions, 'name'))) continue;
if(method_exists($model, 'isClickable') && !$model->isClickable($item, $otherActionName)) $otherAction .= '-';
$otherAction .= $otherActionName . ',';
}
}
$item->actions[] = 'other:' . $otherAction;
}
elseif($actionKey === 'more')
{
$moreAction = '';
foreach($actionMenu as $moreActionName)
{
if(method_exists($model, 'isClickable') && !$model->isClickable($item, $moreActionName)) $moreAction .= '-';
$moreAction .= $moreActionName . ',';
}
$item->actions[] = 'more:' . $moreAction;
}
elseif(is_array($actionMenu)) // Two or more grups.
{
/*
* Menu可能会有多套,如果只有一套可以直接用一维数组。
* There are maybe two or more groups of action menus.
*/
$item->actions = array();
$isClickable = false;
foreach($actionMenu as $actionName) $isClickable |= initItemActions($item, $actionName, $fieldList['actions']['list'], $model);
if($isClickable) break; // If the action is clickable, use this group.
}
else // Only one group of action menus.
{
initItemActions($item, $actionMenu, $fieldList['actions']['list'], $model);
}
}
if(count($item->actions) > $maxActionCount) $maxActionCount = count($item->actions);
}
if(isset($fieldList['actions'])) $fieldList['actions']['minWidth'] = $maxActionCount * 24 + 24;
if($fieldList['actions']['minWidth'] < 48) $fieldList['actions']['minWidth'] = 48;
return array_values($items);
}
/**
* Set the parent property of the data.
*
* @param array $items
* @access public
* @return array
*/
function setParent(array $items)
{
foreach($items as $item)
{
/* Set parent attribute. */
$item->isParent = false;
if(isset($item->parent) && $item->parent == -1)
{
/* When the parent is -1, the hierarchical structure is displayed incorrectly. */
$item->parent = 0;
$item->isParent = true;
}
if(!empty($item->parent) && isset($items[$item->parent])) $items[$item->parent]->isParent = true;
}
return $items;
}
/**
* Init column actions of a table.
*
* @param array $fieldList
* @param string $actionMenu
* @access public
* @return void
*/
function initTableActions(array &$fieldList, string $actionMenu): void
{
$actions = explode('|', $actionMenu);
foreach($actions as $action)
{
if(!isset($fieldList['actions']['list'][$action])) continue;
$actionConfig = $fieldList['actions']['list'][$action];
$actionConfig['text'] = '';
if(!empty($actionConfig['url']['module']) && !empty($actionConfig['url']['method']))
{
$module = $actionConfig['url']['module'];
$method = $actionConfig['url']['method'];
$params = !empty($actionConfig['url']['params']) ? $actionConfig['url']['params'] : array();
$actionConfig['url'] = helper::createLink($module, $method, $params);
}
$fieldList['actions']['actionsMap'][$action] = $actionConfig;
}
}
/**
* Init row actions of a item.
*
* @param object $item
* @param string $actionMenu
* @param array $actionList
* @param object $model
* @access public
* @return bool
*/
function initItemActions(object &$item, string $actionMenu, array $actionList, object $model): bool
{
global $app;
$module = $app->getModuleName();
$method = '';
$isClickable = false;
$actions = explode('|', $actionMenu);
foreach($actions as $action)
{
if(!isset($actionList[$action])) continue;
$actionConfig = $actionList[$action];
if(!empty($actionConfig['url']['module']) && $module != $actionConfig['url']['module'])
{
$module = $actionConfig['url']['module'];
$model = $app->control->loadModel($module);
}
$method = $action;
if(!empty($actionConfig['url']['method']) && $method != $actionConfig['url']['method']) $method = $actionConfig['url']['method'];
if(!method_exists($model, 'isClickable') || $model->isClickable($item, $method))
{
$isClickable = true;
break;
}
}
if(method_exists($model, 'isDisplay') && !$model->isDisplay($item, $method)) return $isClickable;
if(!$method || !common::hasPriv($module, $method)) return $isClickable;
$item->actions[] = array('name' => $action, 'disabled' => !$isClickable);
return $isClickable;
}
+25 -1
View File
@@ -122,7 +122,7 @@ class model extends baseModel
if($action->extensionType == 'override') return $this->loadModel('flow')->buildActionMenu($moduleName, $action, $data, $type);
$conditions = json_decode($action->conditions);
$conditions = empty($action->conditions) ? array() : json_decode($action->conditions);
if($conditions and $action->extensionType == 'extend')
{
if($icon != 'copy' and $methodName != 'create') $title = $action->name;
@@ -133,6 +133,10 @@ class model extends baseModel
if(method_exists($this, 'isClickable')) $enabled = $this->isClickable($data, $method, $module);
}
}
elseif(strpos($misc, 'disabled') !== false)
{
$enabled = false;
}
else
{
if(method_exists($this, 'isClickable')) $enabled = $this->isClickable($data, $method, $module);
@@ -395,4 +399,24 @@ class model extends baseModel
$app->triggerError("the module {$moduleName} has no {$method} method", __FILE__, __LINE__, $exit = true);
}
/**
* Load dao of bi.
*
* @access public
* @return void
*/
public function loadBIDAO()
{
global $config, $biDAO;
if(is_object($biDAO)) return $this->dao = $biDAO;
if(!isset($config->biDB)) return;
$driver = $config->db->driver;
$biDAO = new $driver();
$biDAO->slaveDBH = $this->app->connectByPDO($config->biDB, 'BI');
$this->dao = $biDAO;
}
}
+15 -12
View File
@@ -78,7 +78,7 @@ class router extends baseRouter
{
if($this->dbh)
{
$langs = $this->dbh->query('SELECT `value` FROM ' . TABLE_CONFIG . " WHERE `owner`='system' AND `module`='common' AND `section`='global' AND `key`='langs'")->fetch();
$langs = $this->dbQuery('SELECT `value` FROM ' . TABLE_CONFIG . " WHERE `owner`='system' AND `module`='common' AND `section`='global' AND `key`='langs'")->fetch();
$langs = empty($langs) ? array() : json_decode($langs->value, true);
foreach($langs as $langKey => $langData) $this->config->langs[$langKey] = $langData['name'];
}
@@ -112,7 +112,7 @@ class router extends baseRouter
$customMenus = array();
try
{
$customMenus = $this->dbh->query('SELECT * FROM' . TABLE_LANG . "WHERE `module`='common' AND `section`='mainNav' AND `lang`='{$this->clientLang}' AND `vision`='{$this->config->vision}'")->fetchAll();
$customMenus = $this->dbQuery('SELECT * FROM' . TABLE_LANG . "WHERE `module`='common' AND `section`='mainNav' AND `lang`='{$this->clientLang}' AND `vision`='{$this->config->vision}'")->fetchAll();
}
catch(PDOException $exception){}
@@ -206,7 +206,7 @@ class router extends baseRouter
try
{
$commonSettings = $this->dbh->query('SELECT `section`, `key`, `value` FROM' . TABLE_CONFIG . "WHERE `owner`='system' AND (`module`='custom' or `module`='common') and `key` in ('sprintConcept', 'hourPoint', 'URSR', 'mode', 'URAndSR', 'scoreStatus', 'disabledFeatures', 'closedFeatures')")->fetchAll();
$commonSettings = $this->dbQuery('SELECT `section`, `key`, `value` FROM' . TABLE_CONFIG . "WHERE `owner`='system' AND (`module`='custom' or `module`='common') and `key` in ('sprintConcept', 'hourPoint', 'URSR', 'mode', 'URAndSR', 'scoreStatus', 'disabledFeatures', 'closedFeatures')")->fetchAll();
}
catch (PDOException $exception)
{
@@ -274,7 +274,7 @@ class router extends baseRouter
{
$sql = new sql();
$account = $sql->quote($account);
$userSetting = $this->dbh->query('SELECT `key`, `value` FROM ' . TABLE_CONFIG . " WHERE `owner`= $account AND `module`='common' and `key` in ('programLink', 'productLink', 'projectLink', 'executionLink', 'URSR')")->fetchAll();
$userSetting = $this->dbQuery('SELECT `key`, `value` FROM ' . TABLE_CONFIG . " WHERE `owner`= $account AND `module`='common' and `key` in ('programLink', 'productLink', 'projectLink', 'executionLink', 'URSR')")->fetchAll();
}
foreach($userSetting as $setting)
@@ -290,7 +290,7 @@ class router extends baseRouter
$lang->SRCommon = '';
if($this->dbh and !empty($this->config->db->name))
{
$productProject = $this->dbh->query('SELECT `value` FROM ' . TABLE_CONFIG . "WHERE `owner`='system' AND `module`='custom' AND `key`='productProject'")->fetch();
$productProject = $this->dbQuery('SELECT `value` FROM ' . TABLE_CONFIG . "WHERE `owner`='system' AND `module`='custom' AND `key`='productProject'")->fetch();
if($productProject)
{
$productProject = $productProject->value;
@@ -301,8 +301,8 @@ class router extends baseRouter
{
/* Get story concept in project and product. */
$clientLang = $this->clientLang == 'zh-tw' ? 'zh-cn' : $this->clientLang;
$URSRList = $this->dbh->query('SELECT `key`, `value` FROM' . TABLE_LANG . "WHERE `module` = 'custom' and `section` = 'URSRList' and `lang` = '{$clientLang}'")->fetchAll();
if(empty($URSRList)) $URSRList = $this->dbh->query('SELECT `key`, `value` FROM' . TABLE_LANG . "WHERE module = 'custom' and `section` = 'URSRList' and `key` = '{$config->URSR}'")->fetchAll();
$URSRList = $this->dbQuery('SELECT `key`, `value` FROM' . TABLE_LANG . "WHERE `module` = 'custom' and `section` = 'URSRList' and `lang` = '{$clientLang}'")->fetchAll();
if(empty($URSRList)) $URSRList = $this->dbQuery('SELECT `key`, `value` FROM' . TABLE_LANG . "WHERE module = 'custom' and `section` = 'URSRList' and `key` = '{$config->URSR}'")->fetchAll();
/* Get UR pairs and SR pairs. */
$URPairs = array();
@@ -323,7 +323,7 @@ class router extends baseRouter
$customMenus = array();
try
{
$customMenus = $this->dbh->query('SELECT * FROM' . TABLE_LANG . "WHERE `module`='common' AND `lang`='{$this->clientLang}' AND `section`='' AND `vision`='{$config->vision}'")->fetchAll();
$customMenus = $this->dbQuery('SELECT * FROM' . TABLE_LANG . "WHERE `module`='common' AND `lang`='{$this->clientLang}' AND `section`='' AND `vision`='{$config->vision}'")->fetchAll();
}
catch(PDOException $exception){}
foreach($customMenus as $menu) if(isset($lang->{$menu->key})) $lang->{$menu->key} = $menu->value;
@@ -375,8 +375,11 @@ class router extends baseRouter
/* 先获得模块的主配置文件。Get the main config file for current module first. */
$mainConfigFile = $this->getModulePath($appName, $moduleName) . 'config.php';
/* 获取 config 目录的配置文件。Get config files from config directory. */
$configDirFiles = helper::ls($this->getModulePath($appName, $moduleName) . DS . 'config', '.php');
/* 查找扩展配置文件。Get extension config files. */
if($config->framework->extensionLevel > 0) $extConfigPath = $this->getModuleExtPath($appName, $moduleName, 'config');
if($config->framework->extensionLevel > 0) $extConfigPath = $this->getModuleExtPath($moduleName, 'config');
if($config->framework->extensionLevel >= 1)
{
if(!empty($extConfigPath['common'])) $commonExtConfigFiles = helper::ls($extConfigPath['common'], '.php');
@@ -386,7 +389,7 @@ class router extends baseRouter
if(!empty($extConfigPath['custom'])) $commonExtConfigFiles = array_merge($commonExtConfigFiles, helper::ls($extConfigPath['custom'], '.php'));
}
if($config->framework->extensionLevel == 2 and !empty($extConfigPath['site'])) $siteExtConfigFiles = helper::ls($extConfigPath['site'], '.php');
$extConfigFiles = array_merge($commonExtConfigFiles, $siteExtConfigFiles);
$extConfigFiles = array_merge($commonExtConfigFiles, $configDirFiles, $siteExtConfigFiles);
/* 将主配置文件和扩展配置文件合并在一起。Put the main config file and extension config files together. */
$configFiles = array_merge(array($mainConfigFile), $extConfigFiles);
@@ -481,7 +484,7 @@ class router extends baseRouter
if($this->config->edition == 'open' or defined('IN_INSTALL') or defined('IN_UPGRADE')) return parent::setControlFile($exitIfNone);
/* Check if the requested module is defined in workflow. */
$flow = $this->dbh->query("SELECT * FROM " . TABLE_WORKFLOW . " WHERE `module` = '$this->moduleName'")->fetch();
$flow = $this->dbQuery("SELECT * FROM " . TABLE_WORKFLOW . " WHERE `module` = '$this->moduleName'")->fetch();
if(!$flow) return parent::setControlFile($exitIfNone);
if($flow->status != 'normal') die("<html><head><meta charset='utf-8'></head><body>{$this->lang->flowNotRelease}</body></html>");
@@ -509,7 +512,7 @@ class router extends baseRouter
$actionQuery = "SELECT * FROM " . TABLE_WORKFLOWACTION . " WHERE `module` = '$this->moduleName' AND `action` = '$this->methodName'";
if(isset($this->app->user) && $this->app->user->account != 'guest') $actionQuery .= " AND `vision` = '{$this->config->vision}'";
$action = $this->dbh->query($actionQuery)->fetch();
$action = $this->dbQuery($actionQuery)->fetch();
if(zget($action, 'extensionType') == 'override')
{
$this->rawModule = $this->moduleName;
+16 -10
View File
@@ -406,7 +406,6 @@ class baseDAO
if($orderPOS) $subLength = $orderPOS;
if($groupPOS) $subLength = $groupPOS;
$sql = substr($sql, 0, $subLength);
self::$querys[] = $sql;
/*
* 获取记录数。
@@ -414,7 +413,8 @@ class baseDAO
**/
try
{
$row = $this->dbh->rawQuery($sql)->fetch(PDO::FETCH_OBJ);
$dbh = $this->slaveDBH ? $this->slaveDBH : $this->dbh;
$row = $dbh->rawQuery($sql)->fetch(PDO::FETCH_OBJ);
}
catch (PDOException $e)
{
@@ -568,7 +568,7 @@ class baseDAO
*/
public function get()
{
return $this->processKeywords($this->processSQL(false));
return self::processKeywords($this->processSQL());
}
/**
@@ -605,7 +605,7 @@ class baseDAO
* @access public
* @return string the sql string after process.
*/
public function processSQL($record = true)
public function processSQL()
{
$sql = $this->sqlobj->get();
@@ -682,7 +682,6 @@ class baseDAO
}
}
if($record) self::$querys[] = $this->processKeywords($sql);
return $sql;
}
@@ -694,7 +693,7 @@ class baseDAO
* @access public
* @return string the sql string.
*/
public function processKeywords($sql)
static public function processKeywords($sql)
{
return str_replace(array(DAO::WHERE, DAO::GROUPBY, DAO::HAVING, DAO::ORDERBY, DAO::LIMIT), array('WHERE', 'GROUP BY', 'HAVING', 'ORDER BY', 'LIMIT'), $sql);
}
@@ -748,12 +747,15 @@ class baseDAO
$method = $this->method;
$this->reset();
if($this->slaveDBH and $method == 'select')
if($this->slaveDBH and in_array($method, array('select', 'desc')))
{
return $this->slaveDBH->rawQuery($sql);
}
else
{
/* Force to query from master db, if db has been changed. */
$this->slaveDBH = false;
return $this->dbh->rawQuery($sql);
}
}
@@ -847,6 +849,10 @@ class baseDAO
{
if($this->table) unset(dao::$cache[$this->table]);
$this->reset();
/* Force to query from master db, if db has been changed. */
$this->slaveDBH = false;
return $this->dbh->exec($sql);
}
catch (PDOException $e)
@@ -1925,7 +1931,7 @@ class baseSQL
}
else
{
$condition = ctype_alnum($arg1) ? '`' . $arg1 . '`' : $arg1;
$condition = (is_string($arg1) && ctype_alnum($arg1)) ? '`' . $arg1 . '`' : $arg1;
}
if(!$this->inMark) $this->sql .= ' ' . DAO::WHERE ." $condition ";
@@ -1944,7 +1950,7 @@ class baseSQL
public function andWhere($condition, $addMark = false)
{
if($this->inCondition and !$this->conditionIsTrue) return $this;
if(ctype_alnum($condition)) $condition = '`' . $condition . '`';
if(is_string($condition) && ctype_alnum($condition)) $condition = '`' . $condition . '`';
$mark = $addMark ? '(' : '';
$this->sql .= " AND {$mark} $condition ";
@@ -1962,7 +1968,7 @@ class baseSQL
public function orWhere($condition)
{
if($this->inCondition and !$this->conditionIsTrue) return $this;
if(ctype_alnum($condition)) $condition = '`' . $condition . '`';
if(is_string($condition) && ctype_alnum($condition)) $condition = '`' . $condition . '`';
$this->sql .= " OR $condition ";
return $this;
+1 -1
View File
@@ -661,7 +661,7 @@ class baseValidater
global $config;
if(empty($config->framework->filterXSS)) return $var;
if(stripos($var, '&lt;script') !== false or stripos($var, '<script') !== false)
if(stripos($var, '&lt;script') !== false || stripos($var, '<script') !== false)
{
$var = (string)$var;
$evils = array('appendchild(', 'createElement(', 'xss.re', 'onfocus', 'onclick', 'innerHTML', 'replaceChild(', 'html(', 'append(', 'appendTo(', 'prepend(', 'prependTo(', 'after(', 'insertBefore', 'before(', 'replaceWith(', 'alert(', 'confirm(');
+33 -23
View File
@@ -1160,18 +1160,8 @@ EOT;
return $js;
}
/**
* 导出$config到js,因为js的createLink()方法需要获取config信息。
* Export the config vars for createLink() js version.
*
* @static
* @access public
* @return void
*/
static public function exportConfigVars()
static function getJSConfigVars()
{
if(!function_exists('json_encode')) return false;
global $app, $config, $lang;
$defaultViewType = $app->getViewType();
$themeRoot = $app->getWebRoot() . 'theme/';
@@ -1180,7 +1170,7 @@ EOT;
$clientLang = $app->getClientLang();
$runMode = defined('RUN_MODE') ? RUN_MODE : '';
$requiredFields = '';
if(isset($config->$moduleName->$methodName->requiredFields)) $requiredFields = str_replace(' ', '', $config->$moduleName->$methodName->requiredFields);
if(isset($config->$moduleName->$methodName->requiredFields)) $requiredFields = str_replace(' ', '', (string) $config->$moduleName->$methodName->requiredFields);
$jsConfig = new stdclass();
$jsConfig->webRoot = $config->webRoot;
@@ -1199,21 +1189,41 @@ EOT;
$jsConfig->clientLang = $clientLang;
$jsConfig->requiredFields = $requiredFields;
$jsConfig->router = $app->server->SCRIPT_NAME;
$jsConfig->save = isset($lang->save) ? $lang->save : '';
$jsConfig->save = $lang->save ?? '';
$jsConfig->runMode = $runMode;
$jsConfig->timeout = isset($config->timeout) ? $config->timeout : '';
$jsConfig->pingInterval = isset($config->pingInterval) ? $config->pingInterval : '';
$jsConfig->timeout = $config->timeout ?? '';
$jsConfig->pingInterval = $config->pingInterval ?? '';
$jsConfig->onlybody = zget($_GET, 'onlybody', 'no');
$jsConfig->version = $config->version;
$jsConfig->tabSession = $config->tabSession;
if($config->tabSession and helper::isWithTID()) $jsConfig->tid = zget($_GET, 'tid', '');
return $jsConfig;
}
/**
* 导出$config到js,因为js的createLink()方法需要获取config信息。
* Export the config vars for createLink() js version.
*
* @static
* @access public
* @return void
*/
static public function exportConfigVars()
{
if(!function_exists('json_encode')) return false;
global $lang;
$jsConfig = static::getJSConfigVars();
$jsLang = new stdclass();
$jsLang->submitting = isset($lang->loading) ? $lang->loading : '';
$jsLang->submitting = $lang->loading ?? '';
$jsLang->save = $jsConfig->save;
$jsLang->expand = isset($lang->expand) ? $lang->expand : '';
$jsLang->timeout = isset($lang->timeout) ? $lang->timeout : '';
$jsLang->confirmDraft = isset($lang->confirmDraft) ? $lang->confirmDraft : '';
$jsLang->resume = isset($lang->resume) ? $lang->resume : '';
$jsLang->expand = $lang->expand ?? '';
$jsLang->timeout = $lang->timeout ?? '';
$jsLang->confirmDraft = $lang->confirmDraft ?? '';
$jsLang->resume = $lang->resume ?? '';
$jsLang->program = zget($lang->program, 'common', '');
$jsLang->project = zget($lang->project, 'common', '');
$jsLang->product = zget($lang->product, 'common', '');
@@ -1300,12 +1310,12 @@ EOT;
elseif(is_bool($value))
{
$value = $value ? 'true' : 'false';
$js .= "{$prefix}{$key} = $value;";
$js .= "{$prefix}{$key} = $value;";
}
else
{
$value = addslashes($value);
$js .= "{$prefix}{$key} = '{$value}';";
$value = empty($value) ? '' : addslashes($value);
$js .= "{$prefix}{$key} = '{$value}';";
}
$js .= static::end($newline = false);
echo $js;
+23 -5
View File
@@ -19,6 +19,14 @@
*/
class dbh
{
/**
* Flag for database.
*
* @var string MASTER|SLAVE|BI
* @access private
*/
private $flag;
/**
* PDO.
*
@@ -40,10 +48,11 @@ class dbh
*
* @param object $config
* @param bool $setSchema
* @param string $flag
* @access public
* @return void
*/
public function __construct($config, $setSchema = true)
public function __construct($config, $setSchema = true, $flag = 'MASTER')
{
$dsn = "{$config->driver}:host={$config->host};port={$config->port}";
if($setSchema) $dsn .= ";dbname={$config->name}";
@@ -64,6 +73,7 @@ class dbh
$this->pdo = $pdo;
$this->config = $config;
$this->flag = $flag;
}
/**
@@ -78,6 +88,7 @@ class dbh
$sql = $this->formatSQL($sql);
if(!$sql) return true;
if(class_exists('dao')) dao::$querys[] = "[$this->flag] " . dao::processKeywords($sql);
return $this->pdo->exec($sql);
}
@@ -91,7 +102,11 @@ class dbh
public function query($sql)
{
$sql = $this->formatSQL($sql);
return $this->pdo->query($sql);
if(class_exists('dao')) dao::$querys[] = "[$this->flag] " . dao::processKeywords($sql);
$result = $this->pdo->query($sql);
return $result;
}
/**
@@ -103,7 +118,11 @@ class dbh
*/
public function rawQuery($sql)
{
return $this->pdo->query($sql);
if(class_exists('dao')) dao::$querys[] = "[$this->flag] " . dao::processKeywords($sql);
$result = $this->pdo->query($sql);
return $result;
}
/**
@@ -317,7 +336,7 @@ class dbh
}
elseif(stripos($sql, 'CREATE UNIQUE INDEX') === 0 || stripos($sql, 'CREATE INDEX') === 0)
{
preg_match('/ON\ +`([0-9a-zA-Z_]+)`/', $sql, $matches);
preg_match('/ON\s+[^.`\s]+\.`([^\s`]+)`/', $sql, $matches);
$tableName = str_replace($this->config->prefix, '', $matches);
$sql = preg_replace('/INDEX\ +\`/', 'INDEX `' . strtolower($tableName[1]) . '_', $sql);
@@ -668,7 +687,6 @@ class dbh
return $this->pdo->commit();
}
/**
* Prepares a statement for execution and returns a statement object.
*
+322
View File
@@ -0,0 +1,322 @@
<?php
declare(strict_types=1);
/**
* The form class file of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author Lu Fei <lufei@easycorp.ltd>
* @package form
* @link https://www.zentao.net
*/
helper::import(dirname(dirname(__FILE__)) . '/filter/filter.class.php');
class form extends fixer
{
/**
* 批量处理的数据。
* The data to be fixed.
*
* @var object
* @access public
*/
public $dataList;
/**
* 类型。single|batch
* Type. single|batch
*
* @var string
*/
protected $formType = 'single';
/**
* 原始配置。
* The raw cofig.
*
* @var array
*/
protected $rawconfig;
/**
* 错误信息列表。
* Error list.
*
* @var array
*/
public $errors;
/**
* 构造方法。
* The construct function.
*
* @return void
*/
public function __construct()
{
$this->rawdata = (object)$_POST;
$this->data = (object)array();
$this->errors = array();
}
/**
* 获取表单数据。
* Get the form data.
*
* @param array|null $configObject
* @return form
*/
public static function data(array $configObject = null): form
{
global $app, $config;
if($configObject === null) $configObject = $config->{$app->moduleName}->form->{$app->methodName};
return (new form)->config($configObject);
}
/**
* 获取批量表单数据。
* Get the batch form data.
*
* @param array|null $configObject
* @return form
*/
public static function batchData(array $configObject = null): form
{
global $app, $config;
if($configObject === null) $configObject = $config->{$app->moduleName}->form->{$app->methodName};
return (new form)->config($configObject, 'batch');
}
/**
* 设置表单配置项。
* Set form configuration.
*
* @param array $config
* @param string $type single|batch
* @return $this
* @throws EndResponseException
*/
public function config(array $config, string $type = 'single')
{
$this->rawconfig = $config;
$this->formType = $type;
if($type == 'single')
{
foreach($this->rawconfig as $field => $fieldConfig)
{
if(isset($fieldConfig['control']) && in_array($fieldConfig['control'], array('textarea', 'richtext'))) $this->skipSpecial($field);
$this->convertField($field, $fieldConfig);
}
}
else
{
$this->batchConvertField($config);
}
if(!empty($this->errors))
{
$response = array('result' => 'fail', 'message' => $this->errors);
helper::end(json_encode($response));
}
return $this;
}
/**
* 批量转换字段类型。
* Batch convert the field type.
*
* @param array $config
* @return void
*/
public function batchConvertField(array $fieldConfigs)
{
global $app;
$rowDataList = array();
$baseField = '';
foreach($fieldConfigs as $field => $config)
{
/* 在第一行提示类型未定义。 Display type error in the first row. */
if(!isset($config['type']))
{
if(empty($this->errors)) $this->errors[1] = array();
if(!isset($this->errors[1][$field])) $this->errors[1][$field] = array();
$this->errors[1][$field][] = "Field '{$field}' need defined type";
}
/* 以该字段为标准,判断某一行是否要构造数据。 If the value of the field in a row is empty, skip that row. */
if(!empty($config['base'])) $baseField = $field;
if(isset($config['control']) && in_array($config['control'], array('textarea', 'richtext'))) $this->skipSpecial($field);
}
/* 在第一行提示标准字段不能为空。 Display the field error in the first row. */
if(!isset($this->rawdata->$baseField))
{
if(empty($this->errors)) $this->errors[1] = array();
if(!isset($this->errors[1][$field])) $this->errors[1][$field] = array();
$this->errors[1][$field][] = "Field '{$field}' not empty";
}
/* 构造批量表单数据。Construct batch form data. */
foreach($this->rawdata->$baseField as $rowIndex => $value)
{
if(empty($value)) continue;
$rowData = new stdclass();
foreach($fieldConfigs as $field => $config)
{
$defaultValue = zget($config, 'default', '');
$rowData->$field = isset($this->rawdata->$field) ? zget($this->rawdata->$field, $rowIndex, $defaultValue) : $defaultValue;
$rowData->$field = helper::convertType($rowData->$field, $config['type']);
if(isset($config['filter'])) $rowData->$field = $this->filter($rowData->$field, $config['filter']);
/* 检查必填字段。Check required fields. */
if(isset($config['required']) && $config['required'] && empty($rowData->$field))
{
$fieldName = isset($app->lang->{$app->rawModule}->$field) ? $app->lang->{$app->rawModule}->$field : $field;
if(!isset($this->errors["{$field}[{$rowIndex}]"])) $this->errors["{$field}[{$rowIndex}]"] = array();
$this->errors["{$field}[{$rowIndex}]"] = sprintf($app->lang->error->notempty, $fieldName);
}
}
$rowDataList[$rowIndex] = $rowData;
}
$this->dataList = $rowDataList;
}
/**
* 获取$_POST的数据。
* Get the data of $_POST.
*
* @param bool $isRaw 是否获取原始数据。Whether to get the raw data.
* @return object
*/
public function getAll(bool $isRaw = false): object
{
return $isRaw ? $this->rawdata : $this->data;
}
/**
* 转换字段类型。
* Convert the field type.
*
* @param string $field
* @param array $config
* @return void
*/
public function convertField(string $field, array $config)
{
global $app;
if(!isset($config['type']))
{
if(!isset($this->errors[$field])) $this->errors[$field] = array();
$this->errors[$field][] = "Field '{$field}' need defined type";
}
$requireValue = isset($config['required']) && $config['required'] && !isset($config['default']);
if($requireValue && (!isset($this->rawdata->$field) || $this->rawdata->$field === ''))
{
if(!isset($this->errors[$field])) $this->errors[$field] = array();
$fieldName = isset($app->lang->{$app->rawModule}->$field) ? $app->lang->{$app->rawModule}->$field : $field;
$this->errors[$field][] = sprintf($app->lang->error->notempty, $fieldName);
}
if(isset($this->rawdata->$field))
{
$data = $this->rawdata->$field;
}
if(array_key_exists('default', $config) && !isset($this->rawdata->$field))
{
$data = $config['default'];
}
$data = helper::convertType($data, $config['type']);
if(isset($config['filter']))
{
$data = $this->filter($data, $config['filter']);
}
$this->data->$field = $data;
}
/**
* Special array.
*
* @param mixed $data
* @access public
* @return mixed
*/
public function specialArray($data): mixed
{
if(!is_array($data))
{
if(is_string($data)) return htmlspecialchars($data, ENT_QUOTES);
return $data;
}
foreach($data as &$value) $value = $this->specialArray($value);
return $data;
}
/**
* 过滤表单字段数据。
* Filter the form field data.
*
* @param mixed $value
* @param mixed $filter
* @return string
*/
protected function filter($value, $filter)
{
switch($filter)
{
case 'trim':
return trim($value);
case 'join':
return implode(',', $value);
default:
return $value;
}
}
/**
* 过滤富文字段数据。
* Filter the editor fields.
*
* @param string $fields
* @access public
* @return mixed
*/
public function get($fields = ''): mixed
{
global $config;
foreach($this->rawconfig as $field => $fieldConfig)
{
if(isset($fieldConfig['control']) && $fieldConfig['control'] == 'editor') $this->stripTags($field, $config->allowedTags);
}
if($this->formType == 'single') return parent::get($fields);
foreach($this->dataList as $rowIndex => $data)
{
$this->data = $data;
$this->dataList[$rowIndex] = parent::get($fields);
}
return $this->dataList;
}
}
+5 -5
View File
@@ -60,7 +60,7 @@ class Requests_Cookie_Jar implements ArrayAccess, IteratorAggregate {
* @param string $key Item key
* @return boolean Does the item exist?
*/
public function offsetExists($key) {
public function offsetExists($key): bool {
return isset($this->cookies[$key]);
}
@@ -70,7 +70,7 @@ class Requests_Cookie_Jar implements ArrayAccess, IteratorAggregate {
* @param string $key Item key
* @return string|null Item value (null if offsetExists is false)
*/
public function offsetGet($key) {
public function offsetGet($key): string|null {
if (!isset($this->cookies[$key])) {
return null;
}
@@ -86,7 +86,7 @@ class Requests_Cookie_Jar implements ArrayAccess, IteratorAggregate {
* @param string $key Item name
* @param string $value Item value
*/
public function offsetSet($key, $value) {
public function offsetSet($key, $value): void {
if ($key === null) {
throw new Requests_Exception('Object is a dictionary, not a list', 'invalidset');
}
@@ -99,7 +99,7 @@ class Requests_Cookie_Jar implements ArrayAccess, IteratorAggregate {
*
* @param string $key
*/
public function offsetUnset($key) {
public function offsetUnset($key): void {
unset($this->cookies[$key]);
}
@@ -108,7 +108,7 @@ class Requests_Cookie_Jar implements ArrayAccess, IteratorAggregate {
*
* @return ArrayIterator
*/
public function getIterator() {
public function getIterator(): ArrayIterator {
return new ArrayIterator($this->cookies);
}
+3 -3
View File
@@ -23,7 +23,7 @@ class Requests_Response_Headers extends Requests_Utility_CaseInsensitiveDictiona
* @param string $key
* @return string|null Header value
*/
public function offsetGet($key) {
public function offsetGet($key): string|null {
$key = strtolower($key);
if (!isset($this->data[$key])) {
return null;
@@ -40,7 +40,7 @@ class Requests_Response_Headers extends Requests_Utility_CaseInsensitiveDictiona
* @param string $key Item name
* @param string $value Item value
*/
public function offsetSet($key, $value) {
public function offsetSet($key, $value): void {
if ($key === null) {
throw new Requests_Exception('Object is a dictionary, not a list', 'invalidset');
}
@@ -92,7 +92,7 @@ class Requests_Response_Headers extends Requests_Utility_CaseInsensitiveDictiona
* Converts the internal
* @return ArrayIterator
*/
public function getIterator() {
public function getIterator(): ArrayIterator {
return new Requests_Utility_FilteredIterator($this->data, array($this, 'flatten'));
}
}
@@ -13,91 +13,97 @@
* @subpackage Utilities
*/
class Requests_Utility_CaseInsensitiveDictionary implements ArrayAccess, IteratorAggregate {
/**
* Actual item data
*
* @var array
*/
protected $data = array();
/**
* Actual item data
*
* @var array
*/
protected $data = array();
/**
* Creates a case insensitive dictionary.
*
* @param array $data Dictionary/map to convert to case-insensitive
*/
public function __construct(array $data = array()) {
foreach ($data as $key => $value) {
$this->offsetSet($key, $value);
}
}
/**
* Creates a case insensitive dictionary.
*
* @param array $data Dictionary/map to convert to case-insensitive
*/
public function __construct(array $data = array()) {
foreach ($data as $key => $value) {
$this->offsetSet($key, $value);
}
}
/**
* Check if the given item exists
*
* @param string $key Item key
* @return boolean Does the item exist?
*/
public function offsetExists($key) {
$key = strtolower($key);
return isset($this->data[$key]);
}
/**
* Check if the given item exists
*
* @param string $key Item key
* @return boolean Does the item exist?
*/
#[\ReturnTypeWillChange]
public function offsetExists($key): bool {
$key = strtolower($key);
return isset($this->data[$key]);
}
/**
* Get the value for the item
*
* @param string $key Item key
* @return string|null Item value (null if offsetExists is false)
*/
public function offsetGet($key) {
$key = strtolower($key);
if (!isset($this->data[$key])) {
return null;
}
/**
* Get the value for the item
*
* @param string $key Item key
* @return string|null Item value (null if offsetExists is false)
*/
#[\ReturnTypeWillChange]
public function offsetGet($key): string|null {
$key = strtolower($key);
if (!isset($this->data[$key])) {
return null;
}
return $this->data[$key];
}
return $this->data[$key];
}
/**
* Set the given item
*
* @throws Requests_Exception On attempting to use dictionary as list (`invalidset`)
*
* @param string $key Item name
* @param string $value Item value
*/
public function offsetSet($key, $value) {
if ($key === null) {
throw new Requests_Exception('Object is a dictionary, not a list', 'invalidset');
}
/**
* Set the given item
*
* @throws Requests_Exception On attempting to use dictionary as list (`invalidset`)
*
* @param string $key Item name
* @param string $value Item value
*/
#[\ReturnTypeWillChange]
public function offsetSet($key, $value): void {
if ($key === null) {
throw new Requests_Exception('Object is a dictionary, not a list', 'invalidset');
}
$key = strtolower($key);
$this->data[$key] = $value;
}
$key = strtolower($key);
$this->data[$key] = $value;
}
/**
* Unset the given header
*
* @param string $key
*/
public function offsetUnset($key) {
unset($this->data[strtolower($key)]);
}
/**
* Unset the given header
*
* @param string $key
*/
#[\ReturnTypeWillChange]
public function offsetUnset($key): void {
unset($this->data[strtolower($key)]);
}
/**
* Get an iterator for the data
*
* @return ArrayIterator
*/
public function getIterator() {
return new ArrayIterator($this->data);
}
/**
* Get an iterator for the data
*
* @return ArrayIterator
*/
#[\ReturnTypeWillChange]
public function getIterator(): ArrayIterator {
return new ArrayIterator($this->data);
}
/**
* Get the headers as an array
*
* @return array Header data
*/
public function getAll() {
return $this->data;
}
/**
* Get the headers as an array
*
* @return array Header data
*/
#[\ReturnTypeWillChange]
public function getAll() {
return $this->data;
}
}
+3 -1
View File
@@ -322,7 +322,9 @@ class Gitea
if(strpos($fromRevision, '^') !== false)
{
$list = execCmd(escapeCmd("$this->client log -2 $toRevision --pretty=format:%H -- $path"), 'array');
if(isset($list[1])) $fromRevision = $list[1];
if(!isset($list[1])) return execCmd(escapeCmd("$this->client show HEAD"), 'array');
$fromRevision = $list[1];
}
$lines = execCmd(escapeCmd("$this->client diff $fromRevision $toRevision -- $path"), 'array');
return $lines;
+17 -18
View File
@@ -146,7 +146,7 @@ class gitlab
{
$params['page'] = $page;
$list = $this->fetch($api, $params);
if(empty($list)) break;
if(empty($list) || !is_array($list)) break;
foreach($list as $tag) $tags[] = $tag->name;
if(count($list) < $params['per_page']) break;
@@ -173,7 +173,7 @@ class gitlab
{
$params['page'] = $page;
$branchList = $this->fetch("branches", $params);
if(empty($branchList)) break;
if(empty($branchList) || !is_array($branchList)) break;
foreach($branchList as $branch)
{
@@ -260,7 +260,6 @@ class gitlab
if(empty($results) or isset($results->message)) return array();
$blames = array();
$revLine = 0;
$revision = '';
$lineNumber = 1;
@@ -305,22 +304,23 @@ class gitlab
if(!scm::checkRevision($fromRevision) and $extra != 'isBranchOrTag') return array();
if(!scm::checkRevision($toRevision) and $extra != 'isBranchOrTag') return array();
$api = "compare";
$params = array('from' => $fromRevision, 'to' => $toRevision);
$sameVersion = $fromRevision == $toRevision . '^';
$api = $sameVersion ? "commits/$toRevision/diff" : "compare";
$params = array('from' => $fromRevision, 'to' => $toRevision, 'straight' => true);
if($fromProject) $params['from_project_id'] = $fromProject;
if($toRevision == 'HEAD' and $this->branch) $params['to'] = $this->branch;
$results = $this->fetch($api, $params);
if(!isset($results->diffs)) return array();
$results = $this->fetch($api, $sameVersion ? array() : $params);
$diffs = isset($results->diffs) ? $results->diffs : array();
if($sameVersion && is_array($results)) $diffs = $results;
if(!$diffs) return array();
foreach($results->diffs as $key => $diff)
{
if($path != '' and strpos($diff->new_path, $path) === false) unset($results->diffs[$key]);
}
$diffs = $results->diffs;
$lines = array();
foreach($diffs as $diff)
{
if($path != '' && strpos($diff->new_path, $path) === false) continue;
$lines[] = sprintf("diff --git a/%s b/%s", $diff->old_path, $diff->new_path);
$lines[] = sprintf("index %s ... %s %s ", $fromRevision, $toRevision, $diff->b_mode);
$lines[] = sprintf("--a/%s", $diff->old_path);
@@ -374,7 +374,7 @@ class gitlab
$list = $this->tree($parent, 0);
$file = new stdclass();
foreach($list as $node) if($node->path == $entry) $file = $node;
if(!empty($list)) foreach($list as $node) if($node->path == $entry) $file = $node;
$commits = $this->getCommitsByPath($entry);
@@ -789,7 +789,7 @@ class gitlab
$allResults = array();
if($multi)
{
$results = commonModel::httpWithHeader($api . "&page=1");
$results = commonModel::http($api . "&page=1", null, array(), array(), 'data', 'GET', 30, true, false);
if(empty($results['header']['X-Total-Pages'])) return array();
$totalPages = $results['header']['X-Total-Pages'];
@@ -829,15 +829,15 @@ class gitlab
}
else
{
list($response, $httpCode) = commonModel::http($api, null, array(), array(), 'data', 'POST', 30, true, false);
$response = commonModel::http($api, null, array(), array(), 'data', 'POST', 30, true, false);
if(!empty(commonModel::$requestErrors))
{
commonModel::$requestErrors = array();
return array();
}
if($httpCode == 500 or $httpCode == 404) return array();
return json_decode($response);
if(in_array($response[1], array(500, 404, 401))) return array();
return json_decode($response['body']);
}
}
@@ -867,7 +867,6 @@ class gitlab
public function parseLog($logs)
{
$parsedLogs = array();
$i = 0;
foreach($logs as $commit)
{
if(!isset($commit->id)) continue;
+5 -3
View File
@@ -247,7 +247,7 @@ class GitRepo
$path = ltrim($path, DIRECTORY_SEPARATOR);
chdir($this->root);
$list = execCmd(escapeCmd("$this->client blame -l $revision -- $path"), 'array');
$list = execCmd(escapeCmd("$this->client blame -c -l $revision -- $path"), 'array');
$blames = array();
$revLine = 0;
@@ -256,7 +256,7 @@ class GitRepo
{
if(empty($line)) continue;
if($line[0] == '^') $line = substr($line, 1);
preg_match('/^([0-9a-f]{39,40})\s.*\((\S+)\s+([\d-]+)\s(.*)\s(\d+)\)(.*)$/U', $line, $matches);
preg_match('/^([0-9a-f]{39,40})\s.*\(\s*(\S+)\s+([\d-]+)\s(.*)\s(\d+)\)(.*)$/U', $line, $matches);
if(isset($matches[1]) and $matches[1] != $revision)
{
@@ -310,7 +310,9 @@ class GitRepo
if(strpos($fromRevision, '^') !== false)
{
$list = execCmd(escapeCmd("$this->client log -2 $toRevision --pretty=format:%H -- $path"), 'array');
if(isset($list[1])) $fromRevision = $list[1];
if(!isset($list[1])) return execCmd(escapeCmd("$this->client show HEAD"), 'array');
$fromRevision = $list[1];
}
$lines = execCmd(escapeCmd("$this->client diff $fromRevision $toRevision -- $path"), 'array');
return $lines;
+3 -1
View File
@@ -322,7 +322,9 @@ class Gogs
if(strpos($fromRevision, '^') !== false)
{
$list = execCmd(escapeCmd("$this->client log -2 $toRevision --pretty=format:%H -- $path"), 'array');
if(isset($list[1])) $fromRevision = $list[1];
if(!isset($list[1])) return execCmd(escapeCmd("$this->client show HEAD"), 'array');
$fromRevision = $list[1];
}
$lines = execCmd(escapeCmd("$this->client diff $fromRevision $toRevision -- $path"), 'array');
return $lines;
+1 -3
View File
@@ -15,9 +15,7 @@ class scm
$className = $repo->SCM;
if($className == 'Git') $className = 'GitRepo';
if(!class_exists($className)) require(strtolower($className) . '.class.php');
$repoRoot = $repo->path;
if('Subversion' == $className && !empty($repo->prefix)) $repoRoot = substr($repo->path, 0, strlen($repo->path) - strlen($repo->prefix));
$this->engine = new $className($repo->client, $repoRoot, $repo->account, $repo->password, $repo->encoding, $repo);
$this->engine = new $className($repo->client, $className == 'Gitlab' ? $repo->apiPath : $repo->path, $repo->account, $repo->password, $repo->encoding, $repo);
}
/**
+111
View File
@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
/**
* The trace class file of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author Lu Fei <lufei@easycorp.ltd>
* @package trace
* @link https://www.zentao.net
*/
class trace
{
/**
* @var array
*/
public $trace = array();
protected $app;
protected $dao;
public function __construct()
{
global $app, $dao;
$this->app = $app;
$this->dao = $dao;
}
/**
* 获取请求信息。
* Get request info.
*
* @return void
*/
public function getRequestInfo()
{
$this->trace['request'] = array(
'start' => date('Y-m-d H:i:s', (int)$this->app->startTime),
'url' => $this->app->getURI(true),
'protocol' => $this->app->server->server_protocol,
'method' => $this->app->server->request_method,
'timeUsed' => round(getTime() - $this->app->startTime, 4) * 1000,
'memory' => round(memory_get_peak_usage() / 1024, 1),
'querys' => count(dao::$querys),
'caches' => count(dao::$cache),
'files' => count(get_included_files()),
'session' => session_id()
);
}
/**
* 获取请求加载的文件。
* Get request files.
*
* @return void
*/
public function getRequestFiles()
{
$this->trace['files'] = get_included_files();
}
/**
* 获取请求的 SQL 语句。
* Get request SQLs.
*
* @return void
*/
public function getRequestSqls()
{
$this->trace['sqlQuery'] = dao::$querys;
}
/**
* 获取请求的 SQL profiles。
* Get request SQL profiles.
*
* @return array
*/
public function getSQLProfiles()
{
global $config;
/* 达梦数据库不支持下面的语法,直接跳过。The */
if($config->db->driver === 'dm') return;
$profiling = $this->dao->dbh->query('SHOW PROFILES')->fetchAll(PDO::FETCH_ASSOC);
$this->trace['profiles'] = $profiling;
}
/**
* 生成请求 Trace。
* Generate request trace.
*
* @return array
*/
public function getTrace()
{
$this->getRequestInfo();
$this->getRequestFiles();
$this->getRequestSqls();
$this->getSQLProfiles();
return $this->trace;
}
public function __toString(): string
{
return json_encode($this->getTrace());
}
}
+2 -2
View File
@@ -94,7 +94,7 @@ class xmind
foreach($sceneMaps as $key => $scene)
{
if($scene->parentID != $parentScene->sceneID + CHANGEVALUE) continue;
if($scene->parentID != $parentScene->sceneID) continue;
$suffix = $config['scene'].':'.$scene->sceneID;
$sceneNode = $this->createNode($xmlDoc, $scene->sceneName, $suffix, array('nodeType'=>'scene'));
@@ -315,4 +315,4 @@ class xmind
{
return $needle === '' || substr_compare($haystack, $needle, -strlen($needle)) === 0;
}
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
/**
* The config file of zin of ZenTaoPMS.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin;
function loadConfig()
{
global $app, $config;
if(!isset($config->zin)) $config->zin = new \stdClass();
$config->zin->lang = $app->getClientLang();
$config->zin->wgVer = isset($config->wgVer) ? $config->wgVer : '1';
$config->zin->wgVerMap = isset($config->wgVerMap) ? $config->wgVerMap : array();
$config->zin->zuiPath = isset($config->zuiPath) ? $config->zuiPath : ($app->getWebRoot() . 'js/zui3/');
}
+169
View File
@@ -0,0 +1,169 @@
<?php
declare(strict_types=1);
/**
* The context class file of zin of ZenTaoPMS.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin;
require_once dirname(__DIR__) . DS . 'utils' . DS . 'dataset.class.php';
require_once dirname(__DIR__) . DS . 'utils' . DS . 'flat.func.php';
class context extends \zin\utils\dataset
{
public function addImport()
{
return $this->addToList('import', func_get_args());
}
public function getImportList()
{
return $this->getList('import');
}
public function addCSS()
{
return $this->addToList('css', func_get_args());
}
public function getCSS()
{
return trim(implode("\n", $this->getList('css')));
}
public function addJS()
{
return $this->addToList('js', func_get_args());
}
public function addJSVar($name, $value)
{
return $this->addToList('jsVar', h::createJsVarCode($name, $value));
}
public function addWgWithEvents($wg)
{
$list = $this->getWgWithEventsList();
if(in_array($wg, $list)) return $this;
return $this->addToList('wgWithEvents', $wg);
}
public function getWgWithEventsList()
{
return $this->getList('wgWithEvents');
}
public function addJSCall()
{
$code = call_user_func_array('\zin\h::createJsCallCode', func_get_args());
return $this->addToList('jsCall', $code);
}
public function getEventsBindings()
{
$wgs = $this->getList('wgWithEvents');
$codes = [];
foreach($wgs as $wg)
{
if(!method_exists($wg, 'buildEvents')) continue;
$code = $wg->buildEvents();
if(!empty($code)) $codes[] = $code;
}
return $codes;
}
public function getJS()
{
$js = trim(implode("\n", array_merge($this->getList('jsVar'), $this->getList('js'), $this->getEventsBindings(), $this->getList('jsCall'))));
if(empty($js)) return '';
if(strpos($js, 'setTimeout') !== false) $js = 'function setTimeout(callback, time){return typeof window.registerTimer === "function" ? window.registerTimer(callback, time) : window.setTimeout(callback, time);}' . $js;
if(strpos($js, 'setInterval') !== false) $js = 'function setInterval(callback, time){return typeof window.registerTimer === "function" ? window.registerTimer(callback, time, "interval") : window.setInterval(callback, time);}' . $js;
$methods = array('onPageUnmount', 'beforePageUpdate', 'afterPageUpdate', 'onPageRender');
foreach($methods as $method)
{
if(strpos($js, $method) !== false) $js .= "if(typeof $method === 'function') window.$method = $method;";
}
return $js;
}
public static $map = array();
public static function js(/* string ...$code */)
{
$context = static::current();
call_user_func_array(array($context, 'addJS'), \zin\utils\flat(func_get_args()));
}
public static function jsCall(/* string ...$code */)
{
$context = static::current();
call_user_func_array(array($context, 'addJSCall'), func_get_args());
}
public static function jsVar($name, $value)
{
$context = static::current();
$context->addJSVar($name, $value);
}
public static function css(/* string ...$code */)
{
$context = static::current();
call_user_func_array(array($context, 'addCSS'), \zin\utils\flat(func_get_args()));
}
public static function import(/* string ...$files */)
{
$context = static::current();
call_user_func_array(array($context, 'addImport'), func_get_args());
}
/**
* Get current context.
*
* @access public
* @return context
*/
public static function current(): context
{
if(empty(static::$map)) static::$map['current'] = new context();
return static::$map['current'];
}
/**
* Create widget context.
*
* @access public
* @param string $gid The widget gid.
* @return context
*/
public static function create(string $gid): context
{
if(isset(static::$map[$gid])) return static::$map[$gid];
$context = new context();
static::$map[$gid] = $context;
return $context;
}
/**
* Destroy widget context.
*
* @access public
* @param string $gid The widget gid.
* @return void
*/
public static function destroy(string $gid = null): void
{
if($gid === null) unset(static::$map['current']);
elseif(isset(static::$map[$gid])) unset(static::$map[$gid]);
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/**
* The context function file of zin of ZenTaoPMS.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin;
require_once __DIR__ . DS . 'context.class.php';
function js()
{
call_user_func_array('\zin\context::js', func_get_args());
}
function jsCall()
{
call_user_func_array('\zin\context::jsCall', func_get_args());
}
function jsVar()
{
call_user_func_array('\zin\context::jsVar', func_get_args());
}
function css()
{
call_user_func_array('\zin\context::css', func_get_args());
}
function import()
{
call_user_func_array('\zin\context::import', func_get_args());
}
+55
View File
@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
/**
* The data function file of zin of ZenTaoPMS.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin;
function setPageData($name, $value)
{
if(is_array($value) && empty($name))
{
foreach ($value as $key => $val) zin::setData($key, $val);
return;
}
zin::setData($name, $value);
}
function getPageData($name)
{
if(is_array($name))
{
$values = array();
foreach($name as $propName)
{
$values[] = zin::getData($propName);
}
return $values;
}
return zin::getData($name);
}
function data()
{
$args = func_get_args();
if(count($args) >= 2) return setPageData($args[0], $args[1]);
return getPageData($args[0]);
}
/**
* Set page data
* @deprecated Use data($name, $value) insteadOf useData($name, $value)
*/
function useData($name, $value)
{
return setPageData($name, $value);
}
+66
View File
@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
/**
* The directive class file of zin lib.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin;
require_once __DIR__ . DS . 'zin.class.php';
class directive
{
public string $type;
public mixed $data;
public ?array $options;
public ?wg $parent = null;
/**
* Construct a directive object
* @param string $type
* @param mixed $data
* @param array $options
* @access public
*/
public function __construct(string $type, mixed $data, ?array $options = null)
{
$this->type = $type;
$this->data = $data;
$this->options = $options;
zin::renderInGlobal($this);
}
public function __debugInfo(): array
{
return array(
'type' => $this->type,
'data' => $this->data,
'options' => $this->options
);
}
public static function is(mixed $item, ?string $type = null): bool
{
return $item instanceof directive && ($type === null || $item->type === $type);
}
}
function directive($type, $data, $options = null): directive
{
return new directive($type, $data, $options);
}
function isDirective(mixed $item, ?string $type = null): bool
{
return directive::is($item, $type);
}
+395
View File
@@ -0,0 +1,395 @@
<?php
declare(strict_types=1);
/**
* The dom widget class file of zin of ZenTaoPMS.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin;
use stdClass;
require_once dirname(__DIR__) . DS . 'utils' . DS . 'deep.func.php';
require_once __DIR__ . DS . 'selector.func.php';
class dom
{
/**
* @var wg
*/
public $wg;
public $children = array();
public $selectors = null;
public $renderInner = false;
public $renderType;
public $dataGetters = null;
public $dataCommands;
public $buildList = null;
public $buildListInner = false;
/**
* Construct the dom object.
*
* @param wg $wg
* @param array $children
* @param array|string|object $selectors
* @access public
*/
public function __construct($wg, $children, $selectors = null, $renderType = null, $dataCommands = null)
{
$this->wg = $wg;
$this->renderType = $renderType;
$this->add($children);
$this->addSelectors($selectors);
$this->addDataCommands($dataCommands);
}
public function __debugInfo()
{
return array(
'gid' => $this->wg->gid,
'type' => $this->wg->type(),
'count' => count($this->children),
'renderInner' => $this->renderInner,
'renderType' => $this->renderType,
'dataCommands' => $this->dataCommands,
'selectors' => stringifyWgSelectors($this->selectors)
);
}
public function add($children)
{
if(empty($children)) return;
if(!is_array($children)) $children = [$children];
foreach($children as $child)
{
if(is_array($child)) $this->add($child);
else $this->children[] = $child;
}
}
public function addDataCommands($commands)
{
if(empty($commands)) return;
if(is_string($commands))
{
$commandList = explode(',', $commands);
$commands = array();
foreach($commandList as $command)
{
$parts = explode(':', $command, 2);
$commands[$parts[0]] = count($parts) > 1 ? $parts[1] : $parts[0];
}
}
if($this->dataCommands === null) $this->dataCommands = array();
$index = 0;
foreach($commands as $key => $command)
{
$this->dataCommands[$index == $key ? $command : $key] = $command;
$index++;
}
}
public function addSelectors($selectors)
{
if(empty($selectors)) return;
if($this->selectors === null) $this->selectors = array();
$selectors = parseWgSelectors($selectors);
foreach($selectors as $selector)
{
if(isset($selector->command) && !empty($selector->command)) $this->addDataCommands([$selector->tag => $selector->command]);
else $this->selectors[] = $selector;
}
}
public function isMatch($selector)
{
return $this->wg->isMatch($selector);
}
/**
* Build the children dom list.
* @access public
* @return array
*/
public function build()
{
if($this->buildList !== null && $this->buildListInner === $this->renderInner) return $this->buildList;
if(empty($this->selectors) && !empty($this->dataCommands))
{
$this->buildList = array();
return $this->buildList;
}
$list = array();
$children = $this->renderInner ? $this->wg->children() : $this->children;
if(empty($children)) return $list;
foreach($children as $child) $list[] = ($child instanceof wg) ? $child->buildDom() : $child;
if(!empty($this->selectors)) $list = static::filter($list, $this->selectors);
$this->buildList = $list;
$this->buildListInner = $this->renderInner;
return $list;
}
public function render()
{
if($this->renderType === 'json') return $this->renderJson();
if($this->renderType === 'list') return $this->renderList();
return $this->renderHtml();
}
/**
* Render dom to json object.
*
* @access public
* @return object
*/
public function renderJson(): object
{
$list = $this->build();
$output = new stdClass();
foreach($list as $name => $item)
{
$output->$name = static::renderItemToJson($item);
}
if(!empty($this->dataCommands))
{
$data = array();
foreach($this->dataCommands as $name => $command)
{
$data[$name] = data($command);
}
$output->data = $data;
}
return $output;
}
/**
* Render dom to html string.
*
* @access public
* @return string
*/
public function renderHtml(): string
{
$list = $this->build();
if(empty($list)) return '';
$output = array();
foreach($list as $item)
{
$result = static::renderItemToHtml($item);
if(!is_string($result)) $result = json_encode($result);
$output[] = $result;
}
return implode('', $output);
}
/**
* Render dom to list by given selector.
*
* @access public
* @return array
*/
public function renderList(): array
{
$list = $this->build();
$output = array();
foreach($list as $name => $item)
{
if(is_array($item) && count($item) === 1) $item = $item[0];
$renderType = $item instanceof dom ? $item->renderType : 'html';
if(empty($renderType)) $renderType = 'html';
$output[] = array('name' => $name, 'data' => static::renderDomItem($item, $renderType), 'type' => $renderType);
}
if(!empty($this->dataCommands))
{
foreach($this->dataCommands as $name => $command)
{
$output[] = array('name' => $name, 'data' => data($command), 'type' => 'command');
}
}
return $output;
}
public static function renderDomItem($item, $defaultType = 'html')
{
if($item instanceof dom)
{
$renderType = $item->renderType;
if(empty($renderType)) $renderType = $defaultType;
if($renderType === 'json') return dom::renderItemToJson($item);
return dom::renderItemToHtml($item->build());
}
$renderType = $defaultType;
if($renderType === 'json') return static::renderItemToJson($item);
return static::renderItemToHtml($item);
}
public static function renderItemToJson($item)
{
if($item === null || is_bool($item)) return null;
if(is_array($item))
{
$output = array();
foreach($item as $subItem) $output[] = static::renderItemToJson($subItem);
return $output;
}
if($item instanceof dom)
{
$json = $item->wg->toJSON();
if(!empty($item->dataGetters))
{
$output = array();
$props = explode(',', $item->dataGetters);
foreach($props as $prop)
{
$prop = trim($prop);
if(empty($prop)) continue;
$parts = explode(':', $prop, 2);
$name = $parts[0];
$namePath = count($parts) > 1 ? $parts[1] : $parts[0];
$output[$name] = \zin\utils\deepGet($json, $namePath);
}
return $output;
}
return $json;
}
if($item instanceof wg) return dom::renderDomItem($item, 'json');
if(is_string($item)) return $item;
if(is_object($item))
{
if(isDirective($item, 'html')) return $item->data;
if(isDirective($item, 'text')) return htmlspecialchars($item->data);
if(isset($item->html)) return $item->html;
if(isset($item->text)) return htmlspecialchars($item->text);
if(method_exists($item, 'render')) return $item->render();
}
return strval($item);
}
public static function renderItemToHtml($item)
{
if($item === null || is_bool($item)) return '';
if(is_array($item))
{
$output = array();
foreach($item as $subItem) $output[] = static::renderItemToHtml($subItem);
return implode('', $output);
}
if($item instanceof dom) return dom::renderItemToHtml($item->build());
if($item instanceof wg) return $item->render();
if(is_string($item)) return $item;
if(is_object($item))
{
if(isDirective($item, 'html')) return $item->data;
if(isDirective($item, 'text')) return htmlspecialchars($item->data);
if(isset($item->html)) return $item->html;
if(isset($item->text)) return htmlspecialchars($item->text);
if(method_exists($item, 'render')) return $item->render();
}
return strval($item);
}
/**
* Filter the dom list with selector.
*
* @param array $list
* @param object $selector
* @param array $filteredList
* @access public
* @return array
*/
public static function filterList(&$list, $selector, &$filteredList)
{
if(empty($list) || empty($selector)) return [];
$results = array();
foreach($list as $item)
{
if(!($item instanceof dom) || in_array($item->wg->gid, $filteredList)) continue;
if($item->wg->isMatch($selector))
{
$item->selector = $selector;
$item->renderInner = isset($selector->inner) ? $selector->inner : false;
$item->renderType = isset($selector->type) ? $selector->type : null;
$item->dataGetters = isset($selector->data) ? $selector->data : null;
$filteredList[] = $item->wg->gid;
$results[] = $item;
}
else
{
$children = $item->build();
if(!empty($children))
{
$subResults = static::filterList($children, $selector, $filteredList);
foreach($subResults as $subItem) $results[] = $subItem;
}
}
if($selector->first && !empty($results)) break;
}
return $results;
}
/**
* Filter the dom list with selectors.
*
* @param array $domList
* @param array $selectors
* @access public
* @return array
*/
public static function filter(&$domList, $selectors)
{
if(empty($selectors)) return $domList;
$list = array();
$filteredList = array();
foreach($selectors as $selector)
{
$results = static::filterList($domList, $selector, $filteredList);
$list[$selector->name] = $results;
}
return $list;
}
}
+318
View File
@@ -0,0 +1,318 @@
<?php
declare(strict_types=1);
/**
* The html element class file of zin of ZenTaoPMS.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin;
require_once dirname(__DIR__) . DS . 'utils' . DS . 'flat.func.php';
require_once __DIR__ . DS . 'wg.class.php';
require_once __DIR__ . DS . 'wg.func.php';
class h extends wg
{
protected static array $defineProps = array(
'tagName: string',
'selfClose?: bool'
);
public function getTagName(): string
{
return $this->props->get('tagName');
}
public function isDomElement(): bool
{
return true;
}
public function isSelfClose()
{
$selfClose = $this->props->get('selfClose');
if($selfClose !== null) return $selfClose;
return in_array($this->getTagName(), static::$selfCloseTags);
}
public function build(): array
{
if($this->isSelfClose()) return array($this->buildSelfCloseTag());
return array($this->buildTagBegin(), parent::build(), $this->buildTagEnd());
}
public function toJsonData(): array
{
$data = parent::toJsonData();
$data['type'] = 'h:' . $this->getTagName();
return $data;
}
public function type(): string
{
return $this->getTagName();
}
public function shortType(): string
{
return $this->getTagName();
}
protected function getPropsStr(): string
{
$propStr = $this->props->toStr(array_keys(static::definedPropsList()));
if($this->props->hasEvent() && empty($this->id()) && $this->getTagName() !== 'html') $propStr = "$propStr id='$this->gid'";
return empty($propStr) ? '' : " $propStr";
}
protected function buildSelfCloseTag(): string
{
$tagName = $this->getTagName();
$propStr = $this->getPropsStr();
return "<$tagName$propStr />";
}
protected function buildTagBegin(): string
{
$tagName = $this->getTagName();
$propStr = $this->getPropsStr();
return "<$tagName$propStr>";
}
protected function buildTagEnd(): string
{
$tagName = $this->getTagName();
return "</$tagName>";
}
public static function create(): h
{
$args = func_get_args();
$tagName = array_shift($args);
return new h(is_string($tagName) ? set('tagName', $tagName) : $tagName, $args);
}
public static function __callStatic(string $tagName, array $args): h
{
return new h(set('tagName', $tagName), $args);
}
public static function a(): h
{
$a = static::create('a', func_get_args());
if($a->prop('target') === '_blank' && !$a->hasProp('rel')) $a->prop('rel', 'noopener noreferrer');
return $a;
}
public static function button()
{
return static::create('button', set('type', 'button'), func_get_args());
}
public static function input()
{
return static::create('input', set('type', 'text'), func_get_args());
}
public static function formHidden(/* $name, $value, ...$args */)
{
$args = func_get_args();
$name = array_shift($args);
$value = array_shift($args);
return static::create('input', set('type', 'hidden'), set::name($name), set::value($value), $args);
}
public static function checkbox()
{
return static::create('input', set('type', 'checkbox'), func_get_args());
}
public static function radio()
{
return static::create('input', set('type', 'radio'), func_get_args());
}
public static function date()
{
return static::create('input', set('type', 'date'), func_get_args());
}
public static function file()
{
return static::create('input', set('type', 'file'), func_get_args());
}
public static function textarea(/* ...$args */)
{
$args = func_get_args();
list($code, $args) = h::splitRawCode($args);
return static::create('textarea', $code, $args);
}
/**
* create a html comment tag <!--...-->
*
* @access public
* @param string $comment
* @return directive
*/
public static function comment(string $comment): directive
{
return html("<!-- $comment -->");
}
public static function importJs(/* $src, ...$args */)
{
$args = func_get_args();
$src = array_shift($args);
return static::create('script', set('src', $src), $args);
}
public static function importCss(/* $src, ...$args */)
{
$args = func_get_args();
$src = array_shift($args);
return static::create('link', set('rel', 'stylesheet'), set('href', $src), $args);
}
public static function import(/* $file, $type = null, ...$args */)
{
$args = array_merge(func_get_args(), array(null, null));
$file = array_shift($args);
$type = array_shift($args);
if(is_array($file))
{
$children = array();
foreach($file as $file)
{
$children[] = static::import($file, $type);
}
return $children;
}
if($type === null) $type = pathinfo($file, PATHINFO_EXTENSION);
if($type == 'js' || $type == 'cjs') return static::importJs($file, $args);
if($type == 'css') return static::importCss($file, $args);
return null;
}
public static function css(/* ...$args */)
{
list($code, $args) = h::splitRawCode(func_get_args());
if(empty($code)) return null;
return static::create('style', html(implode("\n", $code)), $args);
}
public static function globalJS(/* ...$args */)
{
list($code, $args) = h::splitRawCode(func_get_args());
if(empty($code)) return null;
return static::create('script', html(implode("\n", $code)), $args);
}
public static function js(/* ...$args */)
{
list($code, $args) = h::splitRawCode(func_get_args());
if(empty($code)) return null;
return static::create('script', html(h::createJsScopeCode($code)), $args);
}
public static function jsVar(/* $name, $value, ...$args */)
{
$args = func_get_args();
$name = array_shift($args);
$value = array_shift($args);
return static::js(static::createJsVarCode($name, $value), $args);
}
public static function jsCall(/* $funcName, ...$args */)
{
$args = func_get_args();
$funcName = array_shift($args);
$funcArgs = [];
$directives = [];
foreach($args as $arg)
{
if(isDirective($arg)) $directives[] = $arg;
else $funcArgs[] = $arg;
}
$code = static::createJsCallCode($funcName, $funcArgs);
return static::js($code, $directives);
}
public static function createJsCallCode($func, $args)
{
foreach($args as $index => $arg)
{
$args[$index] = h::encodeJsonWithRawJs($arg, JSON_UNESCAPED_UNICODE);
}
if($func[0] === '~')
{
$func = substr($func, 1);
return "$(() => $func(" . implode(',', $args) . "));";
}
return $func . '(' . implode(',', $args) . ');';
}
public static function createJsVarCode($name, $value)
{
$vars = is_string($name) ? array($name => $value) : $name;
$jsCode = '';
foreach($vars as $var => $val)
{
if(empty($var)) continue;
$val = h::encodeJsonWithRawJs($val);
if(str_starts_with($var, 'window.')) $jsCode .= "$var=" . $val . ';';
elseif(str_starts_with($var, '+')) $jsCode .= 'let ' . substr($var, 1) . '=' . $val . ';';
else $jsCode .= "const $var=" . $val . ';';
}
return $jsCode;
}
public static function createJsScopeCode(string|array $codes): string
{
if(is_array($codes)) $codes = implode("\n", $codes);
return ";(function(){\n$codes\n}());";
}
public static function jsRaw(): string
{
return 'RAWJS<' . implode("\n", func_get_args()) . '>RAWJS';
}
protected static function encodeJsonWithRawJs($data)
{
$json = json_encode($data, JSON_UNESCAPED_UNICODE);
if(empty($json) && (is_array($data) || is_object($data))) return '[]';
$json = str_replace('"RAWJS<', '', str_replace('>RAWJS"', '', $json));
return $json;
}
protected static function splitRawCode($children)
{
$children = \zin\utils\flat($children);
$code = [];
$args = [];
foreach($children as $key => $child)
{
if(is_string($child)) $code[] = $child;
else $args[] = $child;
}
return [$code, $args];
}
public static $selfCloseTags = array('area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr');
}
+47
View File
@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
/**
* The html helper methods file of zin of ZenTaoPMS.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin;
require_once __DIR__ . DS . 'h.class.php';
require_once __DIR__ . DS . 'item.class.php';
require_once __DIR__ . DS . 'wg.func.php';
require_once __DIR__ . DS . 'set.class.php';
require_once __DIR__ . DS . 'to.class.php';
require_once __DIR__ . DS . 'data.func.php';
require_once __DIR__ . DS . 'on.class.php';
function h(): h {return call_user_func_array('\zin\h::create', func_get_args());}
function div(): h {return call_user_func_array('\zin\h::div', func_get_args());}
function span(): h {return call_user_func_array('\zin\h::span', func_get_args());}
function code(): h {return call_user_func_array('\zin\h::code', func_get_args());}
function canvas(): h {return call_user_func_array('\zin\h::canvas', func_get_args());}
function br(): h {return call_user_func_array('\zin\h::br', func_get_args());}
function a(): h {return call_user_func_array('\zin\h::a', func_get_args());}
function p(): h {return call_user_func_array('\zin\h::p', func_get_args());}
function img(): h {return call_user_func_array('\zin\h::img', func_get_args());}
function button(): h {return call_user_func_array('\zin\h::button', func_get_args());}
function h1(): h {return call_user_func_array('\zin\h::h1', func_get_args());}
function h2(): h {return call_user_func_array('\zin\h::h2', func_get_args());}
function h3(): h {return call_user_func_array('\zin\h::h3', func_get_args());}
function h4(): h {return call_user_func_array('\zin\h::h4', func_get_args());}
function h5(): h {return call_user_func_array('\zin\h::h5', func_get_args());}
function h6(): h {return call_user_func_array('\zin\h::h6', func_get_args());}
function ul(): h {return call_user_func_array('\zin\h::ul', func_get_args());}
function li(): h {return call_user_func_array('\zin\h::li', func_get_args());}
function template(): h {return call_user_func_array('\zin\h::template', func_get_args());}
function formHidden(): h {return call_user_func_array('\zin\h::formHidden', func_get_args());}
function fieldset(): h {return call_user_func_array('\zin\h::fieldset', func_get_args());}
function legend(): h {return call_user_func_array('\zin\h::legend', func_get_args());}
function jsRaw(): string {return call_user_func_array('\zin\h::jsRaw', func_get_args());}
+33
View File
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/**
* The common item element class file of zin of ZenTaoPMS.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin;
require_once __DIR__ . DS . 'wg.class.php';
require_once __DIR__ . DS . 'wg.func.php';
class item extends wg
{
public function build(): wg
{
if($this->parent instanceof wg && method_exists($this->parent, 'onBuildItem'))
{
return call_user_func(array($this->parent, 'onBuildItem'), $this);
}
return parent::build();
}
}
function item()
{
return new item(func_get_args());
}
+24
View File
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
/**
* The block setter class file of zin lib.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin;
require_once __DIR__ . DS . 'wg.func.php';
class on
{
public static function __callStatic($name, $args)
{
list($callback, $options) = array_merge($args, array(null));
return on($name, $callback, $options);
}
}
+284
View File
@@ -0,0 +1,284 @@
<?php
declare(strict_types=1);
/**
* The props class file of zin of ZenTaoPMS.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin;
use zin\utils\classlist;
use zin\utils\style;
require_once dirname(__DIR__) . DS . 'utils' . DS . 'dataset.class.php';
require_once dirname(__DIR__) . DS . 'utils' . DS . 'classlist.class.php';
require_once dirname(__DIR__) . DS . 'utils' . DS . 'style.class.php';
/**
* Manage properties for html element and widgets
*/
class props extends \zin\utils\dataset
{
/**
* Style property
*
* @access public
* @var style
*/
public style $style;
/**
* Class property
*
* @access public
* @var classlist
*/
public classlist $class;
public static array $booleanAttrs = array('allowfullscreen', 'async', 'autofocus', 'autoplay', 'checked', 'controls', 'default', 'defer', 'disabled', 'formnovalidate', 'inert', 'ismap', 'itemscope', 'loop', 'multiple', 'muted', 'nomodule', 'novalidate', 'open', 'playsinline', 'readonly', 'required', 'reversed', 'selected');
/**
* Create properties instance
*
* @access public
* @param array $props - Properties list array
*/
public function __construct(array $props = array())
{
$this->style = new style();
$this->class = new classlist();
parent::__construct($props);
}
/**
* Method for sub class to modify value on setting it
*
* @access public
* @param string $prop - Property name or properties list
* @param mixed $value - Property value
*/
protected function setVal(string $prop, mixed $value): props
{
if($prop === 'class' || $prop === '.') $this->class->set($value);
elseif($prop === 'style' || $prop === '~') $this->style->set($value);
elseif(str_starts_with($prop, '~')) $this->style->set(substr($prop, 1), $value);
elseif($prop === '--') $this->style->cssVar($value);
elseif(str_starts_with($prop, '--')) $this->style->cssVar(substr($prop, 2), $value);
elseif($prop === '!') $this->hx($value);
elseif(str_starts_with($prop, '!')) $this->hx(substr($prop, 1), $value);
elseif(str_starts_with($prop, ':')) $this->set('data-' . substr($prop, 1), $value);
elseif($prop === '@') $this->bindEvent($value);
elseif(str_starts_with($prop, '@')) $this->bindEvent(substr($prop, 1), $value);
else parent::setVal($prop, $value);
return $this;
}
protected function getVal(string $prop): mixed
{
if($prop === 'class' || $prop === '.')
{
if(!$this->class->count()) return null;
return $this->class->toStr();
}
if($prop === 'style' || $prop === '~')
{
if(!$this->style->count(true)) return null;
return $this->style->toStr();
}
return parent::getVal($prop);
}
/**
* @param string|string[] $name
* @param mixed $value
*/
public function reset(array|string $name, mixed $value = null)
{
if(is_array($name))
{
foreach($name as $n) $this->reset($n);
return;
}
if($name === 'class') return $this->class->clear();
if($name === 'style') return $this->style->clear();
$this->remove($name);
if($value) $this->setVal($name, $value);
}
public function bindEvent($name, $callback = null)
{
if(is_array($name))
{
foreach($name as $key => $value) $this->bindEvent($key, $value);
return;
}
$events = parent::getVal("@$name") ?? [];
if(is_array($callback)) $events = array_merge($events, $callback);
else $events[] = $callback;
parent::setVal("@$name", $events);
}
public function events(): array
{
$events = array();
foreach($this->data as $name => $value)
{
if(str_starts_with($name, '@')) $events[substr($name, 1)] = $value;
}
return $events;
}
public function hasEvent(): bool
{
foreach($this->data as $name => $value)
{
if(str_starts_with($name, '@')) return true;
}
return false;
}
public function hx(array|string $name, ?string $value = null)
{
if(is_array($name))
{
foreach($name as $key => $val) $this->set("hx-$key", $val);
return;
}
$this->set("hx-$name", $value);
}
/**
* Convert props to html string
*
* Example:
*
* // Properties data map:
* $map = array(
* 'id' => 'sayHelloBtn',
* 'data-title' => 'Say "Hello"!',
* 'data-content' => null,
* 'data-show' => true,
* );
* // Output string: id="sayHelloBtn" data-title="Say &quot;Hello&quot;!" data-show="true"
*
* @access public
*/
public function toStr(array|string $skipProps = array()): string
{
if(is_string($skipProps)) $skipProps = explode(',', $skipProps);
$pairs = array();
if($this->class->count()) $pairs[] = 'class="' . $this->class->toStr() . '"';
if($this->style->count(true)) $pairs[] = 'style="' . $this->style->toStr() . '"';
foreach($this->data as $name => $value)
{
/* Handle boolean attributes */
if(in_array($name, static::$booleanAttrs)) $value = $value ? true : null;
/* Skip any null value or events setting */
if($value === null || in_array($name, $skipProps) || $name[0] === '@') continue;
/* Convert non-string to json */
if($value === true && !str_starts_with($name, 'data-'))
{
$pairs[] = $name;
}
else
{
if(!is_string($value)) $value = json_encode($value);
$pairs[] = $name . '="' . htmlspecialchars($value) . '"';
}
}
return implode(' ', $pairs);
}
public function toJSON(bool $skipEvents = false): array
{
$data = $this->data;
if(!empty($this->style->data)) $data['style'] = $this->style->data;
if(!empty($this->class->toJSON())) $data['class'] = $this->class->toStr();
if($skipEvents)
{
foreach($data as $name => $value)
{
if(str_starts_with($name, '@')) unset($data[$name]);
}
}
return $data;
}
public function skip(array|string $skipProps = array(), bool $skipFalse = false): array
{
if(is_string($skipProps)) $skipProps = explode(',', $skipProps);
$data = $this->toJSON();
foreach($data as $name => $value)
{
if($value === null || $name[0] === '@' || in_array($name, $skipProps)) unset($data[$name]);
if($skipFalse && $value === false) unset($data[$name]);
}
return $data;
}
public function split(array|string $firstListProps = array()): array
{
if(is_string($firstListProps)) $firstListProps = explode(',', $firstListProps);
$data = $this->toJSON();
$firstList = array();
$restList = array();
foreach($data as $name => $value)
{
if($value === null || $name[0] === '@') continue;
if(in_array($name, $firstListProps)) $firstList[$name] = $value;
else $restList[$name] = $value;
}
return array($firstList, $restList);
}
public function pick(array|string $pickProps = array()): array
{
if(is_string($pickProps)) $pickProps = explode(',', $pickProps);
$data = $this->toJSON();
foreach($data as $name => $value)
{
if($value === null || !in_array($name, $pickProps)) unset($data[$name]);
}
return $data;
}
/**
* Clone a new instance
*
* @access public
* @return props
*/
public function copy(): props
{
$props = new props($this->data);
$props->style = clone $this->style;
$props->class = clone $this->class;
return $props;
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
/**
* The rawContent class file of zin lib.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin;
require_once __DIR__ . DS . 'wg.class.php';
class rawContent extends wg
{
protected function build(): directive
{
return h::comment('{{RAW_CONTENT}}');
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
/**
* The render function file of zin of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @link https://www.zentao.net
*/
namespace zin;
require_once __DIR__ . DS . 'zin.class.php';
/**
* 将视图页面声明的所有内容通过一个部件进行渲染,并输出 HTML。
* Render page content with a widget to HTML.
*
* @access public
* @param string $wgName
* @param array $options
* @return void
*/
function render(string $wgName = '', array $options = array())
{
/* 获取全局渲染部件实例和指令。 Get global render widgets and directives. */
$globalItems = zin::getGlobalRenderList();
/* 决定部件名称,如果是 Ajax 请求则进行特殊处理。 Decide widget name, if is ajax request, then do special process. */
if(empty($wgName))
{
$wgName = 'page';
if(isAjaxRequest('modal')) $wgName = 'modalDialog';
else if(isAjaxRequest() && !isAjaxRequest('zin')) $wgName = 'fragment';
}
/* 判断是否渲染为完整页面。 Check if render in full page. */
$isFullPage = str_starts_with($wgName, 'page');
if($isFullPage) $globalItems[] = set::display(false);
/* 获取部件渲染选项。 Get widget display options. */
if(empty($options) && isset($_SERVER['HTTP_X_ZIN_OPTIONS']) && !empty($_SERVER['HTTP_X_ZIN_OPTIONS']))
{
$setting = $_SERVER['HTTP_X_ZIN_OPTIONS'];
$options = $setting[0] === '{' ? json_decode($setting, true) : array('selector' => $setting);
}
/* 创建部件实例。 Create widget instance. */
$wg = createWg($wgName, $globalItems);
/* 如果不是渲染一个完整页面,则使用 fragment 进行渲染。 If not render in full page, then render all items in a fragment. */
if(!$isFullPage && $wgName !== 'fragment') $wg = fragment($wg);
/* 渲染并输出 HTML。 Render and display html. */
$wg->display($options);
zin::$rendered = true;
}
+165
View File
@@ -0,0 +1,165 @@
<?php
declare(strict_types=1);
/**
* The selector helpers file of zin of ZenTaoPMS.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin;
/**
* Parse wg selector
* @param string|object $selector
* @return object|null
*/
function parseWgSelector(string|object $selector): ?object
{
if(is_object($selector)) return $selector;
$selector = trim($selector);
$len = strlen($selector);
if($len < 1) return null;
$result = array(
'class' => array(),
'id' => null,
'tag' => null,
'inner' => false,
'name' => null,
'first' => false,
'selector' => $selector
);
if(str_contains($selector, '/'))
{
$parts = explode('/', $selector, 2);
$result['name'] = $parts[0];
$selector = $parts[1];
$len = strlen($selector);
}
$selector = str_replace('> *', '>*', $selector);
if(substr($selector, strlen($selector) - 2) == '>*')
{
$result['inner'] = true;
$selector = substr($selector, 0, strlen($selector) - 2);
$len = strlen($selector);
}
$type = 'tag';
$current = '';
$updateResult = function(&$result, $current, $type)
{
if(empty($current)) return;
if($type === 'class')
{
$result[$type][] = $current;
}
elseif($type === 'option')
{
$options = [];
parse_str($current, $options);
foreach($options as $key => $value) $result[$key] = empty($value) ? true : $value;
}
else
{
$result[$type] = $current;
}
};
for($i = 0; $i < $len; $i++)
{
$c = $selector[$i];
$t = '';
if($c === '#' & $type !== 'option')
{
$t = 'id';
}
elseif($c === '.' & $type !== 'option')
{
$t = 'class';
}
elseif($c === '(' && $type !== 'option' && str_ends_with($selector, ')'))
{
$command = substr($selector, $i + 1, -1);
if(empty($command)) $command = $current;
$result['command'] = $command;
break;
}
elseif($c === ':')
{
$t = 'option';
}
if(empty($t))
{
$current .= $c;
}
else
{
$updateResult($result, $current, $type);
$current = '';
$type = $t;
}
}
$updateResult($result, $current, $type);
if(empty($result['class'])) $result['class'] = null;
if(empty($result['name']))
{
if(!empty($result['id'])) $result['name'] = $result['id'];
elseif(!empty($result['tag'])) $result['name'] = $result['tag'];
else $result['name'] = $selector;
}
return (object)$result;
}
/**
* Parse wg selectors.
* @param object|string|object[]|string[] $selectors
* @return object[]
*/
function parseWgSelectors(object|string|array $selectors): array
{
if(is_object($selectors)) return array($selectors);
if(is_string($selectors)) $selectors = explode(',', trim($selectors));
$results = array();
foreach($selectors as $selector)
{
$selector = parseWgSelector($selector);
if(is_object($selector)) $results[] = $selector;
}
return $results;
}
/**
* Stringify wg selectors.
* @param object|object[] $selector
* @return string
*/
function stringifyWgSelectors(array|object|null $selector): string
{
if(empty($selector)) return '';
if(is_array($selector))
{
$result = [];
foreach($selector as $s) $result[] = stringifyWgSelectors($s);
return implode(',', $result);
}
$result = '';
if(!empty($selector->name) && $selector->name !== $selector->selector) $result .= $selector->name . '/';
if(!empty($selector->tag)) $result .= $selector->tag;
if(!empty($selector->id)) $result .= '#' . $selector->id;
if(!empty($selector->class)) $result .= '.' . implode('.', $selector->class);
if(!empty($selector->first)) $result .= ':first';
if($selector->inner) $result .= '>*';
return $result;
}
+40
View File
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/**
* The properties setter class file of zin lib.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin;
require_once __DIR__ . DS . 'directive.class.php';
class set
{
public static function __callStatic($prop, $args)
{
if($prop === 'class' || strtolower($prop) === 'classname')
{
global $config;
if($prop === 'class' && isset($config->debug) && $config->debug)
{
trigger_error("[ZIN] Use set::className() instead of set::class() to compatible with php 5.4.", E_USER_WARNING);
}
return directive('prop', array('class' => $args));
}
// compatible with zui prop className.
else if($prop === '_className')
{
return directive('prop', array('className' => $args));
}
$value = array_shift($args);
if(is_object($value)) $value = (array)$value;
return directive('prop', array($prop => $value));
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
/**
* The block setter class file of zin lib.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin;
require_once __DIR__ . DS . 'wg.func.php';
class to
{
public static function __callStatic($name, $args)
{
return to($name, $args);
}
}
+756
View File
@@ -0,0 +1,756 @@
<?php
declare(strict_types=1);
/**
* The base widget class file of zin of ZenTaoPMS.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin;
require_once __DIR__ . DS . 'props.class.php';
require_once __DIR__ . DS . 'directive.class.php';
require_once __DIR__ . DS . 'zin.class.php';
require_once __DIR__ . DS . 'context.class.php';
require_once __DIR__ . DS . 'selector.func.php';
require_once __DIR__ . DS . 'dom.class.php';
class wg
{
/**
* Define props for the element
*
* @var array
*/
protected static array $defineProps = array();
protected static array $defaultProps = array();
protected static array $defineBlocks = array();
protected static array $wgToBlockMap = array();
protected static array $definedPropsMap = array();
protected static array $pageResources = array();
/**
* The props of the element
*
* @access public
* @var props
*/
public props $props;
public array $blocks = array();
public ?wg $parent = null;
public string $gid;
public bool $displayed = false;
protected array $renderOptions = array();
public function __construct(/* string|element|object|array|null ...$args */)
{
$this->props = new props();
$this->gid = 'zin_' . uniqid();
$this->setDefaultProps(static::getDefaultProps());
$this->add(func_get_args());
$this->created();
zin::renderInGlobal($this);
static::checkPageResources();
$this->checkErrors();
}
public function __debugInfo(): array
{
return $this->toJSON();
}
public function isDomElement(): bool
{
return false;
}
/**
* Check if the element is match any of the selectors
* @param string|array|object $selectors
*/
public function isMatch(string|array|object $selectors): bool
{
$list = parseWgSelectors($selectors);
foreach($list as $selector)
{
if(isset($selector->command)) continue;
if(!empty($selector->id) && $this->id() !== $selector->id) continue;
if(!empty($selector->tag) && $this->shortType() !== $selector->tag) continue;
if(!empty($selector->class) && !$this->props->class->has($selector->class)) continue;
return true;
}
return false;
}
/**
* Build dom object
* @return dom
*/
public function buildDom(): dom
{
$before = $this->buildBefore();
$children = $this->build();
$after = $this->buildAfter();
$options = $this->renderOptions;
$selectors = (!empty($options) && isset($options['selector'])) ? $options['selector'] : null;
return new dom
(
$this,
[$before, $children, $after],
$selectors,
(!empty($options) && isset($options['type'])) ? $options['type'] : 'html', // TODO: () may not work in lower php
(!empty($options) && isset($options['data'])) ? $options['data'] : null,
);
}
/**
* Render widget to html
* @return string
*/
public function render(): string
{
$dom = $this->buildDom();
$result = $dom->render();
return is_string($result) ? $result : json_encode($result);
}
public function display(array $options = array()): wg
{
zin::disableGlobalRender();
$this->renderOptions = $options;
$dom = $this->buildDom();
$result = $dom->render();
$context = context::current();
$css = $context->getCSS();
$js = $context->getJS();
global $app, $config;
$zinDebug = null;
if($config->debug && (!isAjaxRequest() || isAjaxRequest('zin')))
{
$zinDebug = data('zinDebug');
if(is_array($zinDebug))
{
$zinDebug['basePath'] = $app->getBasePath();
if(isset($app->zinErrors)) $zinDebug['errors'] = $app->zinErrors;
}
}
$rawContent = ob_get_contents();
if(!is_string($rawContent)) $rawContent = '';
ob_end_clean();
if(is_object($result))
{
if($zinDebug && isset($result['zinDebug'])) $result['zinDebug'] = $zinDebug;
$result = json_encode($result);
}
elseif(is_array($result))
{
foreach($result as $index => $item)
{
if($item['name'] === 'zinDebug' && $zinDebug)
{
$result[$index]['data'] = $zinDebug;
continue;
}
if(!isset($item['type']) || $item['type'] !== 'html') continue;
$data = $item['data'];
$data = str_replace('/*{{ZIN_PAGE_CSS}}*/', $css, $data);
$data = str_replace('/*{{ZIN_PAGE_JS}}*/', $js, $data);
$data = str_replace('<!-- {{RAW_CONTENT}} -->', $rawContent, $data);
$result[$index]['data'] = $data;
}
$result = json_encode($result);
}
else
{
if($zinDebug) $js .= h::createJsVarCode('window.zinDebug', $zinDebug);
$result = str_replace('/*{{ZIN_PAGE_CSS}}*/', $css, $result);
$result = str_replace('/*{{ZIN_PAGE_JS}}*/', $js, $result);
$result = str_replace('<!-- {{RAW_CONTENT}} -->', $rawContent, $result);
}
ob_start();
echo $result;
$this->displayed = true;
context::destroy();
return $this;
}
protected function created() {}
protected function buildBefore(): array
{
return $this->block('before');
}
protected function buildAfter(): array
{
return $this->block('after');
}
protected function build(): array|wg|directive
{
return $this->children();
}
public function buildEvents(): ?string
{
$events = $this->props->events();
if(empty($events)) return null;
$id = $this->id();
$code = array($this->shortType() === 'html' ? 'const ele = document;' : 'const ele = document.getElementById("' . (empty($id) ? $this->gid : $id) . '");if(!ele)return;const $ele = $(ele); const events = new Set(($ele.attr("data-zin-events") || "").split(" ").filter(Boolean));');
foreach($events as $event => $bindingList)
{
$code[] = "\$ele.on('$event.on.zin', function(e){";
foreach($bindingList as $binding)
{
if(is_string($binding)) $binding = (object)array('handler' => $binding);
$selector = isset($binding->selector) ? $binding->selector : null;
$handler = isset($binding->handler) ? trim($binding->handler) : '';
$stop = isset($binding->stop) ? $binding->stop : null;
$prevent = isset($binding->prevent) ? $binding->prevent : null;
$self = isset($binding->self) ? $binding->self : null;
$code[] = '(function(){';
if($selector) $code[] = "const target = e.target.closest('$selector');if(!target) return;";
else $code[] = "const target = ele;";
if($self) $code[] = "if(ele !== e.target) return;";
if($stop) $code[] = "e.stopPropagation();";
if($prevent) $code[] = "e.preventDefault();";
if(preg_match('/^[$A-Z_][0-9A-Z_$\[\]."\']*$/i', $handler)) $code[] = "($handler).call(target,e);";
else $code[] = $handler;
$code[] = '})();';
}
$code[] = "});events.add('$event');";
}
$code[] = '$ele.attr("data-zin-events", Array.from(events).join(" "));';
return h::createJsScopeCode($code);
}
protected function onAddBlock(array|string|wg|directive $child, string $name)
{
return $child;
}
protected function onAddChild(array|string|wg|directive $child)
{
return $child;
}
protected function onSetProp(array|string $prop, mixed $value)
{
if($prop === 'id' && $value === '$GID') $value = $this->gid;
if($prop[0] === '@')
{
$this->setDefaultProps(array('id' => $this->gid));
context::current()->addWgWithEvents($this);
}
$this->props->set($prop, $value);
}
protected function onGetProp(string $prop, mixed $defaultValue): mixed
{
return $this->props->get($prop, $defaultValue);
}
public function add($item, string $blockName = 'children')
{
if($item === null || is_bool($item)) return $this;
if(is_array($item))
{
foreach($item as $child) $this->add($child, $blockName);
return $this;
}
zin::disableGlobalRender();
if($item instanceof wg) $this->addToBlock($blockName, $item);
elseif(is_string($item)) $this->addToBlock($blockName, htmlentities($item));
elseif(isDirective($item)) $this->directive($item, $blockName);
else $this->addToBlock($blockName, htmlentities(strval($item)));
zin::enableGlobalRender();
return $this;
}
public function addToBlock(array|string $name, array|string|null|wg|directive $child = null)
{
if(is_array($name))
{
foreach($name as $blockName => $blockChildren)
{
$this->addToBlock($blockName, $blockChildren);
}
return;
}
if(is_array($child))
{
foreach($child as $blockChild)
{
$this->addToBlock($name, $blockChild);
}
return;
}
if($child instanceof wg && empty($child->parent)) $child->parent = &$this;
if($name === 'children' && $child instanceof wg)
{
$blockName = static::getBlockNameForWg($child);
if($blockName !== null) $name = $blockName;
}
$result = $name === 'children' ? $this->onAddChild($child) : $this->onAddBlock($child, $name);
if($result === false) return;
if($result !== null && $result !== true) $child = $result;
if(isset($this->blocks[$name])) $this->blocks[$name][] = $child;
else $this->blocks[$name] = array($child);
}
public function children(): array
{
return $this->block('children');
}
public function block(string $name): array
{
$list = array();
if(isset($this->blocks[$name]))
{
$blocks = $this->blocks[$name];
foreach($blocks as $block)
{
$isWg = $block instanceof wg && $block->shortType() === 'wg';
$block = $isWg ? $block->children() : $block;
if(is_array($block)) $list = array_merge($list, $block);
else $list[] = $block;
}
}
return $list;
}
public function hasBlock(string $name): bool
{
return isset($this->blocks[$name]);
}
/**
* Apply directive
*/
public function directive(directive &$directive, array|string $blockName)
{
$data = $directive->data;
$type = $directive->type;
$directive->parent = &$this;
if($type === 'prop')
{
$this->setProp($data);
return;
}
if($type === 'class' || $type === 'style')
{
$this->setProp($type, $data);
return;
}
if($type === 'cssVar')
{
$this->setProp('--', $data);
return;
}
if($type === 'html')
{
$this->addToBlock($blockName, $directive);
return;
}
if($type === 'text')
{
$this->addToBlock($blockName, htmlspecialchars($data));
return;
}
if($type === 'block')
{
foreach($data as $blockName => $blockChildren)
{
$this->add($blockChildren, $blockName);
}
}
}
public function prop(array|string $name, mixed $defaultValue = null): mixed
{
if(is_array($name))
{
$values = array();
foreach($name as $index => $propName)
{
$values[] = $this->onGetProp($propName, is_array($defaultValue) ? (isset($defaultValue[$propName]) ? $defaultValue[$propName] : $defaultValue[$index]) : $defaultValue);
}
return $values;
}
return $this->onGetProp($name, $defaultValue);
}
/**
* Set property, an array can be passed to set multiple properties
*
* @access public
* @param props|array|string $prop - Property name or properties list
* @param mixed $value - Property value
*/
public function setProp(props|array|string $prop, mixed $value = null)
{
if($prop instanceof props) $prop = $prop->toJSON();
if(is_array($prop))
{
foreach($prop as $name => $value) $this->setProp($name, $value);
return $this;
}
if(!is_string($prop) || empty($prop)) return $this;
if($prop[0] === '#')
{
$this->add($value, substr($prop, 1));
return $this;
}
$this->onSetProp($prop, $value);
return $this;
}
public function hasProp(): bool
{
$names = func_get_args();
if(empty($names)) return false;
foreach($names as $name)
{
if(!$this->props->has($name)) return false;
}
return true;
}
public function setDefaultProps(array $props)
{
if(!is_array($props) || empty($props)) return;
foreach($props as $name => $value)
{
if($this->props->has($name)) continue;
$this->setProp($name, $value);
}
}
public function getRestProps(): array
{
return $this->props->skip(array_keys(static::definedPropsList()));
}
public function getDefinedProps(): array
{
return $this->props->pick(array_keys(static::definedPropsList()));
}
public function type(): string
{
return get_called_class();
}
public function shortType(): string
{
$type = $this->type();
$pos = strrpos($type, '\\');
return $pos === false ? $type : substr($type, $pos + 1);
}
public function id(): ?string
{
return $this->prop('id');
}
public function toJSON(): array
{
$data = array();
$data['gid'] = $this->gid;
$data['props'] = $this->props->toJSON();
$data['type'] = $this->type();
if(str_starts_with($data['type'], 'zin\\')) $data['type'] = substr($data['type'], 4);
$data['blocks'] = array();
foreach($this->blocks as $key => $value)
{
foreach($value as $index => $child)
{
if($child instanceof wg || (is_object($child) && method_exists($child, 'toJSON')))
{
$value[$index] = $child->toJSON();
}
elseif(isDirective($child, 'html'))
{
$value[$index] = $child->data;
}
}
if($key === 'children')
{
unset($data['blocks'][$key]);
$data['children'] = $value;
}
else
{
$data['blocks'][$key] = $value;
}
}
if(empty($data['blocks'])) unset($data['blocks']);
if(!empty($this->parent)) $data['parent'] = $this->parent->gid;
return $data;
}
/**
* Check errors in debug mode.
*
* @access protected
* @return void
*/
protected function checkErrors()
{
global $config;
if(!isset($config->debug) || !$config->debug) return;
$definedProps = static::definedPropsList();
foreach($definedProps as $name => $definition)
{
if($this->hasProp($name)) continue;
if(isset($definition['default']) && $definition['default'] !== null) continue;
if(isset($definition['optional']) && $definition['optional']) continue;
trigger_error("[ZIN] The property \"$name: {$definition['type']}\" of widget \"{$this->type()}#$this->gid\" is required.", E_USER_ERROR);
}
$wgErrors = $this->onCheckErrors();
if(empty($wgErrors)) return;
foreach($wgErrors as $error)
{
if(is_array($error)) trigger_error("[ZIN] $error[0]", count($error) > 1 ? $error[1] : E_USER_WARNING);
else trigger_error("[ZIN] $error", E_USER_ERROR);
}
}
/**
* The lifecycle method for checking errors in debug mode.
*
* @access protected
* @return array|null
*/
protected function onCheckErrors(): array|null
{
return null;
}
public static function getPageCSS(): string|false
{
return false; // No css
}
public static function getPageJS(): string|false
{
return false; // No js
}
protected static function checkPageResources()
{
$name = get_called_class();
if(isset(static::$pageResources[$name])) return;
static::$pageResources[$name] = true;
$pageCSS = static::getPageCSS();
$pageJS = static::getPageJS();
if(!empty($pageCSS)) context::css($pageCSS);
if(!empty($pageJS)) context::js($pageJS);
}
public static function wgBlockMap(): array
{
$wgName = get_called_class();
if(!isset(wg::$wgToBlockMap[$wgName]))
{
$wgBlockMap = array();
if(!empty(static::$defineBlocks))
{
foreach(static::$defineBlocks as $blockName => $setting)
{
if(!isset($setting['map'])) continue;
$map = $setting['map'];
if(is_string($map)) $map = explode(',', $map);
foreach($map as $name) $wgBlockMap[$name] = $blockName;
}
}
wg::$wgToBlockMap[$wgName] = $wgBlockMap;
}
return wg::$wgToBlockMap[$wgName];
}
public static function getBlockNameForWg(wg|string $wg): ?string
{
$wgType = ($wg instanceof wg) ? $wg->type() : $wg;
$wgBlockMap = static::wgBlockMap();
if(str_starts_with($wgType, 'zin\\')) $wgType = substr($wgType, 4);
return isset($wgBlockMap[$wgType]) ? $wgBlockMap[$wgType] : null;
}
protected static function definedPropsList(?string $wgName = null): array
{
if($wgName === null) $wgName = get_called_class();
if(!isset(wg::$definedPropsMap[$wgName]) && $wgName === get_called_class())
{
wg::$definedPropsMap[$wgName] = static::parsePropsDefinition(static::$defineProps);
}
return wg::$definedPropsMap[$wgName];
}
protected static function getDefaultProps(?string $wgName = null): array
{
$defaultProps = array();
foreach(static::definedPropsList($wgName) as $name => $definition)
{
if(!isset($definition['default'])) continue;
$defaultProps[$name] = $definition['default'];
}
return $defaultProps;
}
/**
* Parse props definition
* @param $definition
* @example
*
* $definition = array('name', 'desc:string', 'title?:string|element', 'icon?:string="star"');
* $definition = array('name' => 'mixed', 'desc' => 'string', 'title' => array('type' => 'string|element', 'optional' => true), 'icon' => array('type' => 'string', 'default' => 'star', 'optional' => true))))
*/
private static function parsePropsDefinition(array $definition): array
{
$parentClass = get_parent_class(get_called_class());
/**
* @var array
*/
$props = $parentClass ? call_user_func("$parentClass::definedPropsList") : array();
if($parentClass !== false && $definition === $parentClass::$defineProps)
{
if(!empty(static::$defaultProps) && static::$defaultProps !== $parentClass::$defaultProps)
{
foreach($props as $name => $value)
{
if(isset(static::$defaultProps[$name]))
{
$value['default'] = static::$defaultProps[$name];
$props[$name] = $value;
}
}
}
return $props;
}
foreach($definition as $name => $value)
{
$optional = false;
$type = 'mixed';
$default = (isset($props[$name]) && isset($props[$name]['default'])) ? $props[$name]['default'] : null;
if(is_int($name) && is_string($value))
{
$value = trim($value);
if(!str_contains($value, ':'))
{
$name = $value;
$value = '';
}
else
{
list($name, $value) = explode(':', $value, 2);
}
$name = trim($name);
if($name[strlen($name) - 1] === '?')
{
$name = substr($name, 0, strlen($name) - 1);
$optional = true;
}
}
if(is_array($value))
{
$type = isset($value['type']) ? $value['type'] : $type;
$default = isset($value['default']) ? $value['default'] : $default;
$optional = isset($value['optional'])? $value['optional']: $optional;
}
else if(is_string($value))
{
if(!str_contains($value, '='))
{
$type = $value;
$default = null;
}
else
{
list($type, $default) = explode('=', $value, 2);
}
$type = trim($type);
if(is_string($default)) $default = json_decode(trim($default));
}
$props[$name] = array('type' => empty($type) ? 'mixed' : $type, 'default' => $default, 'optional' => $default !== null || $optional);
}
if(static::$defaultProps && (!$parentClass || static::$defaultProps !== $parentClass::$defaultProps))
{
foreach(static::$defaultProps as $name => $value)
{
if(!isset($props[$name])) continue;
$props[$name]['default'] = $value;
}
}
return $props;
}
}
+305
View File
@@ -0,0 +1,305 @@
<?php
declare(strict_types=1);
/**
* The widget function file of zin module of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author sunhao<sunhao@easycorp.ltd>
* @package zin
* @link http://www.zentao.net
*/
namespace zin;
require_once dirname(__DIR__) . DS . 'utils' . DS . 'flat.func.php';
require_once __DIR__ . DS . 'props.class.php';
require_once __DIR__ . DS . 'directive.class.php';
require_once __DIR__ . DS . 'rawcontent.class.php';
require_once __DIR__ . DS . 'wg.class.php';
require_once __DIR__ . DS . 'context.func.php';
/**
* Create an new widget.
*
* @return wg
*/
function wg(): wg
{
return new wg(func_get_args());
}
/**
* Set widget properties.
*
* @param string|array|props|null $name
* @param mixed $value
* @return directive|null
*/
function set(string|array|props|null $name, mixed $value = null): ?directive
{
if($name === null) return null;
$props = null;
if($name instanceof props) $props = $name;
else if(is_array($name)) $props = $name;
else if(is_object($name)) $props = (array)$name;
else if(is_string($name)) $props = array($name => $value);
return $props ? directive('prop', $props) : null;
}
/**
* Set widget CSS class attribute.
*
* @param array|string|null ...$classList
* @return directive
*/
function setClass(/* array|string|null ...$classList */): directive
{
return directive('class', func_get_args());
}
/**
* Set widget style attribute.
*
* @return directive
*/
function setStyle(array|string $name, ?string $value = null): directive
{
return directive('style', is_array($name) ? $name : array($name => $value));
}
/**
* Set widget CSS variable.
*
* @return directive
*/
function setCssVar(array|string $name, ?string $value = null): directive
{
return directive('cssVar', is_array($name) ? $name : array($name => $value));
}
/**
* Set widget ID attribute.
*
* @return ?directive
*/
function setID(?string $id = null): directive
{
return set('id', $id);
}
/**
* Set widget element tag name.
*
* @return directive
*/
function setTag(string $id): directive
{
return set('tagName', $id);
}
/**
* Set widget data-* attribute.
*
* @param string|array $name
* @param mixed $value
* @return directive
*/
function setData(string|array $name, mixed $value = null): directive
{
$map = is_array($name) ? $name : array($name => $value);
$attrs = array();
foreach($map as $key => $value)
{
$name = "data-$key";
if(is_bool($value)) $attrs[$name] = $value ? 'true' : 'false';
else if(is_array($value)) $attrs[$name] = json_encode($value);
else $attrs[$name] = $value;
}
return set($attrs);
}
/**
* Add event listener to widget element.
*
* @param string $name
* @param bool|string|array $handler
* @param array $options
*/
function on(string $name, bool|string|array $handler, array|string|bool $options = null): directive
{
if(is_string($options) && is_string($handler))
{
$options = array('selector' => $handler, 'handler' => $options);
}
elseif(is_bool($options))
{
$options = array('capture' => $options, 'handler' => $handler);
}
elseif(is_array($options))
{
$options['handler'] = $handler;
}
else
{
$options = array('handler' => $handler);
}
if(str_contains($name, '__'))
{
list($name, $flags) = explode('__', $name);
if(str_contains($flags, 'capture')) $options['capture'] = true;
if(str_contains($flags, 'stop')) $options['stop'] = true;
if(str_contains($flags, 'prevent')) $options['prevent'] = true;
if(str_contains($flags, 'self')) $options['self'] = true;
}
return set("@$name", (object)$options);
}
/**
* Create html content.
*
* @param string ...$lines
* @return directive
*/
function html(/* string ...$lines */): directive
{
return directive('html', implode("\n", \zin\utils\flat(func_get_args())));
}
/**
* Create text content.
*
* @param string ...$lines
* @return directive
*/
function text(/* string ...$lines */): directive
{
return directive('text', implode("\n", \zin\utils\flat(func_get_args())));
}
/**
* Create block content.
*
* @param string $name
* @param mixed ...$wgs
* @return directive
*/
function to(/* string $name, mixed ...$wgs */): directive
{
$args = func_get_args();
$name = array_shift($args);
$wg = new wg(count($args) > 1 ? $args : $args[0]);
return directive('block', array($name => $wg));
}
/**
* Create content for block "before".
*
* @param string $wgs
* @return directive
*/
function before(/* mixed ...$wgs */): directive
{
return to('before', func_get_args());
}
/**
* Create content for block "after".
*
* @param string $wgs
* @return directive
*/
function after(): directive
{
return to('after', func_get_args());
}
/**
* Create widget contents inherited from the given widget.
*
* @param wg|array $item
* @return array
*/
function inherit(wg|array $item): array
{
if(!($item instanceof wg)) $item = new wg($item);
return array(set($item->props), directive('block', $item->blocks), $item->children());
}
/**
* Divorce widget from parent.
*
* @param wg|array $item
* @return array
*/
function divorce(wg|array $item): wg|array
{
if($item instanceof wg)
{
$item->parent = null;
}
else if(is_array($item))
{
foreach($item as $i) divorce($i);
}
return $item;
}
/**
* Check if the given widget list has the given widget type.
*
* @param wg|array $items
* @param string $type
* @return bool
*/
function hasWgInList(wg|array $items, string $type): bool
{
if(!is_array($items)) $items = array($items);
foreach($items as $item)
{
if($item instanceof wg && $item->type() == $type) return true;
}
return false;
}
/**
* Group widgets by type.
*
* @param wg|array $items
* @param string $types
* @return array
*/
function groupWgInList(wg|array $items, string|array $types): array
{
if(is_string($types)) $types = explode(',', $types);
$typesMap = array();
$restList = array();
foreach($types as $type) $typesMap[$type] = array();
foreach($items as $item)
{
if(!($item instanceof wg)) continue;
$type = $item->shortType();
if(isset($typesMap[$type])) $typesMap[$type][] = $item;
else $restList[] = $item;
}
$groups = array();
foreach($types as $index => $type) $groups[] = $typesMap[$type];
$groups[] = $restList;
return $groups;
}
/**
* Create raw content placeholder.
*
* @return rawContent
*/
function rawContent(): rawContent
{
zin::$rawContentCalled = true;
return new rawContent();
}
+76
View File
@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
/**
* The zin class file of zin of ZenTaoPMS.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin;
require_once dirname(__DIR__) . DS . 'utils' . DS . 'deep.func.php';
class zin
{
public static array $globalRenderList = array();
public static bool $enabledGlobalRender = true;
public static array $data = array();
public static bool $rendered = false;
public static bool $rawContentCalled = false;
public static function getData(string $namePath, mixed $defaultValue = null): mixed
{
return \zin\utils\deepGet(static::$data, $namePath, $defaultValue);
}
public static function setData(string $namePath, mixed $value)
{
\zin\utils\deepSet(static::$data, $namePath, $value);
}
public static function enableGlobalRender()
{
static::$enabledGlobalRender = true;
}
public static function disableGlobalRender()
{
static::$enabledGlobalRender = false;
}
public static function renderInGlobal(): bool
{
if(!static::$enabledGlobalRender) return false;
static::$globalRenderList = array_merge(static::$globalRenderList, func_get_args());
return true;
}
public static function getGlobalRenderList(bool $clear = true): array
{
$globalItems = array();
foreach(static::$globalRenderList as $item)
{
if(is_object($item))
{
if((isset($item->parent) && $item->parent) || ($item instanceof wg && $item->shortType() === 'wg'))
continue;
}
$globalItems[] = $item;
}
/* Clear globalRenderList. */
if($clear) static::$globalRenderList = array();
return $globalItems;
}
}
+1759
View File
File diff suppressed because it is too large Load Diff
+131
View File
@@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
/**
* The helper methods file of zin of ZenTaoPMS.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin;
require_once __DIR__ . DS . 'config.php';
function setWgVer($ver, $names = null)
{
global $config;
$zinConfig = $config->zin;
if(is_string($names)) $names = explode(',', $names);
if(!is_array($names)) return;
foreach($names as $name)
{
$name = trim($name);
if(!empty($name)) continue;
$zinConfig->wgVerMap[$name] = $ver;
}
}
function getWgVer($name)
{
global $config;
return isset($config->zin->verMap[$name]) ? $config->zin->verMap[$name] : $config->zin->wgVer;
}
function createWg($name, $args): wg
{
$name = strtolower($name);
$wgVer = getWgVer($name);
include_once __DIR__ . DS . 'wg' . DS . $name . DS . "v$wgVer.php";
$wgName = "\\zin\\$name";
return class_exists($wgName) ? (new $wgName($args)) : $wgName($args);
}
if(!function_exists('str_contains'))
{
/**
* Determine if a string contains a given substring
*
* @param string $haystack
* @param string $needle
* @return bool
*/
function str_contains($haystack, $needle)
{
return strpos($haystack, $needle) !== false;
}
}
else
{
function str_contains($haystack, $needle)
{
return \str_contains($haystack, $needle);
}
}
if(!function_exists('str_starts_with'))
{
/**
* Checks if a string starts with a given substring
*
* @param string $haystack
* @param string $needle
* @return bool
*/
function str_starts_with($haystack, $needle)
{
return strpos($haystack, $needle) === 0;
}
}
else
{
function str_starts_with($haystack, $needle)
{
return \str_starts_with($haystack, $needle);
}
}
if(!function_exists('str_ends_with'))
{
/**
* Checks if a string starts with a given substring.
*
* @param string $haystack
* @param string $needle
* @return bool
*/
function str_ends_with($haystack, $needle)
{
$length = strlen($needle);
if ($length === 0) return true;
$position = strpos($haystack, $needle);
return $position !== false && $position === strlen($haystack) - $length;
}
}
else
{
function str_ends_with($haystack, $needle)
{
return \str_ends_with($haystack, $needle);
}
}
function uncamelize(string $camelCaps, string $separator = '-'): string
{
return strtolower(preg_replace('/([a-z])([A-Z])/', "$1" . $separator . "$2", $camelCaps));
}
function isHTML(string $string): bool
{
return $string !== strip_tags($string) ? true : false;
}
+252
View File
@@ -0,0 +1,252 @@
<?php
declare(strict_types=1);
/**
* The classlist file of zin of ZenTaoPMS.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin\utils;
/**
* Manage classname list for html element and widgets
*/
class classlist
{
/**
* Store classname list, key => value
*
* @access private
* @var array
*/
private array $list = array();
/**
* Create classname instance
*
* @access public
* @param array ...$list - A string or a class name list
*/
public function __construct(/* ...$list */)
{
$list = func_get_args();
if(!empty($list)) $this->set($list);
}
/**
* Convert classnames to string
*
* @access public
* @return string
*/
public function __toString(): string
{
return $this->toStr();
}
/**
* Create classname instance
*
* Example:
*
* // Set class names
* $classlist = new classlist();
* $classlist->set('btn primary rounded');
*
* // Set multiple classnames by string list
* $classlist->set(array('btn', 'primary', 'rounded'));
*
* // Set multiple classnames by a mapped array
* $classlist->set(array('btn' => true, 'primary' => true, 'rounded' => $isRounded));
*
* @access public
* @param string|array|null $list - A string or a class name list
* @param bool $reset
* @return classlist
*/
public function set(string|array|null $list, bool $reset = false): classlist
{
if(is_string($list)) $list = explode(' ', $list);
if(is_array($list))
{
if($reset) $this->list = array();
$expectedKey = 0;
foreach($list as $index => $value)
{
if(is_array($value))
{
$this->set($value);
continue;
}
/* If $index is expected numberic key and the $value is string, then use the $value as the name */
if($expectedKey === $index && is_string($value))
{
$value = trim($value);
if(strlen($value) > 0) $this->list[$value] = true;
}
/* If index is string, then set $index as name */
else if(is_string($index))
{
$index = trim($index);
if(strlen($index) === 0) continue;
$this->list[$index] = boolval($value);
}
$expectedKey++;
}
}
return $this;
}
/**
* Add classnames
*
* Example:
*
* $classlist = new classlist();
* $classlist->add('btn primary rounded');
*
* // Add multiple classnames by string list
* $classlist->add('btn', 'primary', 'rounded');
*
* @access public
* @param array ...$list - classname string joined by space or string array
* @return classlist
*/
public function add(/* ...$list */)
{
return $this->set(func_get_args());
}
/**
* Remove classnames
*
* Example:
*
* $classlist = new classlist('btn primary rounded');
* $classlist->remove('btn primary');
*
* // Add multiple classnames by string list
* $classlist->remove('btn', 'primary');
*
* @access public
* @param array|string $list - classname string joined by space or string array
* @return classlist
*/
public function remove(array|string $list): classlist
{
if(is_string($list)) $list = explode(' ', $list);
foreach($list as $name)
{
if(!is_string($name)) continue;
$name = trim($name);
if(!strlen($name)) continue;
$this->list[$name] = false;
}
return $this;
}
/**
* Toggle classname
*
* Example:
*
* $classlist = new classlist('btn');
* $classlist->toggle('btn'); // class list is ""
*
* // Toggle class name by flag
* $classlist->toggle('primary', true); // class list is "primary"
*
* @access public
* @param string $name - classname string
* @return classlist
*/
public function toggle(string $name, bool|null $toggle = null): classlist
{
$name = trim($name);
if(strlen($name))
{
if($toggle === null) $toggle = !$this->has($name);
$this->list[$name] = $toggle;
}
return $this;
}
/**
* Check whether has specific class name
*
* Example:
*
* $classlist = new classlist('btn primary rounded');
* echo $classlist->has('btn'); // Output true
*
* // Check multiple names
* echo $classlist->has('btn primary'); // Output true
*/
public function has(array|string $list): bool
{
if(is_string($list)) $list = explode(' ', $list);
foreach($list as $name)
{
if(!is_string($name)) continue;
$name = trim($name);
if(!strlen($name)) continue;
if(!isset($this->list[$name]) || !$this->list[$name]) return false;
}
return true;
}
public function clear()
{
$this->list = array();
}
/**
* Convert classnames to string
*
* @access public
* @return string
*/
public function toStr(): string
{
$names = array();
foreach($this->list as $name => $toggle)
{
if(!$toggle) continue;
$name = trim($name);
if(!strlen($name)) continue;
$names[] = $name;
}
return implode(' ', $names);
}
/**
* Get class names count
*
* @access public
* @return int
*/
public function count(): int
{
return count($this->list);
}
public function toJSON(): array
{
return $this->list;
}
}
+196
View File
@@ -0,0 +1,196 @@
<?php
declare(strict_types=1);
/**
* The dataset class file of zin of ZenTaoPMS.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin\utils;
/**
* Manage dataset properties for html element and widgets
*/
class dataset
{
/**
* Store dataset properties list in an array
*
* @var array
* @access protected
*/
protected array $data = array();
/**
* Create an instance, the initialed data can be passed
*
* @access public
* @param array $data - Properties list array
*/
public function __construct(array $data = array())
{
if($data !== null) $this->set($data);
}
/**
* Convert dataset to json string
*
* @access public
* @return string
*/
public function __toString(): string
{
return $this->toStr();
}
/**
* Method for sub class to modify value on setting it
*
* @access protected
* @param string $prop - Property name or properties list
* @param mixed $value - Property value
* @return dataset
*/
protected function setVal(string $prop, mixed $value): dataset
{
$this->data[$prop] = $value;
return $this;
}
protected function getVal(string $prop): mixed
{
return isset($this->data[$prop]) ? $this->data[$prop] : null;
}
/**
* Get properties count
*
* @access public
* @param bool $skipEmpty - Whether to skip to count empty value
* @return int
*/
public function count($skipEmpty = false): int
{
if(!$skipEmpty) return count($this->data);
$count = 0;
foreach($this->data as $value)
{
if($value !== null) $count++;
}
return $count;
}
/**
* Convert dataset to json string
*
* @access public
* @return string
*/
public function toStr(): string
{
return json_encode($this->toJSON());
}
public function toJSON(): array
{
return $this->data;
}
/**
* Set property, an array can be passed to set multiple properties
*
* @access public
* @param array|string $prop - Property name or properties list
* @param mixed $value - Property value
* @return dataset
*/
public function set(array|string $prop, mixed $value = null): dataset
{
if(is_array($prop))
{
foreach($prop as $name => $val) $this->set($name, $val);
return $this;
}
return $this->setVal($prop, $value);
}
/**
* Get property value by name
*
* @access public
* @param string $prop - Property name
* @param mixed $defaultValue - Optional default value if actual value is null
* @return mixed
*/
public function get($prop, $defaultValue = null)
{
$val = $this->getVal($prop);
return $val === null ? $defaultValue : $val;
}
public function addToList($prop, $values)
{
if(!is_array($values)) $values = array($values);
$list = $this->getList($prop);
$this->set($prop, array_merge($list, $values));
}
public function getList($prop)
{
return $this->get($prop, array());
}
/**
* Delete property by name
*
* @access public
* @param string $prop - Property name
* @return dataset
*/
public function remove($prop)
{
return $this->setVal($prop, null);
}
public function clear()
{
$this->data = array();
}
/**
* Check whether has specified property
*
* @access public
* @param string $prop - Property name
* @return boolean
*/
public function has($prop)
{
return $this->getVal($prop) !== null;
}
/**
* Clone a new instance
*
* @access public
* @return object
*/
public function copy()
{
$className = get_called_class();
return new $className($this->data);
}
public function merge($data)
{
if(is_object($data) && isset($data->data)) return $this->set($data->data);
return $this->set($data);
}
}
+45
View File
@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
/**
* The debug helpers file of zin of ZenTaoPMS.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin\utils;
$logs = array();
function log($type, $msg = null, $file)
{
global $config, $logs;
if(!$config->debug) return;
if($msg === null)
{
$msg = $type;
$type = 'i';
}
if(is_array($msg))
{
$msgLines = array();
foreach($msg as $m) $msgLines[] = strval($m);
$msg = implode(' ', $msgLines);
}
else
{
$msg = strval($msg);
}
$logs[] = array(array('type' => strtolower($type), 'msg' => $msg));
}
function logInfo($msg, $file = null) {log('i', $msg, $file);};
function logWarn($msg, $file = null) {log('w', $msg, $file);};
function logError($msg, $file = null) {log('e', $msg, $file);};
+38
View File
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace zin\utils;
function deepGet(object|array &$data, string $namePath, mixed $defaultValue = null): mixed
{
$names = explode('.', $namePath);
foreach($names as $name)
{
if(is_object($data))
{
if(!isset($data->$name)) return $defaultValue;
$data = &$data->$name;
continue;
}
if(!is_array($data) || !isset($data[$name])) return $defaultValue;
$data = &$data[$name];
}
return $data === null ? $defaultValue : $data;
}
function deepSet(array &$data, string $namePath, mixed $value)
{
$names = explode('.', $namePath);
$lastName = array_pop($names);
if(!empty($names))
{
foreach($names as $name)
{
if(!is_array($data)) return;
if(!isset($data[$name])) $data[$name] = array();
$data = &$data[$name];
}
}
$data[$lastName] = $value;
}
+20
View File
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace zin\utils;
function flat(array $array, string $prefix = '')
{
$result = array();
foreach($array as $key => $value)
{
if(is_array($value))
{
$result = array_merge($result, flat($value, "{$prefix}{$key}."));
}
else
{
$result[$prefix . $key] = $value;
}
}
return $result;
}
+140
View File
@@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
/**
* The style class file of zin of ZenTaoPMS.
*
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
* @author Hao Sun <sunhao@easycorp.ltd>
* @package zin
* @version $Id
* @link https://www.zentao.net
*/
namespace zin\utils;
require_once __DIR__ . DS . 'dataset.class.php';
/**
* Manage style for html element and widgets
*
* Example:
*
* // Create a style object an convert to css string
* $style = style::create(array('color' => 'red'));
* echo $style(); // Output "color:red"
*
* // Above example same as:
* echo style::css(array('color' => 'red'));
*
* // Modifier style
* $style = style::create(array('color' => 'red'));
* $style->set('background', 'green');
*
* // Modifier style with property name directly
* $style->background = 'green';
*
* // Get style value
* echo $style->get('background'); // Output "green"
*
* // Get style value with property name directly
* echo $style->background; // Output "green"
*
* @todo @sunhao: Validate style properties on modifying
*/
class style extends dataset
{
/**
* Format CSS variable name with prefix "--"
*
* @access public
* @param string $name - CSS variable name
* @return string
*/
public static function formatVarName(string $name): string
{
return \zin\str_starts_with($name, '--') ? $name : "--$name";
}
/**
* Set or get css variable, an array can be passed to set multiple variables
* If only pass variable name, then the variable value will be returned
* If no params passed, then return all setted variables with an array
*
* Notice: no need to prepend prefix '--' to variable name, the method will prepend it automatically, if prepended already, the method will skip to prepend smartly
*
* Example:
*
* // Create a style object and set
* $style = new style();
* $style->cssVar('text-size', '14px');
*
* // Set multiple variables
* $style->cssVar(array('text-color' => 'yellow', 'background-image' => 'none'));
*
* // Get variable value
* echo $style->cssVar('text-size'); // Output "14px"
*
* // Get all variables value
* echo $style->cssVar();
* // Output array('text-size' => '14px', 'color' => 'yellow', 'background' => 'none');
*
* // Remove variable by setting value with an empty string
* $style->cssVar('text-color', '');
*
* @access public
* @param array|string $name - Variable name or variables list
* @param string|null $value - Property value
* @return style|array|string
*/
public function cssVar(array|string $name = '', ?string $value = null): style|array|string
{
/* Support for setting multiple variables by an array */
if(is_array($name))
{
foreach($name as $n => $value) $this->set(style::formatVarName($n), $value);
return $this;
}
/* Return all setted variables without passed any params */
if(empty($name))
{
$vars = array();
foreach ($this->data as $prop => $value)
{
if(!str_starts_with($name, '--')) continue;
$vars[substr($prop, 2)] = $value;
}
return $vars;
}
$varName = style::formatVarName($name);
/* Return the specific variable value by name */
if($value === null) return $this->get($varName);
/* Set the specific variable value and return style object self */
$this->set($varName, $value === '' ? null : $value);
return $this;
}
/**
* Convert to string
*
* @access public
* @return string
*/
public function toStr(): string
{
$pairs = array();
foreach($this->data as $prop => $value)
{
/* Skip any empty value */
if($value === null || $value === '') continue;
$pairs[] = $prop . ': ' . strval($value) . ';';
}
return implode(' ', $pairs);
}
}
+139
View File
@@ -0,0 +1,139 @@
<?php
declare(strict_types=1);
namespace zin;
require_once dirname(__DIR__) . DS . 'btn' . DS . 'v1.php';
require_once dirname(__DIR__) . DS . 'dropdown' . DS . 'v1.php';
require_once dirname(__DIR__) . DS . 'checkbox' . DS . 'v1.php';
require_once dirname(__DIR__) . DS . 'btngroup' . DS . 'v1.php';
class actionItem extends wg
{
protected static array $defineProps = array(
'name:string="action"',
'type:string="item"',
'outerTag:string="li"',
'tagName:string="a"',
'icon?:string',
'text?:string',
'textClass?: string',
'url?:string',
'target?:string',
'active?:bool',
'disabled?:bool',
'trailingIcon?:string',
'outerProps?:array',
'outerClass?:string',
'badge?:string|array|object',
'props?:array',
'dropdown?:array',
'items?:array',
'caret?:bool|string'
);
protected function buildDividerItem()
{
return null;
}
protected function buildHeadingItem()
{
list($icon, $text, $trailingIcon, $textClass) = $this->prop(array('icon', 'text', 'trailingIcon', 'textClass'));
return h::div
(
set($this->props->skip(array_keys(actionItem::definedPropsList()))),
set($this->prop('props')),
$icon ? icon($icon) : null,
empty($text) ? null : span($text, setClass('text', $textClass)),
$this->children(),
$trailingIcon ? icon($trailingIcon) : null,
);
}
protected function buildDropdownItem()
{
list($dropdown, $items, $icon, $text, $trailingIcon, $active, $disabled, $badge, $props, $caret, $textClass) = $this->prop(array('dropdown', 'items', 'icon', 'text', 'trailingIcon', 'active', 'disabled', 'badge', 'props', 'caret', 'textClass'));
if(is_string($badge))
{
$badge = label($badge);
}
elseif(is_array($badge))
{
$badge = label(set($badge));
}
$dropdown = new dropdown
(
set::items($items),
set($dropdown),
h::a(
setClass(array('active' => $active, 'disabled' => $disabled)),
set($this->getRestProps()),
set($props),
$icon ? icon($icon) : null,
span($text, setClass('text', $textClass)),
$badge,
$this->children(),
$trailingIcon ? icon($trailingIcon) : null,
h::span(setClass(is_string($caret) ? "caret-$caret" : 'caret'))
)
);
return $dropdown;
}
protected function buildBtnItem()
{
return new btn($this->props->skip('tagName,type,name,outerTag,outerProps,props'), set($this->prop('props')),$this->children());
}
protected function buildCheckboxItem()
{
return new checkbox($this->props->skip('tagName,type,name,outerTag,outerProps,props'), set($this->prop('props')),$this->children());
}
protected function buildBtnGroupItem()
{
return new btnGroup($this->props->skip('tagName,type,name,outerTag,outerProps,props'), set($this->prop('props')),$this->children());
}
protected function buildItem()
{
$type = $this->prop('type');
$methodName = "build{$type}Item";
if(method_exists($this, $methodName)) return $this->$methodName();
list($tagName, $icon, $text, $trailingIcon, $url, $target, $active, $disabled, $badge, $textClass) = $this->prop(array('tagName', 'icon', 'text', 'trailingIcon', 'url', 'target', 'active', 'disabled', 'badge', 'textClass'));
if(is_string($badge)) $badge = label($badge);
else if(is_array($badge)) $badge = label(set($badge));
return h::create
(
$tagName,
set($tagName === 'a' ? array('href' => $url, 'target' => $target) : array('data-url' => $url, 'data-target' => $target)),
setClass(array('active' => $active, 'disabled' => $disabled)),
set($this->getRestProps()),
set($this->prop('props')),
$icon ? icon($icon) : null,
span($text, setClass('text', $textClass)),
$badge,
$this->children(),
$trailingIcon ? icon($trailingIcon) : null,
);
}
protected function build(): wg
{
list($name, $type, $outerTag, $outerProps, $outerClass) = $this->prop(array('name', 'type', 'outerTag', 'outerProps', 'outerClass'));
return h::create
(
$outerTag,
setClass(($type !== 'item' && $type !== 'divider') ? 'nav-item' : '', "$name-$type", $outerClass),
set($outerProps),
$this->buildItem()
);
}
}
+299
View File
@@ -0,0 +1,299 @@
<?php
declare(strict_types=1);
namespace zin;
class avatar extends wg
{
protected static array $defineProps = array(
'className?:string',
'style?:array',
'size?:int=32',
'circle?:bool=true',
'rounded?:string|int',
'background?:string',
'foreColor?:string',
'text?:string',
'code?:string',
'maxTextLength?:int=2',
'hueDistance?:int=43',
'saturation?:int=0.4',
'lightness?:int=0.6',
'src?:string'
);
private $textLen = 0;
private $displayTextLen = 0;
private $sizeMap = array('xs' => 20, 'sm' => 24, 'lg' => 48, 'xl' => 80);
private $actualSize = 32;
private $finalClass = array('avatar');
private $finalStyle;
protected function onAddChild($child)
{
if(is_string($child) && !$this->props->has('text'))
{
$this->setProp('text', $child);
return false;
}
return $child;
}
protected function build(): wg
{
/* Attach classes. */
$this->finalClass[] = $this->prop('className');
/* Init style. */
$this->finalStyle = new stdClass();
$this->finalStyle->background = $this->prop('background');
$this->finalStyle->color = $this->prop('foreColor');
foreach($this->props->style->toJSON() as $attr => $val) $this->finalStyle->{$attr} = $val;
/* Init avatar size. */
$this->initSize();
/* Init avatar shape. */
$this->initShape();
$content = $this->getContent();
$finalStyle = json_decode(json_encode($this->finalStyle), true);
return h::div
(
setClass($this->finalClass),
setStyle($finalStyle),
set($this->getRestProps()),
$content,
$this->children()
);
}
private function initSize()
{
$size = $this->prop('size');
$this->actualSize = $size;
if(!$size) return;
if(is_numeric($size))
{
$fontSize = intval($size/2) > 12 ? intval($size/2) : 12;
$this->finalStyle->width = "{$size}px";
$this->finalStyle->height = "{$size}px";
$this->finalStyle->{'font-size'} = "{$fontSize}px";
return;
}
$this->finalClass[] = "size-{$size}";
$this->actualSize = isset($this->sizeMap[$size]) ? $this->sizeMap[$size] : 20;
}
private function initShape()
{
$circle = $this->prop('circle');
$rounded = $this->prop('rounded');
/* Set circle. */
if($circle)
{
$this->finalClass[] = 'rounded-full';
}
else if($rounded)
{
if(is_numeric($rounded)) $this->finalStyle->{'border-radius'} = "{$rounded}px";
else $this->finalClass[] = "rounded-{$rounded}";
}
}
private function getAvatarText()
{
$maxTextLen = intval($this->prop('maxTextLength'));
$text = strtoupper($this->prop('text', ''));
$this->textLen = strlen($text);
if(preg_match('/[\x{4e00}-\x{9fa5}\s]+$/u', $text))
{
$this->textLen = mb_strlen($text);
$text = $this->textLen <= $maxTextLen ? $text : mb_substr($text, $this->textLen - $maxTextLen);
$this->displayTextLen = mb_strlen($text);
return $text;
}
if(preg_match('/[A-Za-z\d\s]+$/', $text))
{
$this->displayTextLen = 1;
return substr($text, 0, 1);
}
return $this->textLen <= $maxTextLen ? $text : substr($text, 0, $maxTextLen);
}
/**
* Convert HSL values to RGB value.
*
* @param int $h
* @param int $s
* @param int $l
* @access private
* @return array
*/
private function hslToRgb($h, $s, $l)
{
$h = ($h % 360) / 360;
$s = ($s > 0 ? $s : 0);
$s = ($s > 255) ? 255 : $s;
$l = ($l > 0 ? $l : 0);
$l = ($l > 255) ? 255 : $l;
$m2 = ($l <= 0.5) ? ($l * ($s + 1)) : ($l + $s - $l * $s);
$m1 = $l * 2 - $m2;
$hueFn = function($val, $m1, $m2)
{
$val = $val < 0 ? $val + 1 : ($val > 1 ? $val - 1 : $val);
if($val * 6 < 1) return $m1 + ($m2 - $m1) * $val * 6;
elseif($val * 2 < 1) return $m2;
elseif($val * 3 < 2) return $m1 + ($m2 - $m1) * (2/3 - $val) * 6;
return $m1;
};
return array(
'r' => $hueFn($h + 1/3, $m1, $m2) * 255,
'g' => $hueFn($h, $m1, $m2) * 255,
'b' => $hueFn($h - 1/3, $m1, $m2) * 255
);
}
private function hex2Rgb($hex)
{
if(!str_starts_with($hex, '#') || !preg_match('/#[0-9A-F]{3,6}$/', $hex)) throw new \Exception('incorrect data format');
$r = 0;
$g = 0;
$b = 0;
if(strlen($hex) == 4) list($r, $g, $b) = sscanf($hex, "#%01x%01x%01x");
elseif(strlen($hex) == 7) list($r, $g, $b) = sscanf($hex, "#%02x%02x%02x");
else throw new \Exception('incorrect RGB value');
return array(
'r' => $r,
'g' => $g,
'b' => $b
);
}
/*
* Get contrast color.
*
* @param array|string $rgb
* @param string $theme dark|light
* @access private
* @return string
*/
private function contrastColor($rgb, $themeDark = null, $themeLight = null)
{
$rgb = is_array($rgb) ? $rgb : $this->hex2Rgb($rgb);
$r = $rgb['r'];
$g = $rgb['g'];
$b = $rgb['b'];
if(($r * 0.299 + $g * 0.587 + $b * 0.114) > 186)
{
/* Is light color. */
return $themeDark ? $themeDark : '#333333';
}
return $themeLight ? $themeLight : '#ffffff';
}
private function getTextStyle()
{
$hueDistance = intval($this->prop('hueDistance'));
$saturation = $this->prop('saturation');
$lightness = $this->prop('lightness');
$background = $this->prop('background');
$foreColor = $this->prop('foreColor');
$code = $this->prop('code');
$avatarCode = $code ? $code : $this->prop('text');
if(!$background)
{
$val = 0;
if(is_numeric($avatarCode))
{
$val = intval($avatarCode);
}
else
{
for($i = 0; $i < strlen($avatarCode); $i++) $val += ord($avatarCode[$i]);
}
$hue = $val * $hueDistance % 360;
$actualSat = $saturation * 100;
$actualLight = $lightness * 100;
$this->finalStyle->background = "hsl({$hue}, {$actualSat}%, {$actualLight}%)";
if(!$foreColor)
{
$rgb = $this->hslToRgb($hue, $saturation, $lightness);
$this->finalStyle->color = $this->contrastColor($rgb);
}
}
elseif (!$foreColor && $background)
{
$this->finalStyle->color = $this->contrastColor($background);
}
$textStyle = array();
if($this->actualSize and $this->actualSize < (14 * $this->displayTextLen))
{
$textStyle = array(
'transform' => 'scale(' . $this->actualSize / (14 * $this->displayTextLen) . ')',
'white-space' => 'nowrap'
);
}
return $textStyle;
}
private function getContent()
{
$src = $this->prop('src');
$text = $this->prop('text');
$code = $this->prop('code');
/* With avatar. */
if($src)
{
$this->finalClass[] = 'has-img';
return h::img
(
setClass('avatar-img'),
set('src', $src),
set('alt', $text),
set('data-code', $code),
);
}
/* Without text and image. */
if(!$text) return null;
$displayText = $this->getAvatarText();
$this->finalClass[] = 'has-text';
$this->finalClass[] = 'has-text-' . $this->textLen;
$textStyle = $this->getTextStyle();
return h::div
(
setClass('avatar-text'),
set('data-actualSize', $this->actualSize),
$textStyle ? setStyle($textStyle) : null,
$displayText
);
}
}
+105
View File
@@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
/**
* The backBtn widget class file of zin module of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author sunhao<sunhao@easycorp.ltd>
* @package zin
* @link http://www.zentao.net
*/
namespace zin;
require_once dirname(__DIR__) . DS . 'btn' . DS . 'v1.php';
/**
* 后退按钮(backBtn)部件类。
* The back button widget class.
*
* @author Hao Sun
*/
class backBtn extends btn
{
/**
* Define widget properties.
*
* @var array
* @access protected
*/
protected static array $defineProps = array(
'back?: string="APP"' // 定义返回行为,可以为 `'APP'`(默认值,返回打开当前页面时的上一个历史记录)、 `'GLOBAL'`(返回上一个全局历史记录)、`'moduleName-methodName'`(从历史记录中向后查找符合指定路径的历史记录)。
);
/**
* Override the getProps method.
*
* @access protected
* @return array
*/
protected function getProps(): array
{
global $app;
$backs = array(
'task' => 'execution-task,my-work,my-contribute,',
'story' => 'product-browse,projectstory-story,execution-story,my-work,my-contribute,productplan-view',
'bug' => 'bug-browse,project-bug,my-work,my-contribute,',
'testcase' => 'testcase-browse,project-testcase,my-work,my-contribute,',
'testsuite' => 'testsuite-browse,testsuite-view,',
'testtask' => 'testtask-browse,testtask-cases,',
'testreport' => 'testreport-browse,project-testreport',
'tree' => 'product-browse,project-browse,execution-task,bug-browse,projectstory-story',
'doc' => 'doc-mySpace,doc-productSpace,doc-projectSpace,doc-teamSpace',
'design' => 'design-browse',
'release' => 'release-browse,release-view',
'projectrelease' => 'projectrelease-browse',
'build' => 'execution-build,build-view',
'projectbuild' => 'projectbuild-browse,projectbuild-view',
'mr' => 'mr-browse',
'repo' => 'repo-browse,repo-log',
'compile' => 'compile-browse',
'store' => 'store-browse',
'space' => 'space-browse',
'instance' => 'space-browse',
'artifactrepo' => 'artifactrepo-browse',
);
$props = parent::getProps();
$back = $this->prop('back');
if($back != 'APP')
{
$props['data-back'] = $back;
}
elseif(isset($backs[$app->rawModule]))
{
$props['data-back'] = $backs[$app->rawModule];
if(!$this->prop('url'))
{
$backLinks = explode(',', $backs[$app->rawModule]);
$props['data-url'] = $backLinks[0];
}
}
else
{
$props['data-back'] = empty($back) ? 'APP' : $back;
}
return $props;
}
/**
* Override the getClassList method.
*
* @access protected
* @return array
*/
protected function getClassList(): array
{
$classList = parent::getClassList();
$classList['open-url'] = true;
return $classList;
}
}
+82
View File
@@ -0,0 +1,82 @@
/**
* Determines whether its argument represents a JavaScript number.
* @param {*} obj
* @returns bool
*/
function isNumeric(obj)
{
return (!isNaN(obj) && typeof obj === 'number') || $.isNumeric(obj);;
}
/**
* Add new item.
*
* @param obj e
* @access public
* @return void
*/
function addItem(e)
{
const obj = e.target
const newItem = $(obj).closest('.form-row').clone();
let index = 0;
newItem.find('.add-btn').on('click', addItem);
newItem.find('.del-btn').on('click', removeItem);
let inputName = newItem.find('input').length > 0 ? newItem.find('input').first().attr('name') : newItem.find('select').first().attr('name');
inputName = inputName.slice(0, inputName.indexOf('['));
$('form').find("[name^='" + inputName + "']").each(function() {
let $name = $(this).attr('name');
let id = parseInt($name.slice($name.indexOf('[')+1, $name.indexOf(']')));
if(isNumeric(id) && id >= index) index = id + 1;
})
/* Fix id and value. */
newItem.addClass('newItem');
newItem.find('.form-label').html('');
newItem.find('input').each(function()
{
let name = $(this).attr('name');
name = name.slice(0, name.indexOf('[')+1) + String(index) + name.slice(name.indexOf(']'));
$(this).attr('name', name);
$(this).attr('id', name);
$(this).val('');
});
newItem.find('select').each(function()
{
let name = $(this).attr('name');
name = name.slice(0, name.indexOf('[')+1) + String(index) + name.slice(name.indexOf(']'));
$(this).attr('name', name);
$(this).attr('id', name);
$(this).val('');
});
$(obj).closest('.form-row').after(newItem);
}
/**
* Remove item.
*
* @param obj e
* @access public
* @return void
*/
function removeItem(e)
{
const obj = e.target
/* Dsiabled btn can't remove line. */
if($(obj).closest('.btn').hasClass('disabled')) return false;
$(obj).closest('.form-row').remove();
let chosenProducts = 0;
$("select[name^='products']").each(function()
{
if($(this).val() > 0) chosenProducts ++;
});
(chosenProducts.length > 1 && (model == 'waterfall' || model == 'waterfallplus')) ? $('.stageBy').removeClass('hide') : $('.stageBy').addClass('hide');
}
+39
View File
@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace zin;
class batchActions extends wg
{
protected static array $defineProps = array(
'actionClass?: string=""',
);
public static function getPageJS(): string|false
{
return file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js');
}
protected function build(): wg
{
return formGroup
(
setClass('ml-2'),
div
(
setClass($this->prop('actionClass')),
btn
(
icon('plus', set::size('lg')),
setClass('bg-white ring-0 rounded bg-opacity-20 add-btn'),
on::click('addItem'),
),
btn
(
icon('close', set::size('lg')),
setClass('bg-white ring-0 rounded bg-opacity-20 del-btn'),
on::click('removeItem'),
),
)
);
}
}
+83
View File
@@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
/**
* The blockPanel widget class file of zin module of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author sunhao<sunhao@easycorp.ltd>
* @package zin
* @link http://www.zentao.net
*/
namespace zin;
require_once dirname(__DIR__) . DS . 'panel' . DS . 'v1.php';
/**
* 仪表盘区块面板(blockPanel)部件类。
* The block panel widget class.
*
* @author Hao Sun
*/
class blockPanel extends panel
{
protected static array $defineProps = array
(
'class?: string="rounded bg-canvas panel-block"', // 类名。
'id?: string', // ID。
'name?: string', // 区块内部名称。
'block?: object|array', // 区块对象。
'title?: string', // 标题。
'headingClass?: string="border-b"', // 标题栏类名。
'longBlock?: bool', // 是否为长区块。
'moreLink?: string' // 更多链接。
);
protected function created()
{
global $lang;
$props = array();
$name = $this->prop('name');
$block = $this->prop('block', data('block'));
if(is_array($block)) $block = (object)$block;
if(empty($name) && !empty($block))
{
$name = $block->code;
$props['name'] = $name;
if(empty($this->prop('id'))) $props['id'] = $block->module . '-' . $block->code . '-' . $block->id;
}
$moreLink = $this->prop('moreLink');
if(empty($moreLink) && !empty($block) && isset($block->moreLink)) $moreLink = $block->moreLink;
if(empty($this->prop('headingActions')) && !empty($moreLink))
{
$props['headingActions'] = array(array('type' => 'ghost', 'url' => $moreLink, 'text' => $lang->more, 'caret' => 'right', 'size' => 'sm'));
}
if(empty($this->prop('title'))) $props['title'] = empty($block) ? $lang->block->titleList[$name] : $block->title;
if($this->prop('longBlock') === null) $props['longBlock'] = data('longBlock');
$this->setProp($props);
}
protected function buildProps(): array
{
$props = parent::buildProps();
$name = $this->prop('name');
if(!empty($name))
{
$props[] = setData('block', $name);
$props[] = setClass("block-{$name}");
$props[] = setID($this->prop('id'));
}
$props[] = setClass($this->prop('longBlock') ? 'is-long' : 'is-short');
return $props;
}
}
+105
View File
@@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
namespace zin;
class btn extends wg
{
protected static array $defineProps = array(
'text?:string', // 按钮的文本。
'icon?:string', // 图标名称。
'iconClass?:string', // 图标的样式类。
'square?:bool', // 是否为方形按钮,通常用于只显示一个图标的按钮。
'disabled?:bool', // 是否禁用按钮。
'active?:bool', // 是否为激活状态。
'url?:string', // 按钮的链接地址。
'target?:string', // 按钮的链接目标。
'size?:string|int', // 按钮的尺寸,可选值为 `'xl'`、`'lg'`、`'md'`、`'sm'` 或者通过数字设置宽高,如 `20`。
'trailingIcon?:string', // 按钮尾部图标的名称。
'trailingIconClass?:string', // 按钮尾部图标的样式类。
'caret?:string|bool', // 按钮的下拉箭头,可选值为 `'top'`(向上)、`'bottom'`(向下) 或者 `true`(自动)。
'hint?:string', // 按钮的提示文本(鼠标悬停时显示)。
'type?:string', // 按钮的类型,可选值为 `'default'`、`'primary'`、`'success'`、`'info'`、`'warning'`、`'danger'`、`'link'`。
'btnType?:string="button"' // 按钮的类型,可选值为 `'button'`、`'submit'`、`'reset'`。
);
public function onAddChild($child)
{
if(is_string($child) && !$this->props->has('text'))
{
$this->props->set('text', $child);
return false;
}
}
protected function getProps()
{
$url = $this->prop('disabled') ? null : $this->prop('url');
$target = $this->prop('target');
$props = array_merge($this->getRestProps(), array('title' => $this->prop('hint')));
if(empty($url))
{
$props['type'] = $this->prop('btnType');
if(!isset($props['data-target'])) $props['data-target'] = $target;
return $props;
}
$props['tagName'] = 'a';
if(!isset($props['href'])) $props['href'] = $url;
if(!isset($props['target'])) $props['target'] = $target;
return $props;
}
private function getChildren()
{
list($caret, $text, $icon, $iconClass, $trailingIcon, $trailingIconClass) = $this->prop(array('caret', 'text', 'icon', 'iconClass', 'trailingIcon', 'trailingIconClass'));
$children = array();
if(!empty($icon)) $children[] = icon($icon, setClass($iconClass));
if(!empty($text)) $children[] = h::span($text, setClass('text'));
$children[] = parent::build();
if(!empty($trailingIcon)) $children[] = icon($trailingIcon, setClass($trailingIconClass));
if(!empty($caret)) $children[] = h::span(setClass(is_string($caret) ? "caret-$caret" : 'caret'));
return $children;
}
protected function getClassList()
{
list($url, $type, $caret, $text, $icon, $trailingIcon) = $this->prop(array('url', 'type', 'caret', 'text', 'icon', 'trailingIcon'));
$onlyCaret = empty($text) && !empty($caret) && empty($icon) && empty($trailingIcon);
$classList = array(
'btn' => true,
'disabled' => $this->prop('disabled'),
'active' => $this->prop('active'),
'btn-caret' => $onlyCaret,
'square' => $this->prop('square')
);
if(empty($type) && !empty($url)) $type = 'btn-default';
else if($type === 'link') $type = 'btn-link';
else if($type === 'default') $type = 'btn-default';
if(!empty($type)) $classList[$type] = true;
if(empty($text) && !empty($icon) && !isset($classList['square'])) $classList['square'] = true;
$size = $this->prop('size');
if(!empty($size)) $classList["size-$size"] = true;
return $classList;
}
protected function build(): wg
{
$props = $this->getProps();
$children = $this->getChildren();
$classList = $this->getClassList();
return button
(
set($props),
setClass($classList),
$children
);
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace zin;
class btnGroup extends wg
{
protected static array $defineProps = array(
'items?:array',
'disabled?:bool',
'size?:string',
);
public function onBuildItem($item): btn
{
if(!($item instanceof item)) $item = item(set($item));
return btn(inherit($item));
}
private function getClassName(): string
{
$disabled = $this->prop('disabled');
$size = $this->prop('size');
$className = 'btn-group';
if(!empty($disabled)) $className .= ' disabled';
if(!empty($size)) $className .= " size-$size";
return $className;
}
protected function build(): wg
{
$items = $this->prop('items');
$className = $this->getclassName();
return div
(
setClass($className),
set($this->getRestProps()),
is_array($items) ? array_map(array($this, 'onBuildItem'), $items) : null,
$this->children()
);
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
/**
* The burn widget class file of zin module of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author Yanyi Cao<caoyanyi@easycorp.ltd>
* @package zin
* @link http://www.zentao.net
*/
namespace zin;
/**
* 仪表盘(burn)部件类。
* The burn widget class.
*
* @author Hao Sun
*/
class burn extends wg
{
/**
* Define widget properties.
*
* @var array
* @access protected
*/
protected static array $defineProps = array(
'data?: string|array', // 数据源
'referenceLine?: bool=false' // 参考线
);
/**
* Build widget.
*/
protected function build(): zui
{
return zui::burn(inherit($this));
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace zin;
class cell extends wg
{
protected static array $defineProps = array(
'flex?: string', // flex 类型或具体的值,例如:'auto'、'none'、'1'、'auto 1 1'。
'order?: int', // flex-order 属性。
'grow?: int', // flex-grow 属性。
'shrink?: int', // flex-shrink 属性。
'width?: string|int', // flex-basis 属性,支持数值或百分比,例如 128px、1/3、30%、128px。
'align?: string' // align-self 属性,例如 'auto'、'flex-start'、'flex-end'、'center'、'baseline'、'stretch'。
);
protected function build(): wg
{
$basis = null;
$class = array('cell');
$width = $this->prop('width');
$flex = $this->prop('flex');
if(!empty($width))
{
$basis = $width;
if(is_numeric($width)) $basis = $width . 'px';
elseif(preg_match('/^(\d+)\/(\d+)$/', $width, $matches) !== 0) $basis = ((int)$matches[1] / (int)$matches[2] * 100) . '%';
}
if(!empty($flex))
{
if(strpos($flex, ' ') !== false) $style['flex'] = $flex;
else $class[] = "flex-$flex";
}
$style = array();
$style['order'] = $this->prop('order');
$style['flex-grow'] = $this->prop('grow');
$style['flex-shrink'] = $this->prop('shrink');
$style['flex-basis'] = $basis;
$style['align-self'] = $this->prop('align');
return div
(
setClass($class),
setStyle($style),
set($this->getRestProps()),
$this->children()
);
}
}
+16
View File
@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace zin;
class center extends wg
{
protected function build(): wg
{
return div
(
setClass("center"),
set($this->getRestProps()),
$this->children()
);
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace zin;
class checkbox extends wg
{
protected static array $defineProps = array(
'text?: string',
'checked?: bool',
'name?: string',
'primary: bool=true',
'id?: string',
'disabled?: bool',
'type: string="checkbox"',
'value?: string',
'typeClass?: string',
'rootClass?: string',
'labelClass?: string',
);
public function onAddChild($child)
{
if(is_string($child) && !$this->props->has('text'))
{
$this->props->set('text', $child);
return false;
}
}
protected function buildPrimary()
{
list($id, $text, $name, $checked, $disabled, $type, $typeClass, $rootClass, $labelClass, $value) = $this->prop(array('id', 'text', 'name', 'checked', 'disabled', 'type', 'typeClass', 'rootClass', 'labelClass', 'value'));
if(empty($typeClass)) $typeClass = $type;
if(empty($id)) $id = $name . '_' . $value;
return div
(
setClass("$typeClass-primary", $rootClass, array('disabled' => $disabled)),
h::input
(
set::type($type),
set::id($id),
set::name($name),
set::checked($checked),
set($this->props->skip('text,primary,typeClass,rootClass,id,labelClass')),
),
h::label
(
set('for', $id),
setClass($labelClass),
$text,
),
$this->children()
);
}
protected function build(): wg
{
if($this->prop('primary')) return $this->buildPrimary();
list($text, $type, $typeClass) = $this->prop(array('text', 'type', 'typeClass'));
return h::label
(
setClass(empty($typeClass) ? $type : $typeClass),
h::input
(
set::type($type),
set($this->props->skip('text,primary,typeClass')),
),
is_string($text) ? span($text, set::className('text')) : $text,
$this->children()
);
}
}
+1
View File
@@ -0,0 +1 @@
.checkbox-list {border-left: 1px solid var(--color-gray-400);}
+41
View File
@@ -0,0 +1,41 @@
window.handleCheckboxGroupClick = function(event)
{
const $target = $(event.target);
const $checkboxGroup = $target.closest('.checkbox-group');
if($target.closest('.checkbox-title').length > 0)
{
const $checkboxTitle = $target.closest('.checkbox-title');
$checkboxGroup
.find('.checkbox-child')
.prop('checked', $checkboxTitle.prop('checked'));
return;
}
if($target.closest('.checkbox-child').length > 0)
{
let checkedCount = 0;
const $checkboxChildren = $checkboxGroup.find('.checkbox-child');
$checkboxChildren.each((_i, input) =>
{
if(input.checked === true) checkedCount++;
});
const checkboxTitle = $checkboxGroup.find('.checkbox-title')[0];
if(checkedCount === 0)
{
checkboxTitle.checked = false;
checkboxTitle.indeterminate = false;
}
else if(checkedCount === $checkboxChildren.length)
{
$checkboxGroup.find('.checkbox-title').prop('checked', true);
checkboxTitle.checked = true;
checkboxTitle.indeterminate = false;
}
else
{
checkboxTitle.checked = false;
checkboxTitle.indeterminate = true;
}
}
}
+67
View File
@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace zin;
class checkboxGroup extends wg
{
protected static array $defineProps = array(
'title: array',
'items: array'
);
private static array $checkboxProps = array(
'checked' => false,
'disabled' => false,
);
public static function getPageCSS(): string|false
{
return file_get_contents(__DIR__ . DS . 'css' . DS . 'v1.css');
}
public static function getPageJS(): string|false
{
return file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js');
}
private function buildTitle(): wg
{
$title = array_merge(self::$checkboxProps, $this->prop('title'));
return checkbox(set($title), setClass('checkbox-title'));
}
private function buildCheckboxList(): wg
{
$items = $this->prop('items');
$title = array_merge(self::$checkboxProps, $this->prop('title'));
$list = ul(setClass('flex', 'flex-wrap', 'ml-1.5', 'checkbox-list', 'pl-3'));
foreach($items as $item)
{
$item = array_merge(self::$checkboxProps, $item);
if($title['checked'] === true) $item['checked'] = true;
if($title['disabled'] === true) $item['disabled'] = true;
$list->add
(
li
(
setClass('basis-1/2'),
checkbox(set($item), setClass('checkbox-child'))
)
);
}
return $list;
}
public function build(): wg
{
return div
(
set('data-on', 'click'),
set('data-call', 'window.handleCheckboxGroupClick'),
set('data-params', 'event'),
setClass('checkbox-group'),
$this->buildTitle(),
$this->buildCheckboxList(),
);
}
}
+71
View File
@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace zin;
require_once dirname(__DIR__) . DS . 'checkbox' . DS . 'v1.php';
class checkList extends wg
{
protected static array $defineProps = array(
'primary: bool=true',
'type: string="checkbox"',
'name?: string',
'value?: string|array',
'items?: array',
'inline?: bool',
'disabled?: bool'
);
public function getValueList()
{
$value = $this->prop('value');
if(is_null($value)) return array();
if($this->prop('type') === 'checkbox') return is_array($value) ? $value : explode(',', $value);
return [$value];
}
public function onBuildItem($item): checkbox
{
if($item instanceof item) $item = $item->props->toJSON();
if(!isset($item['checked']))
{
$value = isset($item['value']) ? $item['value'] : '';
$valueList = $this->getValueList();
$item['checked'] = in_array($value, $valueList);
$item['disabled'] = $this->prop('disabled');
}
$props = $this->props->pick(['primary', 'type', 'name', 'disabled']);
if(!empty($props['name']) && !empty($item['value'])) $props['id'] = $props['name'] . $item['value'];
return new checkbox(set($props), set($item));
}
protected function build(): wg
{
list($items, $inline, $disabled) = $this->prop(['items', 'inline', 'disabled']);
if(!empty($items))
{
$valueList = $this->getValueList();
foreach($items as $key => $item)
{
if(!is_array($item)) $item = array('text' => $item, 'value' => $key);
if(!isset($item['checked'])) $item['checked'] = in_array($item['value'], $valueList);
$items[$key] = $this->onBuildItem($item);
}
}
return div
(
setClass($inline ? 'check-list-inline' : 'check-list'),
set($this->getRestProps()),
$disabled ? set('disabled', 'disabled') : '',
$items,
$this->children()
);
}
}
+26
View File
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace zin;
class col extends wg
{
protected static array $defineProps = array(
'justify?:string',
'align?:string'
);
protected function build(): wg
{
$classList = 'col';
list($justify, $align) = $this->prop(array('justify', 'align'));
if(!empty($justify)) $classList .= ' justify-' . $justify;
if(!empty($align)) $classList .= ' items-' . $align;
return div
(
setClass($classList),
set($this->getRestProps()),
$this->children()
);
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace zin;
require_once dirname(__DIR__) . DS . 'btn' . DS . 'v1.php';
class collapseBtn extends wg
{
protected static array $defineProps = array(
'target: string', // 展开折叠的目标元素选择器。
'parent: string' // 目标元素与按钮共同的父级元素选择器,使用 closest 辅助目标元素的确定。
);
protected function build(): wg
{
$target = $this->prop('target');
$parent = $this->prop('parent');
return btn
(
setClass('btn-link', 'collapse-btn'),
set($this->getRestProps()),
set::icon('angle-down'),
on::click
(
<<<FUNC
const btn = event.target.closest('.collapse-btn');
const icon = btn.querySelector('.icon');
icon.classList.toggle('icon-angle-down');
icon.classList.toggle('icon-angle-top');
const parentElm = btn.closest('$parent');
const targetElm = parentElm.querySelector('$target');
if(targetElm) targetElm.classList.toggle('hidden');
FUNC
)
);
}
}
+83
View File
@@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
/**
* The colorPicker widget class file of zin module of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author sunhao<sunhao@easycorp.ltd>
* @package zin
* @link http://www.zentao.net
*/
namespace zin;
/**
* 颜色选择器(colorPicker)部件类
* The colorPicker widget class
*/
class colorPicker extends wg
{
/**
* Define widget properties.
*
* @var array
* @access protected
*/
protected static array $defineProps = array(
'id?: string="$GID"', // 组件根元素的 ID。
'formID?: string', // 组件隐藏的表单元素 ID。
'className?: string|array', // 类名。
'style?: array', // 样式。
'tagName?: string', // 组件根元素的标签名。
'attrs?: array', // 附加到组件根元素上的属性。
'clickType?: "toggle"|"open"', // 点击类型,`toggle` 表示点击按钮时切换显示隐藏,`open` 表示点击按钮时只打。
'afterRender?: function', // 渲染完成后的回调函数。
'beforeDestroy?: function', // 销毁前的回调函数。
'name?: string', // 作为表单项的名称。
'value?: string|string[]', // 默认值。
'onChange?: function', // 值变更回调函数。
'disabled?: boolean', // 是否禁用。
'multiple?: boolean|number=false', // 是否允许选择多个值,如果指定为数字,则限制多选的数目,默认 `false`。
'required?: boolean', // 是否必选(不允许空值,不可以被清除)。
'items?: string | string[]', // 颜色选项列表。
'icon?: string|array="color"', // 将触发按钮显示为图标。
'syncValue?: string', // 指定选择器同步颜色值作为文本到的元素。
'syncColor?: string', // 指定选择器同步文字颜色到的元素。
'syncBackground?: string', // 指定选择器同步背景颜色到的元素。
'syncBorder?: string', // 指定选择器同步边框颜色到的元素。
'hint?: string', // 提示文字。
'closeBtn?: boolean', // 是否在弹出面板上显示关闭按钮。
'heading?: ComponentChildren' // 弹出面板的标题。
);
/**
* Build widget.
*
* @access protected
*/
protected function build(): wg
{
list($props, $restProps) = $this->props->split(array_keys(static::definedPropsList()));
if(isset($props['id']))
{
$props['_id'] = $props['id'];
unset($props['id']);
}
if(!isset($props['items']))
{
global $app, $lang;
$moduleName = $app->getModuleName();
if(isset($lang->$moduleName->colorList)) $props['items'] = $lang->$moduleName->colorList;
}
return zui::colorPicker
(
set::_class('form-group-wrapper'),
set::_map(array('value' => 'defaultValue', 'items' => 'colors', 'formID' => 'id')),
set::_props($restProps),
set($props),
$this->children(),
);
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace zin;
class commentBtn extends btn
{
protected static array $defineProps = array(
'dataTarget?:string',
'dataUrl?:string',
'dataType?:string',
'icon?:string',
'iconClass?:string',
'text?:string',
'square?:bool',
'disabled?:bool',
'active?:bool',
'url?:string',
'target?:string',
'size?:string|int',
'trailingIcon?:string',
'trailingIconClass?:string',
'caret?:string|bool',
'hint?:string',
'type?:string',
'btnType?:string'
);
protected function getProps(): array
{
$dataTarget = $this->prop('dataTarget');
$dataUrl = $this->prop('dataUrl');
$dataType = $this->prop('dataType');
$props = parent::getProps();
$props['data-toggle'] = 'modal';
$props['data-type'] = $dataType;
$props['data-url'] = $dataUrl;
$props['data-target'] = $dataTarget;
return $props;
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace zin;
class commentDialog extends wg
{
protected static array $defineProps = array(
'title?:string',
'url?:string',
'name?:string="comment"',
'method?:string="post"'
);
protected function build(): wg
{
global $lang;
$title = $this->prop('title');
$name = $this->prop('name');
$url = $this->prop('url');
$method = $this->prop('method');
if(empty($title)) $title = $lang->action->create;
return modal
(
setID('comment-dialog'),
set::modalProps(array('title' => $title)),
commentForm
(
set::url($url),
set::method($method),
set::name($name),
)
);
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace zin;
class commentForm extends wg
{
protected static array $defineProps = array(
'url?:string',
'name?:string="comment"',
'method?:string="POST"'
);
protected function build(): wg
{
global $lang;
$url = $this->prop('url');
$name = $this->prop('name');
$method = $this->prop('method');
if(empty($name)) $name = 'comment';
return form
(
set::url($url),
set::method($method),
set::submitBtnText($lang->save),
setClass('comment-form'),
editor
(
setID($name),
set::name($name)
),
set::actions
(
array(
'submit',
array('data-dismiss' => 'modal', 'text' => $lang->close)
)
)
);
}
}
+169
View File
@@ -0,0 +1,169 @@
<?php
declare(strict_types=1);
namespace zin;
require_once dirname(__DIR__) . DS . 'input' . DS . 'v1.php';
require_once dirname(__DIR__) . DS . 'textarea' . DS . 'v1.php';
require_once dirname(__DIR__) . DS . 'editor' . DS . 'v1.php';
require_once dirname(__DIR__) . DS . 'checkbox' . DS . 'v1.php';
require_once dirname(__DIR__) . DS . 'checklist' . DS . 'v1.php';
require_once dirname(__DIR__) . DS . 'radiolist' . DS . 'v1.php';
require_once dirname(__DIR__) . DS . 'select' . DS . 'v1.php';
require_once dirname(__DIR__) . DS . 'inputcontrol' . DS . 'v1.php';
require_once dirname(__DIR__) . DS . 'picker' . DS . 'v1.php';
require_once dirname(__DIR__) . DS . 'datepicker' . DS . 'v1.php';
require_once dirname(__DIR__) . DS . 'timepicker' . DS . 'v1.php';
require_once dirname(__DIR__) . DS . 'pripicker' . DS . 'v1.php';
require_once dirname(__DIR__) . DS . 'severitypicker' . DS . 'v1.php';
require_once dirname(__DIR__) . DS . 'colorpicker' . DS . 'v1.php';
class control extends wg
{
protected static array $defineProps = array(
'type?: string', // 表单输入元素类型,值可以为:static, text, password, email, number, date, time, datetime, month, url, search, tel, color, picker, pri, severity, select, checkbox, radio, checkboxList, radioList, checkboxListInline, radioListInline, file, textarea
'name: string', // HTML name 属性
'id?: string', // HTML id 属性
'value?: string', // HTML value 属性
'placeholder?: string', // HTML placeholder 属性
'readonly?: bool', // HTML readonly 属性
'required?: bool', // 是否为必填项
'disabled?: bool', // 是否为禁用状态
'items?: array' // 表单输入元素子项数据
);
protected function created()
{
$name = $this->prop('name');
if($this->prop('type') === 'static' && $name === null) $this->setProp('name', '');
if($this->prop('id') === null && $name !== null)
{
$id = substr($name, -2) == '[]' ? substr($name, 0, - 2) : $name;
$this->setProp('id', $id);
}
}
/**
* Build control with static content.
*
* @return wg
*/
protected function buildStatic(): wg
{
$name = $this->prop('name');
return div
(
set::className('form-control-static'),
set($this->props->skip(array('type', 'name', 'value', 'required', 'disabled', 'placeholder', 'items', 'required'))),
$name ? set('data-name', $name) : null,
$this->prop('value')
);
}
protected function buildTextarea(): wg
{
return new textarea(set($this->props->skip('type')));
}
protected function buildInputControl(): wg
{
$controlProps = array();
$allProps = $this->props->skip('type');
$propsNames = array_keys(inputControl::definedPropsList());
foreach($propsNames as $propName)
{
if(!isset($allProps[$propName])) continue;
$controlProps[$propName] = $allProps[$propName];
unset($allProps[$propName]);
}
return new inputControl
(
set($controlProps),
new input(set($allProps)),
);
}
protected function buildCheckbox(): wg
{
if($this->hasProp('items')) return $this->buildCheckList();
return new checkList
(
new checkbox(set($this->props->skip('type')))
);
}
protected function buildCheckList(): wg
{
return new checkList
(
set($this->props->skip('type'))
);
}
protected function buildRadioList(): wg
{
return new radioList
(
set($this->props->skip('type'))
);
}
protected function buildCheckListInline(): wg
{
return new checkList
(
set::inline(true),
set($this->props->skip('type'))
);
}
protected function buildRadioListInline(): wg
{
return new radioList
(
set::inline(true),
set($this->props->skip('type'))
);
}
protected function buildDate(): wg
{
return new datePicker(set($this->props->skip('type')));
}
protected function buildTime(): wg
{
return new timePicker(set($this->props->skip('type')));
}
protected function buildPri(): wg
{
return new priPicker(set($this->props->skip('type')));
}
protected function buildSeverity(): wg
{
return new severityPicker(set($this->props->skip('type')));
}
protected function buildColor(): wg
{
return new colorPicker(set($this->props->skip('type')));
}
protected function build(): wg
{
$type = $this->prop('type');
if(empty($type)) $type = $this->hasProp('items') ? 'picker' : 'text';
$methodName = "build{$type}";
if(method_exists($this, $methodName)) return $this->$methodName();
$wgName = "\\zin\\$type";
if(class_exists($wgName)) return new $wgName(set($this->props->skip('type')), $this->children());
return input(set($this->props));
}
}
+66
View File
@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
/**
* The dashboard widget class file of zin module of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author sunhao<sunhao@easycorp.ltd>
* @package zin
* @link http://www.zentao.net
*/
namespace zin;
/**
* 仪表盘(dashboard)部件类。
* The dashboard widget class.
*
* @author Hao Sun
*/
class dashboard extends wg
{
/**
* Define widget properties.
*
* @var array
* @access protected
*/
protected static array $defineProps = array(
'id?: string', // ID。
'cache?: bool|string', // 是否启用缓存。
'responsive?: bool', // 是否启用响应式。
'blocks: array', // 区块列表。
'grid?: int', // 栅格数。
'gap?: int', // 间距。
'leftStop?: int', // 区块水平停靠间隔。
'cellHeight?: int', // 网格高度。
'blockFetch?: string|function|array', // 区块数据获取 url 或选项。
'blockDefaultSize?: array', // 区块默认大小。
'blockSizeMap?: array', // 区块大小映射。
'blockMenu?: array', // 区块菜单。
'onLayoutChange?: function', // 布局变更事件。
'onClickMenu?: function' // 布局变更事件。
);
static $dashboardID = 0;
protected function created()
{
$this->setDefaultProps(array('id' => static::$dashboardID ? static::$dashboardID : 'dashboard', 'cache' => data('app.user.account')));
static::$dashboardID++;
}
/**
* Build widget.
*/
protected function build(): wg
{
return zui::dashboard
(
set($this->props->skip(array('id'))),
set('_id', $this->prop('id')),
set('_props', $this->getRestProps())
);
}
}
+85
View File
@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
/**
* The datePicker widget class file of zin module of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author sunhao<sunhao@easycorp.ltd>
* @package zin
* @link http://www.zentao.net
*/
namespace zin;
require_once dirname(__DIR__) . DS . 'input' . DS . 'v1.php';
/**
* 日期选择器(datePicker)部件类
* The datePicker widget class
*/
class datePicker extends wg
{
/**
* Define widget properties.
*
* @var array
* @access protected
*/
protected static array $defineProps = array
(
'id?: string="$GID"', // 组件根元素的 ID。
'formID?: string', // 组件隐藏的表单元素 ID。
'className?: string|array', // 类名。
'style?: array', // 样式。
'tagName?: string', // 组件根元素的标签名。
'attrs?: array', // 附加到组件根元素上的属性。
'clickType?: "toggle"|"open"', // 点击类型,`toggle` 表示点击按钮时切换显示隐藏,`open` 表示点击按钮时只打。
'afterRender?: function', // 渲染完成后的回调函数。
'beforeDestroy?: function', // 销毁前的回调函数。
'name?: string', // 作为表单项的名称。
'value?: string|string[]', // 默认值。
'onChange?: function', // 值变更回调函数。
'disabled?: boolean', // 是否禁用。
'multiple?: boolean|number=false', // 是否允许选择多个值,如果指定为数字,则限制多选的数目,默认 `false`。
'required?: boolean', // 是否必选(不允许空值,不可以被清除)。
'placeholder?: string', // 选择框上的占位文本。
'format?: string', // 日期格式,默认 yyyy-MM-dd。
'icon?: string|array="calendar"', // 在输入框右侧显示的图标。
'weekNames?: string[]', // 星期名称,索引为 0 表示周日。
'monthNames?: string[]', // 月份名称,索引为 0 表示一月份。
'yearText?: string', // 用于显示年份的格式化文本。
'todayText?: string', // 用于显示“今天”的文本。
'clearText?: string', // 用于显示“清除”的文本。
'weekStart?: int', // 一周从星期几开始,默认 1。
'minDate?: string|int', // 最小可选的日期。
'maxDate?: string|int', // 最大可选的日期。
'menu?: array', // 左侧显示的菜单设置。
'actions?: array', // 底部工具栏设置。
'onInvalid?: function', // 日期值无效时的回调函数。
);
/**
* Build the widget.
*
* @access protected
* @return wg
*/
protected function build(): wg
{
list($props, $restProps) = $this->props->split(array_keys(static::definedPropsList()));
if(isset($props['id']))
{
$props['_id'] = $props['id'];
unset($props['id']);
}
return zui::datePicker
(
set::_class('form-group-wrapper'),
set::_map(array('value' => 'defaultValue', 'formID' => 'id')),
set($props),
set::_props($restProps),
$this->children(),
);
}
}
+74
View File
@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace zin;
require_once dirname(__DIR__) . DS . 'input' . DS . 'v1.php';
class datetimePicker extends wg
{
/**
* Define widget properties.
*
* @var array
* @access protected
*/
protected static array $defineProps = array
(
'id?: string="$GID"', // 组件根元素的 ID。
'formID?: string', // 组件隐藏的表单元素 ID。
'className?: string|array', // 类名。
'style?: array', // 样式。
'tagName?: string', // 组件根元素的标签名。
'attrs?: array', // 附加到组件根元素上的属性。
'clickType?: "toggle"|"open"', // 点击类型,`toggle` 表示点击按钮时切换显示隐藏,`open` 表示点击按钮时只打。
'afterRender?: function', // 渲染完成后的回调函数。
'beforeDestroy?: function', // 销毁前的回调函数。
'name?: string', // 作为表单项的名称。
'value?: string|string[]', // 默认值。
'onChange?: function', // 值变更回调函数。
'disabled?: boolean', // 是否禁用。
'multiple?: boolean|number=false', // 是否允许选择多个值,如果指定为数字,则限制多选的数目,默认 `false`。
'required?: boolean', // 是否必选(不允许空值,不可以被清除)。
'placeholder?: string', // 选择框上的占位文本。
'icon?: string|array="calendar"', // 在输入框右侧显示的图标。
'weekNames?: string[]', // 星期名称,索引为 0 表示周日。
'monthNames?: string[]', // 月份名称,索引为 0 表示一月份。
'yearText?: string', // 用于显示年份的格式化文本。
'todayText?: string', // 用于显示“今天”的文本。
'clearText?: string', // 用于显示“清除”的文本。
'weekStart?: int', // 一周从星期几开始,默认 1。
'minDate?: string|int', // 最小可选的日期。
'maxDate?: string|int', // 最大可选的日期。
'menu?: array', // 左侧显示的菜单设置。
'actions?: array', // 底部工具栏设置。
'onInvalid?: function', // 日期值无效时的回调函数。
'dateFormat?: string', // 日期格式,默认 yyyy-MM-dd。
'timeFormat?: string', // 时间格式,默认 hh:mm
'joiner?: string' // 日期与时间的连接符,默认为单个空格
);
/**
* Build the widget.
*
* @access protected
* @return wg
*/
protected function build(): wg
{
list($props, $restProps) = $this->props->split(array_keys(static::definedPropsList()));
if(isset($props['id']))
{
$props['_id'] = $props['id'];
unset($props['id']);
}
return zui::datetimePicker
(
set::_class('form-group-wrapper'),
set::_map(array('value' => 'defaultValue', 'formID' => 'id')),
set($props),
set::_props($restProps),
$this->children(),
);
}
}
+1
View File
@@ -0,0 +1 @@
.detail-body > .form-actions:last-child {margin-top: 0;}
+9
View File
@@ -0,0 +1,9 @@
$(() =>
{
const $formActions = $('form.detail-body + .form-actions');
if($formActions.length)
{
$detailBody = $formActions.prev();
$detailBody.append($formActions);
}
});
+82
View File
@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace zin;
class detailBody extends wg
{
protected static array $defineProps = array(
'isForm?: bool=false'
);
protected static array $defineBlocks = array(
'main' => array('map' => 'sectionList'),
'side' => array('map' => 'detailSide'),
'bottom' => array('map' => 'history,fileList'),
'floating' => array('map' => 'floatToolbar'),
);
public static function getPageCSS(): string|false
{
return file_get_contents(__DIR__ . DS . 'css' . DS . 'v1.css');
}
public static function getPageJS(): string|false
{
return file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js');
}
protected function build(): wg
{
$main = $this->block('main');
$side = $this->block('side');
$bottom = $this->block('bottom');
$floating = $this->block('floating');
$isForm = $this->prop('isForm');
if(!$isForm)
{
return div
(
setClass('detail-body rounded flex gap-1'),
set($this->getRestProps()),
div
(
setClass('col gap-1 grow'),
$main,
$bottom,
center(setClass('pt-6'), $floating),
),
$side
);
}
return formBase
(
set::actionsClass('h-14 flex flex-none items-center justify-center shadow'),
setClass('detail-body rounded col overflow-y-hidden bg-white'),
set($this->getRestProps()),
setStyle('height', 'calc(100vh - 120px)'),
div
(
setClass('flex-auto overflow-y-auto'),
div
(
setClass('flex'),
setStyle('min-height', '100%'),
div
(
setClass('col grow'),
$main,
$bottom,
),
div
(
setClass('w-1'),
setStyle('background', 'var(--zt-page-bg)'),
),
$side,
),
)
);
}
}
+51
View File
@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace zin;
class detailHeader extends wg
{
protected static array $defineProps = array(
'back?: string="APP"',
'backUrl?: string',
);
protected static array $defineBlocks = array(
'prefix' => array(),
'title' => array(),
'suffix' => array(),
);
private function backBtn(): wg
{
global $lang;
return backBtn
(
set::icon('back'),
set::type('secondary'),
set::back($this->prop('back')),
set::url($this->prop('backUrl')),
$lang->goback
);
}
protected function build(): wg
{
$prefix = $this->block('prefix');
$title = $this->block('title');
$suffix = $this->block('suffix');
if(empty($prefix) && !isAjaxRequest('modal')) $prefix = $this->backBtn();
return div
(
setClass('detail-header flex justify-between mb-3'),
div
(
setClass('flex', 'items-center', 'gap-x-4'),
$prefix,
$title,
),
$suffix
);
}
}
+6
View File
@@ -0,0 +1,6 @@
.detail-side {width: 370px;}
.detail-side .tab-content>.tab-pane {padding-left: 0 !important;}
.detail-side {background: #fff; height: min-content;}
.detail-side .tabs:not(:first-child) {border-top: 1px solid #E6EAF1;}
.detail-side .tabs {padding-top: 12px; padding-bottom: 20px;}
.detail-side > .table-data {margin-top: 16px;}
+21
View File
@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace zin;
class detailSide extends wg
{
public static function getPageCSS(): string|false
{
return file_get_contents(__DIR__ . DS . 'css' . DS . 'v1.css');
}
protected function build(): wg
{
return div
(
setClass('detail-side flex-none px-6'),
set($this->getRestProps()),
$this->children()
);
}
}
+16
View File
@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace zin;
class divider extends wg
{
protected function build(): wg
{
return div
(
setClass("divider"),
set($this->getRestProps()),
$this->children()
);
}
}
+19
View File
@@ -0,0 +1,19 @@
.module-menu {max-height: calc(100vh - 105px); padding: 0 0 8px;}
.module-menu .active {color: var(--color-primary-600); font-weight: 500;}
.module-menu header a:hover > .icon {color: var(--color-primary-600) !important;}
.module-menu .tree-item * {white-space: nowrap;}
#docDropmenu {padding-bottom: 0.5rem;}
#docDropmenu .is-leading {display: none;}
#docDropmenu .primary {--tw-ring-color: var(--btn-border-color); background-color: var(--btn-bg); color: inherit; width: 100%;}
.module-menu .tree .tree-item .tree-link {text-overflow: clip; overflow: hidden; flex: 1 10 auto;}
.module-menu .tree .tree-item .tree-actions {margin-left: 0;}
.module-menu .tree .tree-item .tree-actions .icon-ellipsis-v {display: none;}
.module-menu .tree .tree-item .tree-item-content:hover .tree-actions .icon {display: block;}
.module-menu .tree .tree-item .tree-item-content .tree-actions .with-popover-show .icon {display: block;}
.tree > .tree-item > .project-tree-title {font-size: 16px; margin-bottom: 0.5rem;}
.tree > .tree-item > .project-tree-title .text {margin-left: 0.5rem;}
.tree > .tree-item > .project-tree-title .tree-icon {color: var(--nav-active-color); opacity: 1;}
.tree > .tree-item > .project-tree-title > .tree-toggle-icon {display: none;}
+69
View File
@@ -0,0 +1,69 @@
window.saveModule = function()
{
const name = $(this).val();
if(!name) return $(this).closest('.tree-item').remove();
const {id, type, lib, module} = $(this).data();
const parentID = $(this).data('parent');
const $element = $(`div[data-id='${id}']`);
$.ajaxSubmit({
url: $.createLink('tree', 'ajaxCreateModule'),
data: {
name : name,
libID : lib,
parentID : type == 'child' ? id : parentID,
objectID : id,
moduleType : module,
isUpdate : false,
createType : type,
},
onSuccess: () =>
{
$(this).val('');
}
});
}
window.addModule = function(id, addType)
{
const $element = $(`div[data-id='${id}']`);
const {lib, type, module} = $element.data();
let parentID = ['docLib', 'apiLib'].includes(type) ? '0' : $element.data('parent');
if(addType == 'child') parentID = id;
const level = addType == 'same' ? $element.data('level') : $element.data('level') + 1;
const style = `style="margin-left: calc(${level} * var(--tree-indent, 20px))"`;
let inputTpl = '<li class="tree-item">';
inputTpl += `<div class="tree-item-content" ${style}>`;
inputTpl += `<input id="moduleName" class="form-control" data-id="${id}" data-parent="${parentID}" data-type="${addType}" data-lib="${lib}" data-module="${module}">`;
inputTpl += '</div></li>';
if(addType == 'same')
{
$(`div[data-id='${id}']`).before(inputTpl);
}
else
{
if(!$element.parent().hasClass('show')) $element.find('.tree-toggle-icon').trigger('click');
setTimeout(function()
{
if($element.next('.tree').length == 0) $element.after(`<menu class="tree" level="${level}" data-level="${level}"></menu>`);
$(`div[data-id='${id}']`).next('.tree').prepend(inputTpl);
}, 1);
}
setTimeout(function()
{
$('#moduleName').trigger('focus');
document.getElementById("moduleName").addEventListener('blur', saveModule);
document.getElementById("moduleName").addEventListener('keydown', function(e)
{
if(e.keyCode == 13) saveModule.call(this);
});
}, 1);
}

Some files were not shown because too many files have changed in this diff Show More