Merge branch feature/workflowgroup of zentao/zentaopms (#11206)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
请根据项目下的`zentao-view-to-ui-guide.md`规范文档把$ARGUMENTS[0]模块的view目录下的$ARGUMENTS[1]页面重构为现代化UI页面。
|
||||
@@ -0,0 +1 @@
|
||||
请根据项目下的`zentao-ui-test-guide.md`规范文档生成$ARGUMENTS[0]模块的control.php文件中的$ARGUMENTS[1]方法的UI自动化测试脚本。
|
||||
@@ -0,0 +1 @@
|
||||
请根据项目下的`zentao-unit-test-guide.md`规范文档生成$ARGUMENTS[0]模块的$ARGUMENTS[1].php文件中的$ARGUMENTS[2]方法的单元测试脚本。
|
||||
@@ -0,0 +1,39 @@
|
||||
请分析$ARGUMENTS[0]模块的$ARGUMENTS[1].php中的$ARGUMENTS[2]方法,帮我完善它的单元测试脚本。
|
||||
请遵循以下步骤:
|
||||
1.当前的分支为开发分支。拉取开发分支的最新代码。
|
||||
2.使用测试分支完善单元测试脚本:
|
||||
2.1 测试分支命名规则为test/$ARGUMENTS[0]/$ARGUMENTS[1]/$ARGUMENTS[2]。
|
||||
2.2 如果测试分支不存在则基于开发分支创建测试分支,如果测试分支存在则切换到测试分支。
|
||||
2.3 后续工作全部在测试分支上完成。
|
||||
3.理解$ARGUMENTS[2]方法的业务逻辑。
|
||||
4.分析$ARGUMENTS[2]方法涉及的数据库表,获得每张表对应的字段和数据类型。表结构定义请参考db/zentao.sql。
|
||||
5.生成测试数据文件:
|
||||
5.1 测试数据文件使用yaml格式。
|
||||
5.2 测试数据文件的语法定义请参考.claude/zendata.yaml。
|
||||
5.3 测试数据文件的基本定义请参考test/data目录下的yaml文件。
|
||||
5.4 生成测试数据文件时只需要定义必要的字段,测试框架会把生成的测试数据文件和基本定义文件合并。
|
||||
5.5 测试数据文件生成到module/$ARGUMENTS[0]/test/$ARGUMENTS[1]/yaml/$ARGUMENTS[2]目录下。
|
||||
5.6 测试数据文件的作者标记为Claude Code。
|
||||
6.生成测试数据:
|
||||
6.1 单元测试脚本中使用`zenData()->loadYaml()->gen()`加载测试数据文件来生成测试数据。
|
||||
6.2 禁止使用调用zenData()方法返回PHP对象然后执行链式调用设置range的方式生成测试数据。
|
||||
7.生成单元测试步骤:
|
||||
7.1 使用r()、p()、e()方法生成单元测试步骤。
|
||||
7.2 单元测试步骤要覆盖方法中的所有代码。
|
||||
7.3 单元测试步骤不少于5条。
|
||||
7.4 单元测试步骤的注释使用行尾注释。
|
||||
8.单元测试脚本保存路径为module/$ARGUMENTS[0]/test/$ARGUMENTS[1]/claude_$ARGUMENTS[2].php,文件名小写。
|
||||
9.生成单元测试脚本后使用php命令执行该脚本,如果有报错分析并修复。
|
||||
10.验证单元测试脚本:
|
||||
10.1 使用test/runtime/ztf执行脚本。
|
||||
10.2 执行后输出结果通过数为1,失败数和忽略数为0表示单元测试通过。
|
||||
10.3 如果单元测试没通过根据输出结果修复。
|
||||
10.4 重复上述步骤直到单元测试通过。
|
||||
11.提交代码:
|
||||
11.1 单元测试脚本验证通过后提交代码到本地仓库。
|
||||
11.2 提交代码时使用英文生成提交信息。
|
||||
11.3 提交信息以"symbol [misc] "开头,如果本次提交只新增代码则symbol为+,只删除代码则symbol为-,否则为\*。
|
||||
11.2 拉取开发分支的最新代码。
|
||||
11.3 把测试分支变基到开发分支。
|
||||
11.4 推送测试分支到远程仓库。
|
||||
11.5 切换回开发分支。
|
||||
@@ -0,0 +1,75 @@
|
||||
## 角色定义
|
||||
|
||||
你是一个软件开发工程师,禅道项目管理软件的核心开发者。禅道项目管理软件经过一个大的重构,现在你将删除重构后不再需要保留的文件。
|
||||
|
||||
## 处理流程
|
||||
|
||||
1.编写一个php脚本处理这些工作。
|
||||
2.module目录下的每个一级目录代表一个模块。
|
||||
3.不处理ai、common、file、search和transfer模块。
|
||||
4.检查每个模块下是否同时存在view目录和ui目录,如果没有同时存在则不处理。
|
||||
5.检查每个模块下的view目录里的文件是否在当前模块下的ui目录中存在同名文件,如果存在则删除view目录下的文件。注意:sendmail.html.php文件除外。
|
||||
6.如果view目录里的文件被全部删除则删除view目录。
|
||||
7.解析模块名和方法名的中文名称:
|
||||
- 目录名代表模块名,文件名中除去.html.php外的部分代表方法名。
|
||||
- 读取common目录下的lang目录中的zh-cn.php文件的内容。
|
||||
- 读取每一个目录下的lang目录中的zh-cn.php文件的内容。
|
||||
- 读取文件时可能遇到一些未知的PHP变量,下面是一部分参考:
|
||||
- $lang->ERCommon = '业务需求'
|
||||
- $lang->URCommon = '用户需求'
|
||||
- $lang->SRCommon = '软件需求'
|
||||
- $lang->productCommon = '产品'
|
||||
- $lang->projectCommon = '项目'
|
||||
- $lang->executionCommon = '执行'
|
||||
- $lang->execution->common = '执行'
|
||||
- $lang->mr->common = '合并请求'
|
||||
- $lang->common->story = '需求'
|
||||
- 模块名的中文名称在语言文件中的定义为'$lang->模块名->common'。
|
||||
- 方法名的中文名称在语言文件中的定义为'$lang->模块名->方法名'。
|
||||
- 把读取到的语言项定义转换为小写后和文件名比对,两者一致时语言项定义即为方法名的中文名称。
|
||||
- 以task模块为例:
|
||||
- 模块名为'task',在common/lang/zh-cn.php中存在定义"$lang->task->common = '任务';",则模块名的中文名称为'任务'。
|
||||
- view目录下的文件名为'create.html.php',则方法名为'create',在task/lang/zh-cn.php中存在定义'$lang->task->create = "建任务";',则方法名的中文名称为'建任务'。
|
||||
- view目录下的文件名为'batchcreate.html.php',则方法名为'batchcreate',在task/lang/zh-cn.php中存在定义'$lang->task->batchCreate = "批量创建";',则方法名的中文名称为'批量创建'。
|
||||
- block模块以block结尾的方法名需要特殊处理:
|
||||
- block模块lang目录下的zh-cn.php中定义了数组$lang->block->default。
|
||||
- 提取该数组每一行定义的元素的title和code。
|
||||
- 把code和'block'拼接后和方法名匹配,如果匹配成功以title作为方法名的中文名称。
|
||||
- 以block模块view目录下的projectdynamicblock.html.php文件为例:
|
||||
- 方法名为'projectdynamicblock'。
|
||||
- 在block/lang/zh-cn.php中存在定义"$lang->block->default['waterfallproject'][] = array('title' => '最新动态', 'module' => 'waterfallproject', 'code' => 'projectdynamic', 'width' => '1');",提取到title='最新动态',code='projectdynamic'。
|
||||
- code和'block'拼接后和方法名一致,则方法名的中文名称为'最新动态'。
|
||||
- 如果没有解析到模块名和方法名的中文名称,需要询问我如何处理。
|
||||
8.输出一个csv文件到/tmp目录下:
|
||||
- 文件内容支持中文显示,文件名以'YYYYmmdd_HHiiss'格式包含当前日期和时间。
|
||||
- 分别列出删除的模块名、方法名、模块名的中文名称、方法名的中文名称和被删除文件在本项目中的相对路径。
|
||||
- 文件内容中如存在以下内容需要替换:
|
||||
- '$lang->SRCommon'替换为软件需求'。
|
||||
- '$lang->productCommon'替换为'产品'。
|
||||
- '$lang->executionCommon'替换为'执行'。
|
||||
- '$lang->execution->common'替换为'执行'。
|
||||
- '$lang->URCommon'替换为'用户需求'。
|
||||
- '$lang->mr->common'替换为'合并请求'。
|
||||
- '$lang->projectCommon'替换为'项目'。
|
||||
- '{'、'}'、' '、'.'、'"'和"'"替换为空。
|
||||
9.输出php脚本的路径和csv文件的路径。
|
||||
|
||||
## 调试模式
|
||||
|
||||
在脚本中添加详细的调试输出,显示解析过程
|
||||
|
||||
## 测试验证
|
||||
|
||||
在生成最终报告前,先测试几个已知模块的解析结果
|
||||
|
||||
## 错误处理
|
||||
|
||||
当解析失败时提供更清晰的错误信息
|
||||
|
||||
## 日志记录
|
||||
|
||||
记录解析过程中的异常情况
|
||||
|
||||
## 清除临时文件
|
||||
|
||||
处理过程中生成的脚本和csv文件可以保留,其他临时文件应该删除。
|
||||
@@ -0,0 +1,150 @@
|
||||
title: zendata数据配置语法说明
|
||||
desc:
|
||||
|
||||
# 文件组成
|
||||
|
||||
# zendata以yaml格式的文件来定义各个字段的格式。
|
||||
# yaml文件整体由文件说明和字段定义两部分组成。
|
||||
|
||||
# 文件说明
|
||||
|
||||
# title: 标题,可以用简短的文字概要描述该文件定义的数据类型。
|
||||
# desc: 描述,可以用多行文本来详细描述该文件定义的数据类型,非必选项。
|
||||
# author: 作者,非必选项。
|
||||
# version:版本号,非必选项。
|
||||
|
||||
# 字段列表
|
||||
|
||||
# 字段定义部分都放在fields这个定义里面。
|
||||
# 一个yaml文件可以包含一个或者多个字段。
|
||||
# 字段列表以-field定义开始。
|
||||
# 一个字段可以通过fields属性定义它的子字段。
|
||||
|
||||
# 字段定义
|
||||
|
||||
# field: 字段名,仅支持英文、数字、下换线和.
|
||||
# range: 列表范围,最重要的定义。
|
||||
# loop: 循环次数,可以定义某一字段循环多少次。
|
||||
# loopfix: 每一次循环时的连接符。
|
||||
|
||||
# format: 支持格式化输出。
|
||||
|
||||
# prefix: 该字段的前缀。
|
||||
# postfix: 该字段的后缀。
|
||||
|
||||
# length: 该字段的长度。如果不通过分隔符区分,则需要指定字段长度,单位是字节。
|
||||
# leftpad: 左填充的字符。如果长度不够,可指定左填充的字符。默认是以空格左填充。
|
||||
# rightpad: 右填充的字符。如果长度不够,可指定右填充的字符。
|
||||
|
||||
# config: 可以引用另外一个文件里面的定义。
|
||||
|
||||
# from: 引用某一个定义文件。
|
||||
# use: 使用被引用文件中定义的若干实例。all代表使用所有。
|
||||
# select: 如果引用的文件是excel表,可以查询里面的某一个字段。
|
||||
# where: 如果引用的文件是excel表,可以使用查询条件。
|
||||
|
||||
# loop定义
|
||||
|
||||
# 可以使用一个数字来指定字段循环的次数,比如loop:2。
|
||||
# 可以使用区间来定义字段循环的次数。比如loop:2-10。
|
||||
|
||||
# range定义
|
||||
|
||||
# 使用逗号连接不同的元素。比如 range: 1,2,3。
|
||||
# 元素也可以是一个区间。比如 range:1-10, A-Z。
|
||||
# 区间可以通过冒号:来指定步长。比如 range:1-10:2。
|
||||
# 步长可以是小数。比如 range: 1-10:0.1。
|
||||
# 步长可以是负数。比如 range:100-1:-1。
|
||||
# 区间可以通过R来指定随机。比如 range: 1-10:R,随机和步长只能二选一。
|
||||
# 可以通过一个文件来指定列表。比如range: list.txt。文件名是相对路径时,以配置文件为基准计算。
|
||||
# 可以通过{n}的方式来重复某一个元素。比如 range: user1{100},user2{100}
|
||||
# 如果区间或者几个元素需要重复,需要用[]括起来。比如 range: [user1,user2,user3]{100}
|
||||
|
||||
author: zentao
|
||||
version: 1.0
|
||||
|
||||
fields:
|
||||
|
||||
- field: field_common # 默认的列表类型,通过逗号隔成若干区间。
|
||||
range: 1-10, 20-25, 27, 29, 30 # 1,2,3...,10,20,21,22...,25,27,29.30
|
||||
prefix: "" # 前缀
|
||||
postfix: "\t" # 后缀,特殊字符加引号,否则无法解析。
|
||||
|
||||
- field: field_step # 区间指定步长。
|
||||
range: 1-10:2, 1-2:0.1 # 1,3,5,7,9,1, 1.1,1.2...,2
|
||||
postfix: "\t"
|
||||
|
||||
- field: field_random # 区间指定随机。随机属性R同步长不能同时出现。
|
||||
range: 1-10:R # 1,5,8...
|
||||
postfix: "\t"
|
||||
|
||||
- field: field_loop # 自循环的字段。
|
||||
range: a-z # a|b|c ...
|
||||
loop: 3 # 循环三次
|
||||
loopfix: _ # 每次循环的连接符。
|
||||
postfix: "\t"
|
||||
|
||||
- field: field_repeat # 通过{}定义重复的元素。
|
||||
range: user-1{3},[user2,user3]{2} # user-1,user-1,user-1,user2,user2,user3,user3
|
||||
postfix: "\t"
|
||||
|
||||
- field: field_format # 通过格式化字符串输出。
|
||||
range: 1-10 # passwd 1,passwd 2,passwd 3 ... passwd10。
|
||||
format: "passwd%02d" # 用%02d补零,使密码整体保持8位。
|
||||
postfix: "\t"
|
||||
|
||||
- field: field_length # 指定宽度。
|
||||
range: 1-99 # 01\t,02\t,03\t..., 99\t
|
||||
length: 3 # 包含前后缀的宽度。
|
||||
leftpad: 0 # 宽度不够时,补充的字符。
|
||||
postfix: "\t"
|
||||
|
||||
- field: field_text # 从一个文件中随机读取。
|
||||
range: users.txt:R # 相对当前文件路径。
|
||||
postfix: "\t"
|
||||
|
||||
- field: field_yaml # 引用其他的定义文件整体内容。
|
||||
range: test/test-nested2.yaml{3} # 相对当前文件路径。
|
||||
postfix: "\t"
|
||||
|
||||
- field: field_use_config # 引用其他的config定义文件。
|
||||
config: number.yaml # 相对当前文件路径,config内包含单个字段。
|
||||
postfix: "\t"
|
||||
|
||||
- field: field_use_ranges # 引用內置的定义文件,该文件定义了多个range,他们共享了一些field层面的属性。
|
||||
from: zentao.number.v1.yaml # 引用yaml/zentao/number/v1.yaml文件里面的ranges定义。
|
||||
use: medium # 使用该文件中定义的medium分组。
|
||||
postfix: "\t"
|
||||
|
||||
- field: field_use_instance # 引用其他的定义文件,该文件定义了多个实例。
|
||||
from: ip.v1.yaml # yaml/ip/v1.yaml
|
||||
use: privateC,privateB # 使用该文件中定义的privateC和privateB两个实例。
|
||||
postfix: "\t"
|
||||
|
||||
- field: field_use_excel # 从excel数据源里面取数据。
|
||||
from: address.cn.v1.china # 从data/address/v1.xlsx文件中读取名为china的工作簿。
|
||||
select: city # 查询city字段。
|
||||
where: state like '%山东%' # 条件是省份包含山东。
|
||||
rand: true # 随机取数据
|
||||
postfix: "\t"
|
||||
|
||||
- field: field_with_children # 字段多层嵌套
|
||||
fields:
|
||||
- field: child1
|
||||
range: a-z
|
||||
prefix: part1_
|
||||
postfix: '|'
|
||||
|
||||
- field: child2
|
||||
range: A-Z
|
||||
prefix: part2_
|
||||
postfix: '|'
|
||||
|
||||
- field: child_with_child
|
||||
prefix: part3_
|
||||
postfix:
|
||||
fields:
|
||||
- field: field_grandson
|
||||
prefix: int_
|
||||
range: 10-20
|
||||
postfix:
|
||||
@@ -21,6 +21,7 @@ extension/custom/*
|
||||
test/data/sql/
|
||||
.bak
|
||||
.idea
|
||||
.tree
|
||||
.vscode
|
||||
.gitignore
|
||||
.DS_Store
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Interactive
|
||||
Always reply in Chinese.
|
||||
|
||||
## Project Overview
|
||||
|
||||
ZenTao is a comprehensive, open-source project management software written in PHP that covers the main PM process from product and project management to quality management, documentation management, organization management, and office management. It follows a modular MVC architecture pattern.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Structure
|
||||
- **framework/**: Core framework classes (control, model, router, helper)
|
||||
- **module/**: Modular architecture with each module containing:
|
||||
- `control.php` - Controller logic
|
||||
- `model.php` - Data layer and business logic
|
||||
- `zen.php` - New architecture layer (when present)
|
||||
- `tao.php` - Extended business logic layer (when present)
|
||||
- `config/` - Module-specific configuration
|
||||
- `lang/` - Internationalization files
|
||||
- `view/` - Traditional view templates
|
||||
- `ui/` - Modern UI components
|
||||
- `css/` and `js/` - Frontend assets
|
||||
- **lib/**: Third-party libraries and utility classes
|
||||
- **config/**: Global configuration files
|
||||
- **www/**: Web entry point and public assets
|
||||
- **db/**: Database schemas and migration scripts
|
||||
- **extension/**: Extension system for customization
|
||||
|
||||
### Database
|
||||
- Uses MySQL/MariaDB with custom DAO layer in `lib/dao/`
|
||||
- Database schemas in `db/` directory with versioned SQL files
|
||||
- Supports multiple database engines including experimental DuckDB support
|
||||
|
||||
### Frontend
|
||||
- Mix of traditional PHP templates and modern UI components
|
||||
- Uses ZUI framework (custom UI library)
|
||||
- jQuery-based JavaScript
|
||||
- CSS organized per module
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Build System
|
||||
```bash
|
||||
# Full build process
|
||||
make all
|
||||
|
||||
# Clean build artifacts
|
||||
make clean
|
||||
|
||||
# Common build (core functionality)
|
||||
make common
|
||||
|
||||
# Package for distribution
|
||||
make package
|
||||
|
||||
# Create distribution packages
|
||||
make pms # Standard package
|
||||
make ci # CI build with all packages
|
||||
```
|
||||
|
||||
### Testing
|
||||
The project uses a custom testing framework located in `test/`:
|
||||
```bash
|
||||
# Run tests (navigate to test directory first)
|
||||
cd test
|
||||
php spider.php
|
||||
|
||||
# UI testing configuration available in test/config/config.php
|
||||
```
|
||||
|
||||
### Code Quality
|
||||
- PHP compatibility checks via `misc/compatibility/`
|
||||
- Downgrade scripts for PHP version compatibility in `misc/rector/`
|
||||
- Code minification: `php misc/minifyfront.php`
|
||||
|
||||
## Key Modules
|
||||
|
||||
### Core Business Modules
|
||||
- **product/**: Product management
|
||||
- **project/**: Project management (includes execution/)
|
||||
- **story/**: User story management
|
||||
- **task/**: Task management
|
||||
- **bug/**: Bug tracking
|
||||
- **testcase/**: Test case management
|
||||
- **build/**: Build management
|
||||
|
||||
### Administrative Modules
|
||||
- **user/**: User management
|
||||
- **group/**: Permission groups
|
||||
- **company/**: Organization management
|
||||
- **dept/**: Department structure
|
||||
|
||||
### Integration Modules
|
||||
- **git/, gitlab/, gitea/, gogs/**: Source control integration
|
||||
- **jenkins/**: CI/CD integration
|
||||
- **api/**: API management
|
||||
- **webhook/**: Webhook support
|
||||
|
||||
### Reporting & Analytics
|
||||
- **report/**: Standard reports
|
||||
- **chart/**: Charting functionality
|
||||
- **metric/**: Metrics calculation
|
||||
- **bi/**: Business intelligence
|
||||
|
||||
## Configuration
|
||||
|
||||
### Database Configuration
|
||||
- Main config: `config/config.php`
|
||||
- Database settings typically in `config/my.php` (not tracked)
|
||||
|
||||
### Extension System
|
||||
- Custom extensions in `extension/custom/`
|
||||
- Configuration extensions in `config/ext/`
|
||||
|
||||
### Internationalization
|
||||
- Language files in each module's `lang/` directory
|
||||
- Supported languages: zh-cn, zh-tw, en, de, fr
|
||||
|
||||
## File Patterns
|
||||
|
||||
### Naming Conventions
|
||||
- Controllers: `control.php`
|
||||
- Models: `model.php`
|
||||
- Views: `view/*.html.php`
|
||||
- UI Components: `ui/*.html.php`
|
||||
- Configurations: `config/*.php`
|
||||
- Language files: `lang/{locale}.php`
|
||||
|
||||
### Architecture Layers
|
||||
- **zen.php**: New architecture implementation
|
||||
- **tao.php**: Business logic extension layer
|
||||
- Traditional MVC for legacy code
|
||||
|
||||
## Development Notes
|
||||
|
||||
### PHP Requirements
|
||||
- Minimum PHP 5.6, supports up to PHP 8.1+
|
||||
- Uses strict types declaration in newer files
|
||||
- Extensive use of custom framework classes
|
||||
|
||||
### Security
|
||||
- Custom authentication and authorization system
|
||||
- Input filtering via `lib/filter/`
|
||||
- SQL injection protection through DAO layer
|
||||
|
||||
### Performance
|
||||
- Built-in caching system in `lib/cache/`
|
||||
- Database query optimization
|
||||
- File-based session management
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Adding New Features
|
||||
1. Create module directory structure in `module/`
|
||||
2. Implement controller, model, and views
|
||||
3. Add language files for internationalization
|
||||
4. Configure routing if needed
|
||||
5. Add database tables via SQL files in `db/`
|
||||
|
||||
### Database Changes
|
||||
1. Add migration SQL to `db/update*.sql`
|
||||
2. Update schema in `db/zentao.sql`
|
||||
3. Test with different database engines if needed
|
||||
|
||||
### Testing Changes
|
||||
1. Add test data to `test/data/`
|
||||
2. Create test cases using the custom framework
|
||||
3. Run compatibility checks for PHP versions
|
||||
|
||||
This codebase represents a mature, enterprise-level project management system with extensive customization capabilities and multi-language support.
|
||||
@@ -121,7 +121,7 @@ zentaoxx:
|
||||
sed -i "s/text NOT NULL DEFAULT ''/text NULL/" zentaoxx/db_bak/*.sql
|
||||
sed -i "s/text NOT NULL/text NULL/" zentaoxx/db_bak/*.sql
|
||||
sed -i "s/ENGINE=MyISAM/ENGINE=InnoDB/" zentaoxx/db_bak/*.sql
|
||||
sed -i "s/utf8/utf8mb4/" zentaoxx/db_bak/*.sql
|
||||
sed -i "s/utf8/utf8mb4/g" zentaoxx/db_bak/*.sql
|
||||
sed -i "/`xxb_user` ADD `clientStatus`/d; /`xxb_user` ADD `clientLang`/d; /`xxb_file` CHANGE `pathname`/d" zentaoxx/db_bak/xuanxuan.sql
|
||||
cp zentaoxx/db_bak/upgradexuanxuan*.sql zentaoxx/db_bak/xuanxuan.sql zentaoxx/db/
|
||||
rm -rf zentaoxx/db_bak/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"pkg": {
|
||||
"xuanxuan": {
|
||||
"gitVersion": "02da3cc63dd6a5b60d04726a97578646178ee3e6",
|
||||
"gitVersion": "6c3558721cc2c49edd0ba4b2c9a5734413150e71",
|
||||
"version": "9.3"
|
||||
},
|
||||
"zentaoext": {
|
||||
|
||||
+4
-1
@@ -16,7 +16,7 @@ if(!class_exists('config')){class config{}}
|
||||
if(!function_exists('getWebRoot')){function getWebRoot(){}}
|
||||
|
||||
/* 基本设置。Basic settings. */
|
||||
$config->version = '21.7.4'; // ZenTaoPHP的版本。 The version of ZenTaoPHP. Don't change it.
|
||||
$config->version = '21.7.5'; // 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.
|
||||
@@ -176,6 +176,9 @@ $config->timeout = 30000;
|
||||
$config->maxCount = 500;
|
||||
$config->moreLinks = array();
|
||||
|
||||
/* Docker Compose设置。 */
|
||||
$config->inCompose = strtolower((string)getenv('PROVIDER')) == 'docker';
|
||||
|
||||
/* 渠成平台设置。CNE Api settings. */
|
||||
$config->inQuickon = strtolower((string)getenv('IN_QUICKON')) == 'true';
|
||||
$config->inContainer = strtolower((string)getenv('IS_CONTAINER')) == 'true' || strtolower((string)getenv('IN_CONTAINER')) == 'true';
|
||||
|
||||
@@ -80,6 +80,7 @@ $filter->kanban = new stdclass();
|
||||
$filter->group = new stdclass();
|
||||
$filter->convert = new stdclass();
|
||||
$filter->admin = new stdclass();
|
||||
$filter->install = new stdclass();
|
||||
|
||||
$filter->index->index = new stdclass();
|
||||
$filter->block->default = new stdclass();
|
||||
@@ -112,6 +113,7 @@ $filter->api->edit = new stdClass();
|
||||
$filter->mail->ztcloud = new stdclass();
|
||||
$filter->mail->batchdelete = new stdclass();
|
||||
$filter->misc->checkupdate = new stdclass();
|
||||
$filter->misc->installevent = new stdclass();
|
||||
$filter->file->download = new stdclass();
|
||||
$filter->product->browse = new stdclass();
|
||||
$filter->product->all = new stdclass();
|
||||
@@ -210,6 +212,7 @@ $filter->admin->register = new stdclass();
|
||||
$filter->admin->getcaptcha = new stdclass();
|
||||
$filter->admin->sendcode = new stdclass();
|
||||
$filter->admin->giftpackage = new stdclass();
|
||||
$filter->install->step5 = new stdclass();
|
||||
|
||||
$filter->index->index->get['open'] = 'reg::base64';
|
||||
|
||||
@@ -453,6 +456,10 @@ $filter->mail->batchdelete->get['idList'] = 'reg::idList';
|
||||
$filter->misc->checkupdate->get['browser'] = 'code';
|
||||
$filter->misc->checkupdate->get['note'] = 'reg::base64';
|
||||
|
||||
$filter->misc->installevent->cookie['sn'] = 'string';
|
||||
|
||||
$filter->install->step5->cookie['sn'] = 'string';
|
||||
|
||||
$filter->sso->getbindusers->get['hash'] = 'reg::md5';
|
||||
$filter->sso->gettodolist->get['hash'] = 'reg::md5';
|
||||
$filter->sso->getuserpairs->get['hash'] = 'reg::md5';
|
||||
|
||||
@@ -210,6 +210,7 @@ $config->openMethods[] = 'api.getsessionid';
|
||||
$config->openMethods[] = 'misc.captcha';
|
||||
$config->openMethods[] = 'misc.checkupdate';
|
||||
$config->openMethods[] = 'misc.ajaxsendevent';
|
||||
$config->openMethods[] = 'misc.installevent';
|
||||
$config->openMethods[] = 'sso.bind';
|
||||
$config->openMethods[] = 'sso.feishuauthen';
|
||||
$config->openMethods[] = 'sso.feishulogin';
|
||||
@@ -788,7 +789,7 @@ $config->featureGroup->other = array('devops', 'kanban', 'setCode');
|
||||
$config->db->sqliteBlacklist = array('sqlite_queue', 'cron');
|
||||
$config->hasDropmenuApps = array('program', 'project', 'product', 'execution', 'qa', 'admin', 'bi', 'feedback', 'demandpool');
|
||||
$config->hasBranchMenuModules = array('product', 'story', 'release', 'bug', 'testcase', 'testtask', 'branch', 'tree', 'workflowgroup', 'deliverable', 'process', 'activity');
|
||||
$config->excludeBranchMenu = array('product-dashboard', 'product-view');
|
||||
$config->excludeBranchMenu = array('product-dashboard', 'product-view', 'product-whitelist', 'product-addwhitelist', 'branch-manage', 'branch-batchedit');
|
||||
$config->excludeDropmenuList = array('program-browse', 'program-productview', 'program-kanban', 'product-all', 'product-index', 'product-kanban', 'project-kanban', 'execution-all', 'execution-executionkanban', 'project-browse', 'project-template', 'project-createtemplate', 'project-batchedit', 'product-batchedit', 'admin-index', 'product-create', 'project-create', 'execution-create', 'program-create', 'execution-batchedit', 'metric-preview', 'metric-browse', 'metric-view', 'metriclib-browse', 'qa-index', 'caselib-create', 'feedback-batchedit', 'feedback-batchclose', 'feedback-showimport', 'dimension-browse', 'dataview-browse', 'dataview-create', 'dataview-query', 'ticket-batchedit', 'ticket-batchfinish', 'ticket-batchactivate', 'file-download');
|
||||
$config->hasSwitcherModules = array('design');
|
||||
$config->hasSwitcherMethods = array('project-bug', 'project-testcase', 'execution-bug', 'execution-testcase', 'testtask-cases', 'testtask-view', 'testtask-report', 'testtask-groupcase', 'testtask-linkcase');
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
ALTER TABLE `zt_case` CHANGE `order` `order` mediumint(8) unsigned NOT NULL DEFAULT '0';
|
||||
|
||||
CREATE INDEX `parent` ON `zt_kanbancolumn`(`parent`);
|
||||
CREATE INDEX `group` ON `zt_kanbancolumn`(`group`);
|
||||
CREATE INDEX `execution` ON `zt_kanbanlane`(`execution`);
|
||||
CREATE INDEX `group` ON `zt_kanbanlane`(`group`);
|
||||
CREATE INDEX `lane` ON `zt_kanbancell`(`lane`);
|
||||
CREATE INDEX `feedback` ON `zt_bug`(`feedback`);
|
||||
CREATE INDEX `feedback` ON `zt_story`(`feedback`);
|
||||
CREATE INDEX `feedback` ON `zt_task`(`feedback`);
|
||||
CREATE INDEX `feedback` ON `zt_ticket`(`feedback`);
|
||||
CREATE INDEX `feedback` ON `zt_todo`(`feedback`);
|
||||
+11
-1
@@ -401,6 +401,7 @@ CREATE INDEX `deleted` ON `zt_bug`(`deleted`);
|
||||
CREATE INDEX `project` ON `zt_bug`(`project`);
|
||||
CREATE INDEX `product_status_deleted` ON `zt_bug`(`product`,`status`,`deleted`);
|
||||
CREATE INDEX `idx_repo` ON `zt_bug`(`repo`);
|
||||
CREATE INDEX `feedback` ON `zt_bug`(`feedback`);
|
||||
|
||||
-- DROP TABLE IF EXISTS `zt_build`;
|
||||
CREATE TABLE IF NOT EXISTS `zt_build` (
|
||||
@@ -472,7 +473,7 @@ CREATE TABLE IF NOT EXISTS `zt_case` (
|
||||
`subStatus` varchar(30) NOT NULL default '',
|
||||
`color` char(7) NOT NULL DEFAULT '',
|
||||
`frequency` enum('1','2','3') NOT NULL default '1',
|
||||
`order` tinyint(30) unsigned NOT NULL default '0',
|
||||
`order` mediumint(8) unsigned NOT NULL default '0',
|
||||
`openedBy` char(30) NOT NULL default '',
|
||||
`openedDate` datetime NULL,
|
||||
`reviewedBy` varchar(255) NOT NULL DEFAULT '',
|
||||
@@ -1230,6 +1231,7 @@ CREATE TABLE IF NOT EXISTS `zt_kanbancell` (
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
CREATE UNIQUE INDEX `card_group` ON `zt_kanbancell`(`kanban`,`type`,`lane`,`column`);
|
||||
CREATE INDEX `lane` ON `zt_kanbancell`(`lane`);
|
||||
|
||||
-- DROP TABLE IF EXISTS `zt_kanbangroup`;
|
||||
CREATE TABLE IF NOT EXISTS `zt_kanbangroup` (
|
||||
@@ -1256,6 +1258,8 @@ CREATE TABLE IF NOT EXISTS `zt_kanbanlane` (
|
||||
`deleted` enum('0','1') NOT NULL default '0',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
CREATE INDEX `execution` ON `zt_kanbanlane`(`execution`);
|
||||
CREATE INDEX `group` ON `zt_kanbanlane`(`group`);
|
||||
|
||||
-- DROP TABLE IF EXISTS `zt_kanbancolumn`;
|
||||
CREATE TABLE IF NOT EXISTS `zt_kanbancolumn` (
|
||||
@@ -1272,6 +1276,8 @@ CREATE TABLE IF NOT EXISTS `zt_kanbancolumn` (
|
||||
`deleted` enum('0','1') NOT NULL default '0',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
CREATE INDEX `parent` ON `zt_kanbancolumn`(`parent`);
|
||||
CREATE INDEX `group` ON `zt_kanbancolumn`(`group`);
|
||||
|
||||
-- DROP TABLE IF EXISTS `zt_lang`;
|
||||
CREATE TABLE IF NOT EXISTS `zt_lang` (
|
||||
@@ -2024,6 +2030,7 @@ CREATE INDEX `product` ON `zt_story` (`product`);
|
||||
CREATE INDEX `root` ON `zt_story` (`root`);
|
||||
CREATE INDEX `status` ON `zt_story` (`status`);
|
||||
CREATE INDEX `assignedTo` ON `zt_story` (`assignedTo`);
|
||||
CREATE INDEX `feedback` ON `zt_story` (`feedback`);
|
||||
|
||||
-- DROP TABLE IF EXISTS `zt_storygrade`;
|
||||
CREATE TABLE IF NOT EXISTS `zt_storygrade` (
|
||||
@@ -2156,6 +2163,7 @@ CREATE INDEX `parent` ON `zt_task` (`parent`);
|
||||
CREATE INDEX `path` ON `zt_task` (`path`);
|
||||
CREATE INDEX `assignedTo` ON `zt_task` (`assignedTo`);
|
||||
CREATE INDEX `order` ON `zt_task` (`order`);
|
||||
CREATE INDEX `feedback` ON `zt_task` (`feedback`);
|
||||
|
||||
-- DROP TABLE IF EXISTS `zt_taskestimate`;
|
||||
CREATE TABLE IF NOT EXISTS `zt_taskestimate` (
|
||||
@@ -2361,6 +2369,7 @@ CREATE INDEX `account` ON `zt_todo` (`account`);
|
||||
CREATE INDEX `assignedTo` ON `zt_todo` (`assignedTo`);
|
||||
CREATE INDEX `finishedBy` ON `zt_todo` (`finishedBy`);
|
||||
CREATE INDEX `date` ON `zt_todo` (`date`);
|
||||
CREATE INDEX `feedback` ON `zt_todo` (`feedback`);
|
||||
|
||||
-- DROP TABLE IF EXISTS `zt_user`;
|
||||
CREATE TABLE IF NOT EXISTS `zt_user` (
|
||||
@@ -2842,6 +2851,7 @@ CREATE TABLE IF NOT EXISTS `zt_ticket` (
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
CREATE INDEX `product` ON `zt_ticket` (`product`);
|
||||
CREATE INDEX `feedback` ON `zt_ticket` (`feedback`);
|
||||
|
||||
-- DROP TABLE IF EXISTS `zt_ticketsource`;
|
||||
CREATE TABLE IF NOT EXISTS `zt_ticketsource` (
|
||||
|
||||
@@ -1,3 +1,87 @@
|
||||
2025-08-22 21.7.5
|
||||
完成的需求
|
||||
开源版:
|
||||
77398 源码包安装流程中增加埋点
|
||||
75100 文档编辑器增加对齐功能
|
||||
企业版:
|
||||
76672 重构文档历史版本对比功能
|
||||
修复的Bug
|
||||
开源版:
|
||||
46933 【20版本】点击搜索按钮后搜索表单会消失
|
||||
54900 测试下批量建用例超过50个,点击保存提示用例类型不能为空
|
||||
55125 任务列表下进入需求返回页面不是任务列表 (Feedback#7052)
|
||||
55259 测试用例列表页点击相关研发需求后,详情页点击返回时没有返回到用例列表
|
||||
55577 模块名称中不能输入空格 (Feedback#7177)
|
||||
64109 批量编辑产品页面负责人字段无法回显也无法选择 (Feedback#9853)
|
||||
64121 首页-待处理-点击待评审需求提示app not found。 (Feedback#9665)
|
||||
64389 删除项目后并没有删除该项目下相关的目标评审 (Feedback#9988)
|
||||
64400 合并产品分支报错 (Feedback#9987)
|
||||
64473 用例库导出用例报错 (Feedback#10004)
|
||||
64493 项目和执行下编辑测试单,点击返回,返回的页面不正确 (Feedback#10041)
|
||||
64516 项目概况中的项目描述过多时显示不全 (Feedback#10045)
|
||||
64528 视野权限取消勾选devops后管理应用按钮也会消失 (Feedback#9906)
|
||||
64529 多人并行任务指派给 在地盘待处理 不显示 (Feedback#9902)
|
||||
64549 用户没有需求提交评审权限,但是仍展示了按钮,点击弹出异常框 (Feedback#9803)
|
||||
64581 编辑需求、用例、bug日志,工时显示错误
|
||||
64599 文档编辑权限异常 (Feedback#10044)
|
||||
64623 激活Bug上传的图片在历史记录中显示是裂开的
|
||||
64637 编辑bug的模块,丢失指派给 (Feedback#10064)
|
||||
64639 Scrum教程到“点击指派给”卡住不能往下继续了 (Feedback#10085)
|
||||
64693 没有提交评审权限,需求列表显示了提交评审按钮
|
||||
64717 项目使用重新定义权限分组时不能从项目执行列表访问项目下执行
|
||||
64751 激活bug时上传截图保存后截图展示异常 (Feedback#10098)
|
||||
64776 Bug列表批量指派功能,无法通过拼音或者首字母搜索人员 (Feedback#10056)
|
||||
64787 运营管理界面分配权限报错
|
||||
64792 批量添加用户报错
|
||||
64815 用例覆盖率统计口径与用例需求关联范围不一致的问题 (Feedback#10137)
|
||||
64822 项目下搜索表单空白
|
||||
64904 批量创建时过滤了尖括号之间的内容 (Feedback#10066)
|
||||
64920 任务搜索相关研发需求的条件下拉框没有显示相关研发需求
|
||||
64923 贡献任务列表搜索框没加载显示出来
|
||||
64925 项目--迭代/阶段列表页执行搜索后,总是列出所有任务
|
||||
64927 项目--迭代/阶段列表执行搜索后重置搜索条件,没有列出所有任务
|
||||
企业版:
|
||||
39077 用户分配工作流“流程详情”权限后流程页面未显示“预览”
|
||||
55416 待处理下批量编辑反馈三级导航菜单未高亮
|
||||
58762 工作流数据关联工作流数据名称错误
|
||||
58863 组织动态页面搜索页面和前面的控件距离太近
|
||||
61129 反馈列表页左侧区块中产品名称加粗显示了
|
||||
64317 导入研发需求确认页面评审人建议增加[同上]按钮
|
||||
64468 批量编辑反馈,设置显示多个自定义字段时,字段重叠 (Feedback#10010)
|
||||
64470 项目需求导入导出权限配置错误 (Feedback#10005)
|
||||
64481 用例批量编辑与单个编辑表单数据不一致 (Feedback#10038)
|
||||
64675 配置了office后,文档详情页不显示预览按钮
|
||||
64714 预览导入的office文档在禅道中打开没有新开标签
|
||||
64763 导出项目进度没有显示百分比
|
||||
64764 导出项目的已投入列建议显示单位人天
|
||||
64824 反馈状态更新为已处理后没有发送消息通知 (Feedback#10142)
|
||||
64889 项目和迭代下的任务指派给分布饼图不一致
|
||||
64909 任务进行搜索后查看报表有报错
|
||||
旗舰版:
|
||||
24150 添加自定义项不适用某个语言时,该语言下字段显示为空了
|
||||
64338 项目评审问题列表中选择评审项时会返回已删除评审项 (Feedback#9942)
|
||||
64384 审批人必填提示信息不正确
|
||||
64480 项目甘特图下无法拖拽任务更新任务排序 (Feedback#9998)
|
||||
64484 评审消耗工时小于1的部分被清零了 (Feedback#10033)
|
||||
64492 迭代编辑更改所属项目后,迭代关联的需求没同步到新项目中 (Feedback#9959)
|
||||
64570 文档编辑器中列表中间双击不能插入新的输入行
|
||||
64584 复制项目子阶段的负责人没有同步到父阶段下
|
||||
64761 工单编辑界面编辑器还是老版本编辑器 (Feedback#10107)
|
||||
64782 升级后过程列表和活动列表每页按照左侧区块顺序显示
|
||||
64784 新建产品无法选择产品线 (Feedback#10101)
|
||||
64797 项目模板无法新增子阶段 (Feedback#10126)
|
||||
64850 上传有大小写字母的文件类型,下载文件有双后缀 (Feedback#10094)
|
||||
64873 项目模板下点击阶段的二级导航,页面报错
|
||||
64879 瀑布项目阶段列表点击甘特图按钮报错
|
||||
64919 瀑布项目甘特图搜索条件不起作用
|
||||
IPD版:
|
||||
64285 研发界面点击消息通知链接打开反馈详情,没有展示工作流自定义的字段
|
||||
64422 甘特图上的阶段和评审掉悬停时显示圆圈
|
||||
64495 PHP7.4 项目-文档下,移动文档库的顺序无效 (Feedback#9865)
|
||||
64533 执行甘特图,预计工时与消耗工时重复计算了父子任务工时 (Feedback#10031)
|
||||
64713 任务附件名称异常 (Feedback#9638)
|
||||
64916 OR界面进入地盘时有任务搜索表单的相关报错
|
||||
|
||||
2025-07-29 21.7.4
|
||||
完成的需求
|
||||
企业版:
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* 获取待审批的需求。
|
||||
* Get reviewing stories.
|
||||
*
|
||||
* @param string $orderBy
|
||||
* @param bool $checkExists
|
||||
* @access public
|
||||
* @return array|bool
|
||||
*/
|
||||
public function getReviewingStories(string $orderBy = 'id_desc', bool $checkExists = false, $type = 'story'): array|bool
|
||||
{
|
||||
$this->app->loadLang($type);
|
||||
$stories = $this->dao->select("t1.id, t1.title, 'story' AS type, t1.type AS storyType, t1.openedDate AS time, t1.status, t1.product, 0 AS project, t1.parent")->from(TABLE_STORY)->alias('t1')
|
||||
->leftJoin(TABLE_STORYREVIEW)->alias('t2')->on('t1.id = t2.story and t1.version = t2.version')
|
||||
->leftJoin(TABLE_PROJECTSTORY)->alias('t3')->on('t1.id = t3.story')
|
||||
->leftJoin(TABLE_PROJECT)->alias('t4')->on('t3.project = t4.id')
|
||||
->where('t1.deleted')->eq(0)
|
||||
->beginIF(!$this->app->user->admin)->andWhere('t1.product')->in($this->app->user->view->products)->fi()
|
||||
->andWhere('t2.reviewer')->eq($this->app->user->account)
|
||||
->andWhere('t2.result')->eq('')
|
||||
->andWhere('t1.type')->eq($type)
|
||||
->andWhere('t1.vision')->eq($this->config->vision)
|
||||
->andWhere('t1.status')->eq('reviewing')
|
||||
->andWhere('t4.deleted')->eq('0')
|
||||
->orderBy($orderBy)
|
||||
->beginIF($checkExists)->limit(1)->fi()
|
||||
->fetchAll('id');
|
||||
|
||||
if($checkExists)
|
||||
{
|
||||
return !empty($stories);
|
||||
}
|
||||
|
||||
$actions = $this->dao->select('objectID,`date`')->from(TABLE_ACTION)->where('objectType')->eq('story')->andWhere('objectID')->in(array_keys($stories))->andWhere('action')->eq('submitreview')->orderBy('`date`')->fetchPairs();
|
||||
$projects = $this->dao->select('story,project')->from(TABLE_PROJECTSTORY)->where('story')->in(array_keys($stories))->fetchPairs();
|
||||
foreach($projects as $storyID => $projectID) $stories[$storyID]->project = $projectID;
|
||||
foreach($actions as $storyID => $date) $stories[$storyID]->time = $date;
|
||||
return array_values($stories);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
include dirname(__FILE__, 7) . '/test/lib/init.php';
|
||||
include $app->getModuleRoot() . 'my/test/lib/my.unittest.class.php';
|
||||
|
||||
zenData('story')->loadYaml('story_reviewing')->gen('10');
|
||||
zenData('storyreview')->loadYaml('storyreview')->gen('10');
|
||||
zenData('user')->gen('5');
|
||||
|
||||
/**
|
||||
|
||||
title=测试 myModel->getReviewingStories();
|
||||
timeout=0
|
||||
cid=1
|
||||
|
||||
- 测试获取用户 account 排序 id_desc 的需求id。 @8,6,2
|
||||
|
||||
- 测试获取用户 account 排序 id_desc 的需求是否存在。 @exist
|
||||
- 测试获取用户 account 排序 id_asc 的需求id。 @2,6,8
|
||||
|
||||
- 测试获取用户 account 排序 id_asc 的需求是否存在。 @exist
|
||||
- 测试获取用户 account 排序 id_desc 的需求id。 @empty
|
||||
- 测试获取用户 account 排序 id_desc 的需求是否存在。 @empty
|
||||
- 测试获取用户 account 排序 id_asc 的需求id。 @empty
|
||||
- 测试获取用户 account 排序 id_asc 的需求是否存在。 @empty
|
||||
|
||||
*/
|
||||
|
||||
$account = array('admin', 'user1');
|
||||
$orderBy = array('id_desc', 'id_asc');
|
||||
$checkExist = array(false, true);
|
||||
|
||||
$my = new myTest();
|
||||
r($my->getReviewingStoriesTest($account[0], $orderBy[0], $checkExist[0])) && p() && e('empty'); // 测试获取用户 account 排序 id_desc 的需求id。
|
||||
r($my->getReviewingStoriesTest($account[0], $orderBy[0], $checkExist[1])) && p() && e('empty'); // 测试获取用户 account 排序 id_desc 的需求是否存在。
|
||||
r($my->getReviewingStoriesTest($account[0], $orderBy[1], $checkExist[0])) && p() && e('empty'); // 测试获取用户 account 排序 id_asc 的需求id。
|
||||
r($my->getReviewingStoriesTest($account[0], $orderBy[1], $checkExist[1])) && p() && e('empty'); // 测试获取用户 account 排序 id_asc 的需求是否存在。
|
||||
r($my->getReviewingStoriesTest($account[1], $orderBy[0], $checkExist[0])) && p() && e('empty'); // 测试获取用户 account 排序 id_desc 的需求id。
|
||||
r($my->getReviewingStoriesTest($account[1], $orderBy[0], $checkExist[1])) && p() && e('empty'); // 测试获取用户 account 排序 id_desc 的需求是否存在。
|
||||
r($my->getReviewingStoriesTest($account[1], $orderBy[1], $checkExist[0])) && p() && e('empty'); // 测试获取用户 account 排序 id_asc 的需求id。
|
||||
r($my->getReviewingStoriesTest($account[1], $orderBy[1], $checkExist[1])) && p() && e('empty'); // 测试获取用户 account 排序 id_asc 的需求是否存在。
|
||||
@@ -12,6 +12,7 @@
|
||||
* May you find forgiveness for yourself and forgive others.
|
||||
* May you share freely, never taking more than you give.
|
||||
*/
|
||||
#[AllowDynamicProperties]
|
||||
class entry extends baseEntry
|
||||
{
|
||||
public function __construct()
|
||||
@@ -31,6 +32,7 @@ class entry extends baseEntry
|
||||
* The baseEntry class file of ZenTao API.
|
||||
*
|
||||
*/
|
||||
#[AllowDynamicProperties]
|
||||
class baseEntry
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -342,15 +342,6 @@ class baseRouter
|
||||
*/
|
||||
public $mao;
|
||||
|
||||
/**
|
||||
* 性能分析的方式。
|
||||
* The profiling method.
|
||||
*
|
||||
* @var string
|
||||
* @access public
|
||||
*/
|
||||
public $profiling;
|
||||
|
||||
/**
|
||||
* 从数据库的句柄。
|
||||
* The slave database handler.
|
||||
@@ -522,7 +513,6 @@ class baseRouter
|
||||
|
||||
if($this->config->framework->autoConnectDB) $this->connectDB();
|
||||
|
||||
$this->setupProfiling();
|
||||
$this->setupXhprof();
|
||||
|
||||
$this->setEdition();
|
||||
@@ -856,27 +846,6 @@ class baseRouter
|
||||
if(!empty($this->config->debug)) error_reporting(E_ALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置数据库性能采样。
|
||||
* Setup database profiling.
|
||||
*
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function setupProfiling(): void
|
||||
{
|
||||
if(empty($this->config->debug) || $this->config->debug < 3 || empty($this->config->installed)) return;
|
||||
|
||||
$performanceSchema = $this->dbh->query("SHOW VARIABLES LIKE 'performance_schema'")->fetch();
|
||||
$performanceSwitch = $performanceSchema->Value ?? 'OFF';
|
||||
$this->profiling = $performanceSwitch == 'ON' ? 'performance_schema' : 'show_profiles';
|
||||
|
||||
if($this->profiling != 'show_profiles') return;
|
||||
|
||||
$this->dbh->exec('SET profiling = 1');
|
||||
$this->dbh->exec('SET profiling_history_size = 200');
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用Xhprof。
|
||||
* Setup xhprof.
|
||||
@@ -3399,8 +3368,10 @@ class baseRouter
|
||||
fwrite($fh, date('Ymd H:i:s') . ": " . $this->getURI() . "\n");
|
||||
foreach(dbh::$queries as $key => $query)
|
||||
{
|
||||
if(!empty(dbh::$flags)) fwrite($fh, ' #Flag: ' . (dbh::$flags[$key] ?? '') . "\n");
|
||||
if(!empty(dbh::$durations)) fwrite($fh, ' #Query_time: ' . (dbh::$durations[$key] ?? '') . "\n");
|
||||
if(!empty(dbh::$traces)) fwrite($fh, ' #Trigger_code: ' . (dbh::$traces[$key] ?? '') . "\n");
|
||||
fwrite($fh, " $query\n");
|
||||
if(!empty(dbh::$traces)) fwrite($fh, ' └' . (dbh::$traces[$key] ?? '') . "\n");
|
||||
}
|
||||
fwrite($fh, "\n");
|
||||
fclose($fh);
|
||||
|
||||
@@ -638,7 +638,7 @@ class control extends baseControl
|
||||
if($object && empty($object->{$field->field})) $object->{$field->field} = $field->defaultValue;
|
||||
if(in_array($field->type, $this->config->workflowfield->numberTypes)) $field = $this->workflowfield->processNumberField($field);
|
||||
|
||||
$field->required = $field->readonly || ($notEmptyRule && strpos(",$field->rules,", ",{$notEmptyRule->id},") !== false);
|
||||
$field->required = !$field->readonly && $notEmptyRule && strpos(",$field->rules,", ",{$notEmptyRule->id},") !== false;
|
||||
$field->control = $this->flow->buildFormControl($field);
|
||||
$field->items = $field->options ? array_filter($field->options) : null;
|
||||
$field->value = !empty($object->{$field->field}) ? zget($object, $field->field, '') : '';
|
||||
|
||||
@@ -834,8 +834,7 @@ function debug(mixed ...$messages): void
|
||||
$count = $counts ? end($counts) + 1 : 1;
|
||||
$counts[] = $count;
|
||||
|
||||
global $app;
|
||||
$logFile = $app->getLogRoot() . 'debug.' . date('Ymd') . '.log.php';
|
||||
$logFile = dirname(__FILE__, 2) . '/tmp/log/debug.' . date('Ymd') . '.log.php';
|
||||
$uid = $_SERVER['HTTP_X_ZIN_UID'] ?? '';
|
||||
$count = sprintf('%04d', $count);
|
||||
$time = date('H:i:s');
|
||||
|
||||
@@ -282,6 +282,42 @@ class model extends baseModel
|
||||
return $menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存用于构造搜索参数的方法和参数。
|
||||
* Cache the method and arguments used to build search parameters.
|
||||
*
|
||||
* @param string $module
|
||||
* @param string $classMethod 构造搜索参数的类名和方法名,以::分隔。The class name and method name which builds the search params, separated by ::.
|
||||
* @param array $methodArgs 调用构造搜索参数的方法时传入的参数列表(实参)。 The arguments passed to the method which builds the search params (actual parameters).
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function cacheSearchFunc(string $module, string $classMethod, array $methodArgs)
|
||||
{
|
||||
list($className, $methodName) = explode('::', $classMethod);
|
||||
|
||||
$funcModel = str_replace(['ext', 'Model'], '', $className);
|
||||
|
||||
$key = 0;
|
||||
$funcArgs = [];
|
||||
$method = new ReflectionMethod($className, $methodName);
|
||||
$params = $method->getParameters(); // 构造搜索参数的方法的参数列表(形参)。The parameters of the method which builds the search params (formal parameters).
|
||||
foreach($params as $param)
|
||||
{
|
||||
if(isset($methodArgs[$key]))
|
||||
{
|
||||
$funcArgs[$param->getName()] = $methodArgs[$key];
|
||||
}
|
||||
elseif($param->isDefaultValueAvailable())
|
||||
{
|
||||
$funcArgs[$param->getName()] = $param->getDefaultValue();
|
||||
}
|
||||
$key++;
|
||||
}
|
||||
|
||||
$this->session->set($module . 'SearchFunc', ['funcModel' => $funcModel, 'funcName' => $methodName, 'funcArgs' => $funcArgs]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process status of an object according to its subStatus.
|
||||
*
|
||||
|
||||
+17
-37
@@ -1928,54 +1928,34 @@ class baseDAO
|
||||
public function getProfiles()
|
||||
{
|
||||
$profiles = [];
|
||||
if($this->app->profiling == 'performance_schema')
|
||||
{
|
||||
try
|
||||
{
|
||||
$sql = "SELECT t1.EVENT_ID AS Query_ID, TRUNCATE(t1.TIMER_WAIT/1000000000000,6) AS Duration, t1.SQL_TEXT AS Query FROM performance_schema.events_statements_history_long AS t1 LEFT JOIN performance_schema.threads AS t2 ON t1.THREAD_ID=t2.THREAD_ID wHeRe t2.PROCESSLIST_ID=CONNECTION_ID() oRdEr bY EVENT_ID";
|
||||
$profiles = $this->dbh->query($sql)->fetchAll();
|
||||
}
|
||||
catch(PDOException $e)
|
||||
{
|
||||
$this->sqlError($e);
|
||||
}
|
||||
}
|
||||
elseif($this->app->profiling == 'show_profiles')
|
||||
{
|
||||
$profiles = $this->query('SHOW PROFILES')->fetchAll();
|
||||
}
|
||||
if(empty($profiles)) return [];
|
||||
|
||||
$traces = dbh::$traces;
|
||||
if($this->app->profiling == 'show_profiles')
|
||||
{
|
||||
$count = count($profiles);
|
||||
if(count($traces) > $count) $traces = array_slice($traces, -$count);
|
||||
}
|
||||
|
||||
$basePath = $this->app->getBasePath();
|
||||
foreach($profiles as $key => $profile)
|
||||
$sqlTypes = ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'REPLACE'];
|
||||
foreach(dbh::$queries as $key => $query)
|
||||
{
|
||||
$profile->Duration = round((float)$profile->Duration, 4);
|
||||
$profile = new stdClass();
|
||||
$profile->Query_ID = $key + 1;
|
||||
$profile->Query = $query;
|
||||
$profile->Explain = [];
|
||||
$profile->Error = '';
|
||||
$profile->Code = str_replace($basePath, '', $traces[$key] ?? '');
|
||||
$profile->Duration = dbh::$durations[$key] ?? 0;
|
||||
$profile->Code = str_replace($basePath, '', dbh::$traces[$key] ?? '');
|
||||
$profiles[] = $profile;
|
||||
|
||||
$sql = trim($profile->Query);
|
||||
if(stripos($sql, 'select') !== 0
|
||||
&& strpos($sql, 'insert') !== 0
|
||||
&& strpos($sql, 'update') !== 0
|
||||
&& strpos($sql, 'delete') !== 0
|
||||
&& strpos($sql, 'replace') !== 0
|
||||
)
|
||||
$allowExplain = false;
|
||||
foreach($sqlTypes as $type)
|
||||
{
|
||||
continue;
|
||||
if(stripos($query, $type) === 0)
|
||||
{
|
||||
$allowExplain = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!$allowExplain) continue;
|
||||
|
||||
try
|
||||
{
|
||||
$slow = false;
|
||||
$rows = $this->explain($sql, false);
|
||||
$rows = $this->explain($query, false);
|
||||
foreach($rows as $row)
|
||||
{
|
||||
if($row->type === 'ALL'
|
||||
|
||||
+223
-197
@@ -44,13 +44,22 @@ class dbh
|
||||
private $statement;
|
||||
|
||||
/**
|
||||
* Database config.
|
||||
* 应用配置信息。
|
||||
* The app config.
|
||||
*
|
||||
* @var object
|
||||
* @access private
|
||||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* Database dbConfig.
|
||||
*
|
||||
* @var object
|
||||
* @access private
|
||||
*/
|
||||
private $dbConfig;
|
||||
|
||||
/**
|
||||
* The SQL string of last query.
|
||||
*
|
||||
@@ -60,8 +69,17 @@ class dbh
|
||||
private $sql;
|
||||
|
||||
/**
|
||||
* 这是用来记录SQL执行的文件和行号。
|
||||
* This is used to record the file and line number where the SQL was executed.
|
||||
* 主库还是从库的标记。
|
||||
* Flag for master or slave.
|
||||
*
|
||||
* @var array
|
||||
* @access public
|
||||
*/
|
||||
public static $flags = [];
|
||||
|
||||
/**
|
||||
* 触发SQL执行的文件和行号。
|
||||
* The file and line number that triggered the SQL execution.
|
||||
*
|
||||
* @var array
|
||||
* @access public
|
||||
@@ -77,25 +95,37 @@ class dbh
|
||||
*/
|
||||
public static $queries = [];
|
||||
|
||||
/**
|
||||
* SQL执行时长。
|
||||
* The duration of SQL execution.
|
||||
*
|
||||
* @var array
|
||||
* @access public
|
||||
*/
|
||||
public static $durations = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param object $config
|
||||
* @param bool $setSchema
|
||||
* @param string $flag
|
||||
* @param object $dbConfig
|
||||
* @param bool $setSchema
|
||||
* @param string $flag
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($config, $setSchema = true, $flag = 'MASTER')
|
||||
public function __construct($dbConfig, $setSchema = true, $flag = 'MASTER')
|
||||
{
|
||||
$driver = $config->driver;
|
||||
if($config->driver == 'oceanbase') $driver = 'mysql'; // Oceanbase driver alias mysql.
|
||||
global $config;
|
||||
$this->config = $config;
|
||||
|
||||
$dsn = "{$driver}:host={$config->host};port={$config->port}";
|
||||
if($setSchema) $dsn .= ";dbname={$config->name}";
|
||||
$driver = $dbConfig->driver;
|
||||
if($dbConfig->driver == 'oceanbase') $driver = 'mysql'; // Oceanbase driver alias mysql.
|
||||
|
||||
$password = helper::decryptPassword($config->password);
|
||||
$pdo = new PDO($dsn, $config->user, $password);
|
||||
$dsn = "{$driver}:host={$dbConfig->host};port={$dbConfig->port}";
|
||||
if($setSchema) $dsn .= ";dbname={$dbConfig->name}";
|
||||
|
||||
$password = helper::decryptPassword($dbConfig->password);
|
||||
$pdo = new PDO($dsn, $dbConfig->user, $password);
|
||||
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
@@ -103,26 +133,41 @@ class dbh
|
||||
/* Mysql driver include mysql and oceanbase. */
|
||||
if($driver == 'mysql')
|
||||
{
|
||||
$queries[] = "SET NAMES {$config->encoding}";
|
||||
if(isset($config->strictMode) && empty($config->strictMode)) $queries[] = "SET @@sql_mode= ''";
|
||||
$queries[] = "SET NAMES {$dbConfig->encoding}";
|
||||
if(isset($dbConfig->strictMode) && empty($dbConfig->strictMode)) $queries[] = "SET @@sql_mode= ''";
|
||||
}
|
||||
else if($setSchema)
|
||||
{
|
||||
$queries[] = "SET SCHEMA {$config->name}";
|
||||
$queries[] = "SET SCHEMA {$dbConfig->name}";
|
||||
}
|
||||
if(!empty($queries))
|
||||
{
|
||||
foreach($queries as $query)
|
||||
{
|
||||
$pdo->exec($query);
|
||||
dbh::$traces[] = 'vim +' . (__LINE__ - 1) . ' ' . __FILE__;
|
||||
dbh::$queries[] = "[$flag] " . $query;
|
||||
try
|
||||
{
|
||||
$begin = microtime(true);
|
||||
$pdo->exec($query);
|
||||
$duration = microtime(true) - $begin;
|
||||
}
|
||||
catch (PDOException $e)
|
||||
{
|
||||
$duration = microtime(true) - $begin;
|
||||
$this->sqlError($e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
dbh::$flags[] = $flag;
|
||||
dbh::$queries[] = $query;
|
||||
dbh::$durations[] = round($duration, 6);
|
||||
if(!empty($config->debug)) dbh::$traces[] = 'vim +' . (__LINE__ - 12) . ' ' . __FILE__;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->pdo = $pdo;
|
||||
$this->config = $config;
|
||||
$this->flag = $flag;
|
||||
$this->pdo = $pdo;
|
||||
$this->dbConfig = $dbConfig;
|
||||
$this->flag = $flag;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,28 +187,13 @@ class dbh
|
||||
/**
|
||||
* Execute sql.
|
||||
*
|
||||
* @param string $sql
|
||||
* @param string $sql
|
||||
* @access public
|
||||
* @return PDOStatement|false
|
||||
* @return int|false
|
||||
*/
|
||||
public function exec($sql)
|
||||
{
|
||||
$this->trace();
|
||||
|
||||
$sql = $this->formatSQL($sql);
|
||||
if(!$sql) return true;
|
||||
|
||||
if(!empty($this->config->enableSqlite)) $this->pushSqliteQueue($sql);
|
||||
|
||||
try
|
||||
{
|
||||
dbh::$queries[] = "[$this->flag] " . dao::processKeywords($sql);
|
||||
return $this->pdo->exec($sql);
|
||||
}
|
||||
catch(PDOException $e)
|
||||
{
|
||||
$this->sqlError($e);
|
||||
}
|
||||
return $this->executeSql($sql, __FUNCTION__);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,18 +206,95 @@ class dbh
|
||||
*/
|
||||
public function query($sql)
|
||||
{
|
||||
$this->trace();
|
||||
return $this->executeSql($sql, __FUNCTION__);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query raw sql.
|
||||
*
|
||||
* @param string $sql
|
||||
* @access public
|
||||
* @return PDOStatement|false
|
||||
*/
|
||||
public function rawQuery($sql)
|
||||
{
|
||||
return $this->executeSql($sql, __FUNCTION__);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行SQL语句并记录执行时间和调用栈信息。
|
||||
* Execute SQL and record the duration and trace info.
|
||||
*
|
||||
* @param string $sql
|
||||
* @param string $mode exec|query|rawQuery
|
||||
* @access private
|
||||
* @return mixed
|
||||
*/
|
||||
private function executeSql($sql, $mode)
|
||||
{
|
||||
if($mode == 'exec' || $mode == 'query')
|
||||
{
|
||||
$sql = $this->formatSQL($sql);
|
||||
if(!$sql) return false;
|
||||
|
||||
if($mode == 'exec' && !empty($this->dbConfig->enableSqlite)) $this->pushSqliteQueue($sql);
|
||||
}
|
||||
|
||||
$sql = $this->formatSQL($sql);
|
||||
try
|
||||
{
|
||||
dbh::$queries[] = "[$this->flag] " . dao::processKeywords($sql);
|
||||
return $this->pdo->query($sql);
|
||||
$result = false;
|
||||
$begin = microtime(true);
|
||||
$method = $mode == 'exec' ? 'exec' : 'query';
|
||||
$result = $this->pdo->$method($sql);
|
||||
$duration = microtime(true) - $begin;
|
||||
}
|
||||
catch(PDOException $e)
|
||||
{
|
||||
$duration = microtime(true) - $begin;
|
||||
$this->sqlError($e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
dbh::$flags[] = $this->flag;
|
||||
dbh::$queries[] = dao::processKeywords($sql);
|
||||
dbh::$durations[] = round($duration, 6);
|
||||
|
||||
if(!empty($this->config->debug))
|
||||
{
|
||||
$trace = $this->getTrace();
|
||||
if($trace) dbh::$traces[] = 'vim +' . $trace['line'] . ' ' . $trace['file'];
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前执行的 SQL 的调用栈信息。
|
||||
* Get the call stack information of the currently executed SQL.
|
||||
*
|
||||
* @access private
|
||||
* @return array
|
||||
*/
|
||||
private function getTrace()
|
||||
{
|
||||
$traces = array_reverse(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS));
|
||||
foreach($traces as $key => $trace)
|
||||
{
|
||||
$class = $trace['class'] ?? '';
|
||||
$function = $trace['function'] ?? '';
|
||||
if($class == 'settingModel' && strpos(',getItem,getItems,setItem,setItems,updateItem,deleteItems,', ",$function,") !== false) return $trace;
|
||||
if($class == 'baseDAO' && strpos(',exec,fetch,fetchPairs,fetchGroup,explain,showTables,getTableEngines,descTable,', ",$function,") !== false) return $trace;
|
||||
if($class == 'baseDAO' && $function == 'getProfiles') return $traces[$key + 1];
|
||||
if($class == 'baseDAO' && $function == 'fetchAll')
|
||||
{
|
||||
if($traces[$key + 1]['class'] == 'baseDAO' && $traces[$key + 1]['function'] == 'extractSQLFields') return $traces[$key + 2];
|
||||
return $trace;
|
||||
}
|
||||
if($class == 'baseDAO' && stripos($function, 'findBy') === 0) return $trace;
|
||||
if($class == 'baseRouter' && $function == 'dbQuery') return $trace;
|
||||
if($class == 'dbh' && strpos('exec,query,rawQuery', $function) !== false) return $trace;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,80 +346,6 @@ class dbh
|
||||
return $this->statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query raw sql.
|
||||
*
|
||||
* @param string $sql
|
||||
* @access public
|
||||
* @return PDOStatement|false
|
||||
*/
|
||||
public function rawQuery($sql)
|
||||
{
|
||||
$this->trace();
|
||||
|
||||
try
|
||||
{
|
||||
dbh::$queries[] = "[$this->flag] " . dao::processKeywords($sql);
|
||||
return $this->pdo->query($sql);
|
||||
}
|
||||
catch(PDOException $e)
|
||||
{
|
||||
$this->sqlError($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录当前执行的 SQL 的文件和行号。
|
||||
* Record the file and line number of the currently executed SQL.
|
||||
*
|
||||
* @access private
|
||||
* @return bool
|
||||
*/
|
||||
private function trace()
|
||||
{
|
||||
global $config;
|
||||
if(empty($config->debug) || $config->debug < 3) return false;
|
||||
|
||||
$trace = $this->getTrace();
|
||||
if(empty($trace)) return false;
|
||||
|
||||
$file = $trace['file'];
|
||||
$line = $trace['line'];
|
||||
|
||||
dbh::$traces[] = "vim +$line $file";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前执行的 SQL 的调用栈信息。
|
||||
* Get the call stack information of the currently executed SQL.
|
||||
*
|
||||
* @access private
|
||||
* @return array
|
||||
*/
|
||||
private function getTrace()
|
||||
{
|
||||
$traces = array_reverse(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS));
|
||||
foreach($traces as $key => $trace)
|
||||
{
|
||||
$class = $trace['class'] ?? '';
|
||||
$function = $trace['function'] ?? '';
|
||||
if($class == 'settingModel' && strpos(',getItem,getItems,setItem,setItems,updateItem,deleteItems,', ",$function,") !== false) return $trace;
|
||||
if($class == 'baseDAO' && strpos(',exec,fetch,fetchPairs,fetchGroup,explain,showTables,getTableEngines,descTable,', ",$function,") !== false) return $trace;
|
||||
if($class == 'baseDAO' && $function == 'getProfiles') return $traces[$key + 1];
|
||||
if($class == 'baseDAO' && $function == 'fetchAll')
|
||||
{
|
||||
if($traces[$key + 1]['class'] == 'baseDAO' && $traces[$key + 1]['function'] == 'extractSQLFields') return $traces[$key + 2];
|
||||
return $trace;
|
||||
}
|
||||
if($class == 'baseDAO' && stripos($function, 'findBy') === 0) return $trace;
|
||||
if($class == 'baseRouter' && $function == 'dbQuery') return $trace;
|
||||
if($class == 'dbh' && strpos('exec,query,rawQuery', $function) !== false) return $trace;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attribute.
|
||||
*
|
||||
@@ -334,14 +367,14 @@ class dbh
|
||||
*/
|
||||
public function dbExists()
|
||||
{
|
||||
switch($this->config->driver)
|
||||
switch($this->dbConfig->driver)
|
||||
{
|
||||
case 'oceanbase':
|
||||
case 'mysql':
|
||||
$sql = "SHOW DATABASES like '{$this->config->name}'";
|
||||
$sql = "SHOW DATABASES like '{$this->dbConfig->name}'";
|
||||
break;
|
||||
case 'dm':
|
||||
$sql = "SELECT * FROM ALL_OBJECTS WHERE object_type='SCH' AND owner='{$this->config->name}'";
|
||||
$sql = "SELECT * FROM ALL_OBJECTS WHERE object_type='SCH' AND owner='{$this->dbConfig->name}'";
|
||||
break;
|
||||
default:
|
||||
$sql = '';
|
||||
@@ -359,20 +392,14 @@ class dbh
|
||||
public function tableExits($tableName)
|
||||
{
|
||||
$tableName = str_replace(array("'", '`'), "", $tableName);
|
||||
$sql = "SHOW TABLES FROM {$this->config->name} like '{$tableName}'";
|
||||
switch($this->config->driver)
|
||||
|
||||
if($this->dbConfig->driver == 'dm')
|
||||
{
|
||||
case 'oceanbase':
|
||||
case 'mysql':
|
||||
$sql = "SHOW TABLES FROM {$this->config->name} like '{$tableName}'";
|
||||
break;
|
||||
case 'dm':
|
||||
$sql = "SELECT * FROM all_tables WHERE owner='{$this->config->name}' AND table_name='{$tableName}'";
|
||||
break;
|
||||
default:
|
||||
$sql = '';
|
||||
$sql = "SELECT * FROM all_tables WHERE owner='{$this->dbConfig->name}' AND table_name='{$tableName}'";
|
||||
return $this->rawQuery($sql)->fetch();
|
||||
}
|
||||
|
||||
$sql = "SHOW TABLES FROM {$this->dbConfig->name} like '{$tableName}'";
|
||||
return $this->rawQuery($sql)->fetch();
|
||||
}
|
||||
|
||||
@@ -385,13 +412,13 @@ class dbh
|
||||
*/
|
||||
public function createDB($version)
|
||||
{
|
||||
switch($this->config->driver)
|
||||
switch($this->dbConfig->driver)
|
||||
{
|
||||
case 'oceanbase':
|
||||
$sql = "CREATE DATABASE `{$this->config->name}`";
|
||||
$sql = "CREATE DATABASE `{$this->dbConfig->name}`";
|
||||
return $this->rawQuery($sql);
|
||||
case 'mysql':
|
||||
$sql = "CREATE DATABASE `{$this->config->name}`";
|
||||
$sql = "CREATE DATABASE `{$this->dbConfig->name}`";
|
||||
if(version_compare($version, '5.6', '>='))
|
||||
{
|
||||
$sql .= " DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci";
|
||||
@@ -403,19 +430,19 @@ class dbh
|
||||
return $this->rawQuery($sql);
|
||||
case 'dm':
|
||||
/*
|
||||
$tableSpace = strtoupper($this->config->name);
|
||||
$tableSpace = strtoupper($this->dbConfig->name);
|
||||
$res = $this->rawQuery("SELECT * FROM dba_data_files WHERE TABLESPACE_NAME = '$tableSpace'")->fetchAll();
|
||||
|
||||
if(empty($res))
|
||||
{
|
||||
$createTableSpace = "CREATE TABLESPACE $tableSpace DATAFILE '{$this->config->name}.DBF' size 150 AUTOEXTEND ON";
|
||||
$createUser = "CREATE USER {$this->config->name} IDENTIFIED by {$this->config->password} DEFAULT TABLESPACE {$this->config->name} DEFAULT INDEX TABLESPACE {$this->config->name}";
|
||||
$createTableSpace = "CREATE TABLESPACE $tableSpace DATAFILE '{$this->dbConfig->name}.DBF' size 150 AUTOEXTEND ON";
|
||||
$createUser = "CREATE USER {$this->dbConfig->name} IDENTIFIED by {$this->dbConfig->password} DEFAULT TABLESPACE {$this->dbConfig->name} DEFAULT INDEX TABLESPACE {$this->dbConfig->name}";
|
||||
|
||||
$this->rawQuery($createTableSpace);
|
||||
$this->rawQuery($createUser);
|
||||
}
|
||||
*/
|
||||
$createSchema = "CREATE SCHEMA {$this->config->name} AUTHORIZATION {$this->config->user}";
|
||||
$createSchema = "CREATE SCHEMA {$this->dbConfig->name} AUTHORIZATION {$this->dbConfig->user}";
|
||||
return $this->rawQuery($createSchema);
|
||||
default:
|
||||
return false;
|
||||
@@ -431,14 +458,14 @@ class dbh
|
||||
*/
|
||||
public function useDB($dbName)
|
||||
{
|
||||
switch($this->config->driver)
|
||||
switch($this->dbConfig->driver)
|
||||
{
|
||||
case 'oceanbase':
|
||||
case 'mysql':
|
||||
return $this->exec("USE {$this->config->name}");
|
||||
return $this->exec("USE {$this->dbConfig->name}");
|
||||
|
||||
case 'dm':
|
||||
return $this->exec("SET SCHEMA {$this->config->name}");
|
||||
return $this->exec("SET SCHEMA {$this->dbConfig->name}");
|
||||
|
||||
default:
|
||||
return false;
|
||||
@@ -456,7 +483,7 @@ class dbh
|
||||
{
|
||||
$this->sql = $sql;
|
||||
|
||||
switch($this->config->driver)
|
||||
switch($this->dbConfig->driver)
|
||||
{
|
||||
case 'dm':
|
||||
return $this->formatDmSQL($sql);
|
||||
@@ -537,7 +564,7 @@ class dbh
|
||||
{
|
||||
preg_match('/ON\s+([^.`\s]+\.)?`([^\s`]+)`/', $sql, $matches);
|
||||
|
||||
$tableName = isset($matches[2]) ? str_replace($this->config->prefix, '', $matches[2]) : '';
|
||||
$tableName = isset($matches[2]) ? str_replace($this->dbConfig->prefix, '', $matches[2]) : '';
|
||||
$sql = preg_replace('/INDEX\ +\`/', 'INDEX `' . strtolower($tableName) . '_', $sql);
|
||||
}
|
||||
case 'ALTER':
|
||||
@@ -569,9 +596,9 @@ class dbh
|
||||
/* DMDB must set IDENTITY_INSERT 'on' to insert id field. */
|
||||
if($action == 'INSERT' and stripos($fields, '"id"') !== FALSE)
|
||||
{
|
||||
$tableBegin = strpos($sql, '"' . $this->config->prefix);
|
||||
$tableBegin = strpos($sql, '"' . $this->dbConfig->prefix);
|
||||
$tableEnd = strpos($sql, '"', $tableBegin + 1);
|
||||
$tableName = '' . $this->config->name . '."' . substr($sql, $tableBegin + 1, $tableEnd - $tableBegin - 1) . '"';
|
||||
$tableName = '' . $this->dbConfig->name . '."' . substr($sql, $tableBegin + 1, $tableEnd - $tableBegin - 1) . '"';
|
||||
return "SET IDENTITY_INSERT $tableName ON;" . $sql;
|
||||
}
|
||||
|
||||
@@ -613,7 +640,7 @@ class dbh
|
||||
*/
|
||||
public function formatField($sql)
|
||||
{
|
||||
switch($this->config->driver)
|
||||
switch($this->dbConfig->driver)
|
||||
{
|
||||
case 'dm':
|
||||
$sql = str_replace('`', '"', $sql);
|
||||
@@ -633,7 +660,7 @@ class dbh
|
||||
*/
|
||||
public function formatFunction($sql)
|
||||
{
|
||||
switch($this->config->driver)
|
||||
switch($this->dbConfig->driver)
|
||||
{
|
||||
case 'dm':
|
||||
/* DATE convert to TO_CHAR. */
|
||||
@@ -677,60 +704,59 @@ class dbh
|
||||
/**
|
||||
* Format attribute of field.
|
||||
*
|
||||
* @param string $sql
|
||||
* @param string $sql
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function formatAttr($sql)
|
||||
{
|
||||
switch($this->config->driver)
|
||||
if($this->dbConfig->driver == 'dm')
|
||||
{
|
||||
case 'dm':
|
||||
$pos = stripos($sql, ' ENGINE');
|
||||
if($pos > 0) $sql = substr($sql, 0, $pos);
|
||||
$pos = stripos($sql, ' ENGINE');
|
||||
if($pos > 0) $sql = substr($sql, 0, $pos);
|
||||
|
||||
$sql = preg_replace('/\(\ *\d+\ *\)/', '', $sql);
|
||||
$sql = preg_replace('/\(\ *\d+\ *\)/', '', $sql);
|
||||
|
||||
$replace = array(
|
||||
" AUTO_INCREMENT" => ' IDENTITY(1, 1)',
|
||||
" int " => ' integer ',
|
||||
" mediumint " => ' integer ',
|
||||
" smallint " => ' integer ',
|
||||
" tinyint " => ' integer ',
|
||||
" varchar " => ' varchar(255) ',
|
||||
" char " => ' varchar(255) ',
|
||||
" mediumtext " => ' text ',
|
||||
" mediumtext," => ' text,',
|
||||
" longtext " => ' text ',
|
||||
"COLLATE 'utf8_general_ci'" => ' ',
|
||||
" unsigned " => ' ',
|
||||
" zerofill " => ' ',
|
||||
"0000-00-00" => '1970-01-01',
|
||||
);
|
||||
$replace = array(
|
||||
" AUTO_INCREMENT" => ' IDENTITY(1, 1)',
|
||||
" int " => ' integer ',
|
||||
" mediumint " => ' integer ',
|
||||
" smallint " => ' integer ',
|
||||
" tinyint " => ' integer ',
|
||||
" varchar " => ' varchar(255) ',
|
||||
" char " => ' varchar(255) ',
|
||||
" mediumtext " => ' text ',
|
||||
" mediumtext," => ' text,',
|
||||
" longtext " => ' text ',
|
||||
"COLLATE 'utf8_general_ci'" => ' ',
|
||||
" unsigned " => ' ',
|
||||
" zerofill " => ' ',
|
||||
"0000-00-00" => '1970-01-01',
|
||||
);
|
||||
|
||||
$sql = preg_replace('/ enum[\_0-9a-z\,\'\"\( ]+\)+/i', ' varchar(255) ', $sql);
|
||||
$sql = str_ireplace(array_keys($replace), array_values($replace), $sql);
|
||||
$sql = preg_replace('/\,\s+key[\_\"0-9a-z ]+\(+[\,\_\"0-9a-z ]+\)+/i', '', $sql);
|
||||
$sql = preg_replace('/\,\s*(unique|fulltext)*\s+key[\_\"0-9a-z ]+\(+[\,\_\"0-9a-z ]+\)+/i', '', $sql);
|
||||
$sql = preg_replace('/ float\s*\(+[\,\_\"0-9a-z ]+\)+/i', ' float', $sql);
|
||||
$sql = preg_replace('/ enum[\_0-9a-z\,\'\"\( ]+\)+/i', ' varchar(255) ', $sql);
|
||||
$sql = str_ireplace(array_keys($replace), array_values($replace), $sql);
|
||||
$sql = preg_replace('/\,\s+key[\_\"0-9a-z ]+\(+[\,\_\"0-9a-z ]+\)+/i', '', $sql);
|
||||
$sql = preg_replace('/\,\s*(unique|fulltext)*\s+key[\_\"0-9a-z ]+\(+[\,\_\"0-9a-z ]+\)+/i', '', $sql);
|
||||
$sql = preg_replace('/ float\s*\(+[\,\_\"0-9a-z ]+\)+/i', ' float', $sql);
|
||||
|
||||
/* Convert "date" datetime to "date" datetime(0) to fix bug 25725, dm database datetime default 6 */
|
||||
preg_match_all('/"[0-9a-zA-Z]+" datetime/', $sql, $datetimeMatch);
|
||||
if(!empty($datetimeMatch))
|
||||
/* Convert "date" datetime to "date" datetime(0) to fix bug 25725, dm database datetime default 6 */
|
||||
preg_match_all('/"[0-9a-zA-Z]+" datetime/', $sql, $datetimeMatch);
|
||||
if(!empty($datetimeMatch))
|
||||
{
|
||||
foreach($datetimeMatch[0] as $match) $sql = str_replace($match, $match . '(0)', $sql);
|
||||
}
|
||||
|
||||
if(strpos($sql, "ALTER TABLE") === 0)
|
||||
{
|
||||
$sql = $this->convertAlterTableSql($sql);
|
||||
if(stripos($sql, "ADD") !== false)
|
||||
{
|
||||
foreach($datetimeMatch[0] as $match) $sql = str_replace($match, $match . '(0)', $sql);
|
||||
}
|
||||
|
||||
if(strpos($sql, "ALTER TABLE") === 0)
|
||||
{
|
||||
$sql = $this->convertAlterTableSql($sql);
|
||||
if(stripos($sql, "ADD") !== false)
|
||||
{
|
||||
// 使用正则表达式匹配并去除 "AFTER" 关键字及其后面的内容
|
||||
$pattern = "/\s+AFTER\s+.+$/i";
|
||||
$sql = preg_replace($pattern, "", $sql);
|
||||
}
|
||||
// 使用正则表达式匹配并去除 "AFTER" 关键字及其后面的内容
|
||||
$pattern = "/\s+AFTER\s+.+$/i";
|
||||
$sql = preg_replace($pattern, "", $sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $sql;
|
||||
@@ -920,9 +946,9 @@ class dbh
|
||||
|
||||
if(!in_array($action, $allowedActions)) return null;
|
||||
|
||||
foreach($this->config->sqliteBlacklist as $table)
|
||||
foreach($this->dbConfig->sqliteBlacklist as $table)
|
||||
{
|
||||
$tableName = $this->config->prefix . $table;
|
||||
$tableName = $this->dbConfig->prefix . $table;
|
||||
if(stripos($sql, $tableName) !== false) return null;
|
||||
}
|
||||
|
||||
@@ -963,11 +989,11 @@ class dbh
|
||||
public function checkUserPriv(): string
|
||||
{
|
||||
global $config;
|
||||
if(!in_array($this->config->driver, $config->mysqlDriverList)) return '';
|
||||
if(!in_array($this->dbConfig->driver, $config->mysqlDriverList)) return '';
|
||||
|
||||
$dbName = $this->config->name;
|
||||
$user = $this->config->user;
|
||||
$host = ($this->config->host == 'localhost' || $this->config->host == '127.0.0.1') ? 'localhost' : '%';
|
||||
$dbName = $this->dbConfig->name;
|
||||
$user = $this->dbConfig->user;
|
||||
$host = ($this->dbConfig->host == 'localhost' || $this->dbConfig->host == '127.0.0.1') ? 'localhost' : '%';
|
||||
|
||||
$privPairs = array();
|
||||
try
|
||||
@@ -1013,7 +1039,7 @@ class dbh
|
||||
*/
|
||||
public function getVersion(): string
|
||||
{
|
||||
switch($this->config->driver)
|
||||
switch($this->dbConfig->driver)
|
||||
{
|
||||
case 'oceanbase':
|
||||
case 'mysql':
|
||||
|
||||
@@ -166,7 +166,7 @@ class form extends fixer
|
||||
if($field->buildin || !$field->show || !isset($layouts[$field->field])) continue;
|
||||
if($field->control == 'file') continue;
|
||||
|
||||
$required = $field->readonly || ($notEmptyRule && strpos(",$field->rules,", ",{$notEmptyRule->id},") !== false);
|
||||
$required = !$field->readonly && $notEmptyRule && strpos(",$field->rules,", ",{$notEmptyRule->id},") !== false;
|
||||
if($field->control == 'multi-select' || $field->control == 'checkbox')
|
||||
{
|
||||
$configObject[$field->field] = array('required' => $required, 'type' => 'array', 'default' => array(''), 'filter' => 'join');
|
||||
@@ -488,6 +488,7 @@ class form extends fixer
|
||||
{
|
||||
$this->data = $data;
|
||||
$this->dataList[$rowIndex] = parent::get($fields);
|
||||
$this->stripedFields = array();
|
||||
}
|
||||
return $this->dataList;
|
||||
}
|
||||
|
||||
@@ -700,8 +700,14 @@ function handleSaveDoc(doc)
|
||||
}).complete(() => {
|
||||
docApp.isSavingDoc = false;
|
||||
$(docApp.element).find('[zui-command^="saveDoc"],[zui-command^="saveNewDoc"]').removeAttr('disabled');
|
||||
const {id, acl, users, addedBy} = doc;
|
||||
if(acl == 'private' && !users.includes(docAppData.currentUser) && addedBy !== docAppData.currentUser)
|
||||
const {id, acl, users, groups, addedBy} = doc;
|
||||
let isInEditGroups = false;
|
||||
for(let groupID of Object.values(userGroups))
|
||||
{
|
||||
if(groups.includes(groupID)) isInEditGroups = true;
|
||||
}
|
||||
|
||||
if(acl == 'private' && !users.includes(docAppData.currentUser) && addedBy !== docAppData.currentUser && !isInEditGroups)
|
||||
{
|
||||
if(docApp.hasEditingDoc)
|
||||
{
|
||||
@@ -1218,10 +1224,24 @@ const actionsMap =
|
||||
const lang = getLang();
|
||||
const canEdit = !isReadonly && privs.edit && (!doc.privs || doc.privs.edit !== false) && privs.editFile;
|
||||
const canDelete = !isReadonly && privs.edit && (!doc.privs || doc.privs.edit !== false) && privs.deleteFile;
|
||||
const hasExtension = file.title.lastIndexOf('.' + file.extension) == (file.title.length - file.extension.length - 1);
|
||||
const canPreview = hasExtension && 'mp4,txt,jpg,jpeg,gif,png,bmp'.split(',').includes(file.extension);
|
||||
|
||||
let previewAction = null;
|
||||
let isOfficeFile = 'doc|docx|xls|xlsx|ppt|pptx|pdf'.includes(file.extension);
|
||||
let canPreview = 'txt|jpg|jpeg|gif|png|bmp|mp4'.includes(file.extension);
|
||||
if(libreOfficeTurnon && isOfficeFile) canPreview = true;
|
||||
if(canPreview)
|
||||
{
|
||||
previewAction = {url: $.createLink('file', 'download', `fileID=${file.id}&mouse=left`), hint: lang.filePreview, icon: 'eye', target: '_blank'};
|
||||
if(!isOfficeFile)
|
||||
{
|
||||
previewAction['data-toggle'] = 'modal';
|
||||
previewAction['data-size'] = 'lg';
|
||||
delete previewAction.target;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
canPreview ? {'data-toggle': 'modal', 'data-size': 'lg', url: $.createLink('file', 'download', `fileID=${file.id}&mouse=left`), hint: lang.filePreview, icon: 'eye'} : null,
|
||||
previewAction,
|
||||
{target: '_blank', url: zui.formatString(docApp.props.fileUrl, file), hint: lang.fileDownload, icon: 'download'},
|
||||
canEdit ? {hint: lang.fileRename, icon: 'pencil-alt', onClick: renameDocFile.bind(this, file, doc)} : null,
|
||||
canDelete ? {hint: lang.fileDelete, icon: 'trash', onClick: deleteDocFile.bind(this, file, doc)} : null,
|
||||
|
||||
@@ -171,6 +171,7 @@ class docApp extends wg
|
||||
jsVar('needReadable', $lang->doc->needReadable);
|
||||
jsVar('vision', $config->vision);
|
||||
jsVar('isInModal', isInModal());
|
||||
jsVar('userGroups', $app->user->groups);
|
||||
|
||||
/**
|
||||
* 定义文档应用接口链接。
|
||||
|
||||
@@ -420,7 +420,7 @@ function initGantt()
|
||||
|
||||
gantt.config.readonly = canGanttEdit ? false : true;
|
||||
gantt.config.details_on_dblclick = false;
|
||||
gantt.config.order_branch = false;
|
||||
gantt.config.order_branch = ganttType == 'assignedTo' ? false : true;
|
||||
gantt.config.drag_progress = false;
|
||||
gantt.config.drag_links = true;
|
||||
gantt.config.drag_move = true;
|
||||
|
||||
@@ -62,6 +62,7 @@ class history extends wg
|
||||
$objectType = data('objectType');
|
||||
if(empty($objectType)) $objectType = $app->rawModule;
|
||||
if($objectType == 'requirement' || $objectType == 'epic') $objectType = 'story';
|
||||
if($objectType == 'researchtask') $objectType = 'task';
|
||||
$this->setProp('objectType', $objectType);
|
||||
}
|
||||
if(empty($objectID))
|
||||
@@ -82,9 +83,13 @@ class history extends wg
|
||||
}
|
||||
if(empty($actions))
|
||||
{
|
||||
$actions = data('actions');
|
||||
if(empty($actions) && !empty($objectID)) $actions = $app->loadTarget('action')->getList($objectType, $objectID);
|
||||
if(!empty($actions)) $actions = $app->loadTarget('action')->buildActionList($actions, $users, (bool)$this->prop('commentBtn'));
|
||||
static $actions;
|
||||
if(empty($actions))
|
||||
{
|
||||
$actions = data('actions');
|
||||
if(empty($actions) && !empty($objectID)) $actions = $app->loadTarget('action')->getList($objectType, $objectID);
|
||||
if(!empty($actions)) $actions = $app->loadTarget('action')->buildActionList($actions, $users, (bool)$this->prop('commentBtn'));
|
||||
}
|
||||
$this->setProp('actions', $actions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,6 +146,10 @@ class picker extends wg
|
||||
unset($pickerProps['class']);
|
||||
}
|
||||
|
||||
global $config;
|
||||
$name = isset($pickerProps['name']) ? $pickerProps['name'] : '';
|
||||
if(isset($config->moreLinks[$name])) $pickerItems = $config->moreLinks[$name];
|
||||
|
||||
$pickerProps['_props'] = $restProps;
|
||||
$pickerProps['items'] = $pickerItems;
|
||||
$pickerProps['defaultValue'] = $defaultValue;
|
||||
|
||||
@@ -205,15 +205,16 @@ class actionModel extends model
|
||||
*/
|
||||
public function getList(string $objectType, array|int $objectID): array
|
||||
{
|
||||
$this->loadModel('file');
|
||||
$this->loadModel('workflow');
|
||||
|
||||
$modules = $objectType == 'module' ? $this->dao->select('id')->from(TABLE_MODULE)->where('root')->in($objectID)->fetchPairs('id') : array();
|
||||
$commiters = $this->loadModel('user')->getCommiters();
|
||||
$actions = $this->actionTao->getActionListByTypeAndID($objectType, $objectID, $modules);
|
||||
$histories = $this->getHistory(array_keys($actions));
|
||||
$files = $this->file->groupByObjectID('comment', array_keys($actions));
|
||||
$flowList = array();
|
||||
if($objectType == 'project') $actions = $this->processProjectActions($actions);
|
||||
|
||||
$this->loadModel('file');
|
||||
$this->loadModel('workflow');
|
||||
foreach($actions as $actionID => $action)
|
||||
{
|
||||
$actionName = strtolower($action->action);
|
||||
@@ -271,7 +272,7 @@ class actionModel extends model
|
||||
}
|
||||
|
||||
$action->comment = $this->file->setImgSize($action->comment, $this->config->action->commonImgSize);
|
||||
$action->files = $this->file->getByObject('comment', $actionID);
|
||||
$action->files = !empty($files[$actionID]) ? $files[$actionID] : array();
|
||||
$actions[$actionID] = $action;
|
||||
}
|
||||
return $actions;
|
||||
@@ -1937,7 +1938,7 @@ class actionModel extends model
|
||||
|
||||
$beginAndEnd = $this->computeBeginAndEnd($period, '', $direction);
|
||||
$hasProduct = preg_match('/t2\.(`?)product/', $condition);
|
||||
$condition = preg_replace("/AND +`?date`? +(<|>|<=|>=) +'\d{4}\-\d{2}\-\d{2}'/", '', $condition);
|
||||
$condition = preg_replace("/AND +`?date`?\s?(<|>|<=|>=)\s?'\d{4}\-\d{2}\-\d{2}'/", '', $condition);
|
||||
$actions = $this->dao->select('action.id')->from(TABLE_ACTION)->alias('action')
|
||||
->beginIF($hasProduct)->leftJoin(TABLE_ACTIONPRODUCT)->alias('t2')->on('action.id=t2.action')->fi()
|
||||
->where($condition)
|
||||
|
||||
@@ -59,8 +59,6 @@ cid=1
|
||||
- 测试获取对象类型 bug 对象ID 1 的动态信息 @link
|
||||
- 测试获取对象类型 bug 对象ID 2 的动态信息 @link
|
||||
- 测试获取对象类型 bug 对象ID 3 的动态信息 @link
|
||||
- 测试获取对象类型 feedback 对象ID 1 的动态信息 @nochanged
|
||||
- 测试获取对象类型 ticket 对象ID 1 的动态信息 @nochanged
|
||||
- 测试获取对象类型 task 对象ID 1 的动态信息 @link
|
||||
- 测试获取对象类型 module 对象ID 1 的动态信息 @nochanged
|
||||
- 测试获取对象类型 story 对象ID 28 的动态信息 @link
|
||||
@@ -93,12 +91,6 @@ cid=1
|
||||
- 测试获取对象类型 task 对象ID 9 的动态信息 @link
|
||||
- 测试获取对象类型 task 对象ID 10 的动态信息 @link
|
||||
- 测试获取对象类型 task 对象ID 11 的动态信息 @link
|
||||
- 测试获取对象类型 feedback 对象ID 2 的动态信息 @nochanged
|
||||
- 测试获取对象类型 feedback 对象ID 3 的动态信息 @nochanged
|
||||
- 测试获取对象类型 feedback 对象ID 4 的动态信息 @nochanged
|
||||
- 测试获取对象类型 feedback 对象ID 5 的动态信息 @nochanged
|
||||
- 测试获取对象类型 feedback 对象ID 6 的动态信息 @nochanged
|
||||
- 测试获取对象类型 feedback 对象ID 7 的动态信息 @nochanged
|
||||
- 测试获取对象类型 story 对象ID 36 的动态信息 @link
|
||||
- 测试获取对象类型 story 对象ID 37 的动态信息 @link
|
||||
- 测试获取对象类型 story 对象ID 38 的动态信息 @link
|
||||
@@ -108,9 +100,6 @@ cid=1
|
||||
- 测试获取对象类型 testtask 对象ID 2 的动态信息 @link
|
||||
- 测试获取对象类型 testtask 对象ID 3 的动态信息 @link
|
||||
- 测试获取对象类型 task 对象ID 12 的动态信息 @link
|
||||
- 测试 开源版 获取对象类型 risk 对象ID 1 的动态信息 @link
|
||||
- 测试 开源版 获取对象类型 isue 对象ID 1 的动态信息 @link
|
||||
- 测试 开源版 获取对象类型 opportunity 对象ID 1 的动态信息 @link
|
||||
- 测试获取对象类型 execution 对象ID 1 的动态信息 @link
|
||||
- 测试获取对象类型 execution 对象ID 2 的动态信息 @link
|
||||
- 测试获取对象类型 execution 对象ID 3 的动态信息 @link
|
||||
@@ -135,7 +124,6 @@ $objectType = array('story', 'task', 'bug', 'feedback', 'ticket', 'module', 're
|
||||
$storyID = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43);
|
||||
$taskID = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
|
||||
$bugID = array(1, 2, 3, 4, 5, 6, 7, 8, 9);
|
||||
$feedbackID = array(1, 2, 3, 4, 5, 6, 7);
|
||||
$hasOneID = array(1);
|
||||
$hasTwoID = array(1, 2);
|
||||
$todoID = array(1, 2, 3, 4, 5);
|
||||
@@ -179,12 +167,6 @@ r($action->getListTest($objectType[2], $bugID[0])) && p() && e('link'); // 测
|
||||
r($action->getListTest($objectType[2], $bugID[1])) && p() && e('link'); // 测试获取对象类型 bug 对象ID 2 的动态信息
|
||||
r($action->getListTest($objectType[2], $bugID[2])) && p() && e('link'); // 测试获取对象类型 bug 对象ID 3 的动态信息
|
||||
|
||||
// 操作是 tostory 对象类型 feedback
|
||||
r($action->getListTest($objectType[3], $feedbackID[0])) && p() && e('link'); // 测试获取对象类型 feedback 对象ID 1 的动态信息
|
||||
|
||||
// 操作是 tostory 对象类型 ticket
|
||||
r($action->getListTest($objectType[4], $hasOneID[0])) && p() && e('link'); // 测试获取对象类型 ticket 对象ID 1 的动态信息
|
||||
|
||||
// 操作是 moved 对象类型是 task
|
||||
r($action->getListTest($objectType[1], $taskID[0])) && p() && e('link'); // 测试获取对象类型 task 对象ID 1 的动态信息
|
||||
|
||||
@@ -281,24 +263,6 @@ r($action->getListTest($objectType[1], $taskID[9])) && p() && e('link'); // 测
|
||||
// 操作 deletechildrentask 对象类型 task
|
||||
r($action->getListTest($objectType[1], $taskID[10])) && p() && e('link'); // 测试获取对象类型 task 对象ID 11 的动态信息
|
||||
|
||||
// 操作 totask 对象类型 feedback
|
||||
r($action->getListTest($objectType[3], $feedbackID[1])) && p() && e('link'); // 测试获取对象类型 feedback 对象ID 2 的动态信息
|
||||
|
||||
// 操作 linkchildtask 对象类型 feedback
|
||||
r($action->getListTest($objectType[3], $feedbackID[2])) && p() && e('nochanged'); // 测试获取对象类型 feedback 对象ID 3 的动态信息
|
||||
|
||||
// 操作 unlinkchildrentask 对象类型 feedback
|
||||
r($action->getListTest($objectType[3], $feedbackID[3])) && p() && e('nochanged'); // 测试获取对象类型 feedback 对象ID 4 的动态信息
|
||||
|
||||
// 操作 linkparenttask 对象类型 feedback
|
||||
r($action->getListTest($objectType[3], $feedbackID[4])) && p() && e('nochanged'); // 测试获取对象类型 feedback 对象ID 5 的动态信息
|
||||
|
||||
// 操作 unlinkparenttask 对象类型 feedback
|
||||
r($action->getListTest($objectType[3], $feedbackID[5])) && p() && e('nochanged'); // 测试获取对象类型 feedback 对象ID 6 的动态信息
|
||||
|
||||
// 操作 deletechildrentask 对象类型 feedback
|
||||
r($action->getListTest($objectType[3], $feedbackID[6])) && p() && e('nochanged'); // 测试获取对象类型 feedback 对象ID 7 的动态信息
|
||||
|
||||
// 操作 linkchildstory
|
||||
r($action->getListTest($objectType[0], $storyID[35])) && p() && e('link'); // 测试获取对象类型 story 对象ID 36 的动态信息
|
||||
|
||||
@@ -323,19 +287,6 @@ r($action->getListTest($objectType[9], $testtaskID[1])) && p() && e('link'); //
|
||||
// 操作 testtaskclosed
|
||||
r($action->getListTest($objectType[9], $testtaskID[2])) && p() && e('link'); // 测试获取对象类型 testtask 对象ID 3 的动态信息
|
||||
|
||||
// 开源版 导入资产库相关
|
||||
// 操作 importfromstorylib
|
||||
r($action->getListTest($objectType[1], $taskID[11])) && p() && e('link'); // 测试获取对象类型 task 对象ID 12 的动态信息
|
||||
|
||||
// 操作 importfromrisklib
|
||||
r($action->getListTest($objectType[10], $hasOneID[0])) && p() && e('link'); // 测试 开源版 获取对象类型 risk 对象ID 1 的动态信息
|
||||
|
||||
// 操作 importfromissuelib
|
||||
r($action->getListTest($objectType[11], $hasOneID[0])) && p() && e('link'); // 测试 开源版 获取对象类型 isue 对象ID 1 的动态信息
|
||||
|
||||
// 操作 importfromopportunitylib
|
||||
r($action->getListTest($objectType[12], $hasOneID[0])) && p() && e('link'); // 测试 开源版 获取对象类型 opportunity 对象ID 1 的动态信息
|
||||
|
||||
// 操作 opened 对象类型 execution
|
||||
r($action->getListTest($objectType[13], $executionID[0])) && p() && e('link'); // 测试获取对象类型 execution 对象ID 1 的动态信息
|
||||
|
||||
|
||||
@@ -25,9 +25,10 @@ $config->admin->form->log['days'] = array('type' => 'int', 'required' => true, '
|
||||
$config->admin->form->resetpwdsetting = array();
|
||||
$config->admin->form->resetpwdsetting['resetPWDByMail'] = array('type' => 'int', 'required' => false, 'default' => 0);
|
||||
|
||||
$config->admin->form->register['mobile'] = array('type' => 'string', 'required' => true);
|
||||
$config->admin->form->register['code'] = array('type' => 'string', 'required' => true);
|
||||
$config->admin->form->register['agreeUX'] = array('type' => 'string', 'required' => false);
|
||||
$config->admin->form->register['mobile'] = array('type' => 'string', 'required' => true);
|
||||
$config->admin->form->register['code'] = array('type' => 'string', 'required' => true);
|
||||
$config->admin->form->register['agreeUX'] = array('type' => 'string', 'required' => false);
|
||||
$config->admin->form->register['fingerprint'] = array('type' => 'string', 'required' => true);
|
||||
|
||||
$config->admin->form->giftPackage['nickname'] = array('type' => 'string', 'required' => true);
|
||||
$config->admin->form->giftPackage['position'] = array('type' => 'string', 'required' => true);
|
||||
|
||||
@@ -580,6 +580,11 @@ class admin extends control
|
||||
$this->loadModel('setting')->setItem('system.admin.register.agreeUX', $agreeUX);
|
||||
$this->config->admin->agreeUX = $agreeUX;
|
||||
|
||||
$eventData = new stdClass();
|
||||
$eventData->fingerprint = $data->fingerprint;
|
||||
$eventData->location = 'join-community';
|
||||
$this->loadModel('misc')->sendInstallEvent($eventData);
|
||||
|
||||
$callBack = $this->loadModel('user')->isLogon() ? 'loadToRegister()' : 'loadToIndex()';
|
||||
|
||||
return $this->send(array('result' => 'success', 'message' => $this->lang->admin->community->joinSuccess, 'callback' => $callBack));
|
||||
|
||||
@@ -91,4 +91,34 @@ window.loadToIndex = function()
|
||||
setTimeout(function() {
|
||||
location.href = $.createLink('index', 'index');
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
window.getFingerprint = function()
|
||||
{
|
||||
$.getLib(config.webRoot + 'js/fingerprint/fingerprint.js', {root: false}, async function(fingerprint)
|
||||
{
|
||||
const agent = typeof(FingerprintJS) !== 'undefined' ? await FingerprintJS.load() : '';
|
||||
fingerprint = agent ? await agent.get() : '';
|
||||
fingerprint = fingerprint ? fingerprint.visitorId : '';
|
||||
$('.form-fingerprint').val(fingerprint);
|
||||
});
|
||||
}
|
||||
|
||||
window.skip = function()
|
||||
{
|
||||
ajaxInstallEvent('click-skip');
|
||||
setTimeout(function() {
|
||||
location.href = $.createLink('index', 'index');
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
window.ajaxInstallEvent = function(location = '')
|
||||
{
|
||||
$.getLib(config.webRoot + 'js/fingerprint/fingerprint.js', {root: false}, async function()
|
||||
{
|
||||
const agent = typeof(FingerprintJS) !== 'undefined' ? await FingerprintJS.load() : '';
|
||||
let fingerprint = agent ? await agent.get() : '';
|
||||
fingerprint = fingerprint ? fingerprint.visitorId : '';
|
||||
$.ajax({url: $.createLink('misc', 'installEvent'), type: "post", data: {fingerprint, location}, timeout: 2000});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ if(strpos($_SERVER['REQUEST_URI'], '_single=1') !== false)
|
||||
(
|
||||
setClass('btn capitalize skip-btn font-normal'),
|
||||
$lang->admin->community->skip,
|
||||
set::href(createLink('index')),
|
||||
on::click()->call('skip')
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -240,6 +240,12 @@ else
|
||||
$lang->admin->community->uxPlanStatusTitle
|
||||
)
|
||||
)
|
||||
),
|
||||
formHidden(
|
||||
'fingerprint',
|
||||
'',
|
||||
setClass('form-fingerprint'),
|
||||
on::init()->call('getFingerprint')
|
||||
)
|
||||
),
|
||||
$skip,
|
||||
|
||||
@@ -273,6 +273,7 @@ class blockModel extends model
|
||||
->markRight(1)
|
||||
->andWhere('vision')->eq($block->vision)
|
||||
->andWhere('hidden')->eq('0')
|
||||
->andWhere('account')->eq($this->app->user->account)
|
||||
->fetch('top');
|
||||
|
||||
if(!$top) $top = 0;
|
||||
|
||||
@@ -25,6 +25,12 @@ title=测试 blockModel->getByID();
|
||||
timeout=0
|
||||
cid=1
|
||||
|
||||
- 测试新加入的区块宽度为2的时候,top是否为6。 @6
|
||||
- 测试新加入的区块宽度为1的时候,top是否为15。 @15
|
||||
- 测试新加入的区块宽度为3的时候,top是否为3。 @3
|
||||
- width为0的时候,仍然是3 @3
|
||||
- width为100的时候,仍然是3 @3
|
||||
|
||||
*/
|
||||
|
||||
global $tester;
|
||||
@@ -44,3 +50,21 @@ $block->dashboard = 'my';
|
||||
$block->width = '1';
|
||||
$block->vision = 'rnd';
|
||||
r($tester->block->computeBlockTop($block)) && p('') && e('15'); // 测试新加入的区块宽度为1的时候,top是否为15。
|
||||
|
||||
$block = new stdclass();
|
||||
$block->dashboard = 'my';
|
||||
$block->width = '3';
|
||||
$block->vision = 'rnd';
|
||||
r($tester->block->computeBlockTop($block)) && p('') && e('3'); // 测试新加入的区块宽度为3的时候,top是否为3。
|
||||
|
||||
$block = new stdclass();
|
||||
$block->dashboard = 'my';
|
||||
$block->width = '0';
|
||||
$block->vision = 'rnd';
|
||||
r($tester->block->computeBlockTop($block)) && p('') && e('3'); // width为0的时候,仍然是3
|
||||
|
||||
$block = new stdclass();
|
||||
$block->dashboard = 'my';
|
||||
$block->width = '100';
|
||||
$block->vision = 'rnd';
|
||||
r($tester->block->computeBlockTop($block)) && p('') && e('3'); // width为100的时候,仍然是3
|
||||
@@ -27,7 +27,7 @@
|
||||
<th title='<?php echo $lang->product->plans?>' class='c-num w-120px'><?php echo $lang->product->plans;?></th>
|
||||
<th title='<?php echo $lang->product->releases?>' class='c-num w-100px'><?php echo $lang->product->releases;?></th>
|
||||
<th title='<?php echo $lang->product->activeStoriesTitle?>' class='c-num w-120px'><?php echo $lang->product->activeStories;?></th>
|
||||
<th title='<?php echo $lang->product->unResolvedBugsTitle?>' class='c-num w-100px'><?php echo $lang->product->unResolvedBugs;?></th>
|
||||
<th title='<?php echo $lang->product->unresolvedBugsTitle?>' class='c-num w-100px'><?php echo $lang->product->unresolvedBugs;?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -324,7 +324,7 @@ class branch extends control
|
||||
$targetBranch = $this->branch->mergeBranch($productID, $branchIdList, $postData);
|
||||
if(dao::isError()) return $this->sendError(dao::getError());
|
||||
|
||||
$this->loadModel('action')->create('branch', $targetBranch, 'MergedBranch', '', implode(',', $mergedBranches));
|
||||
$this->loadModel('action')->create('branch', $targetBranch, 'MergedBranch', '', implode(',', array_keys($mergedBranches)));
|
||||
if(dao::isError()) return $this->sendError(dao::getError());
|
||||
}
|
||||
return $this->sendSuccess(array('load' => true, 'closeModel' => true));
|
||||
|
||||
@@ -15,40 +15,40 @@ timeout=0
|
||||
cid=1
|
||||
|
||||
- 获取空产品列表下的分支数据 @0
|
||||
- 获取产品列表下的分支数据 @6:|0|1|3|2;7:|0|5|4|6;8:|0|7|9|8;9:|0|11|10|12;10:|0|13|15|14;1:|0;2:|0;
|
||||
- 获取产品列表下的分支数据 @6:|0|1|2|3;7:|0|4|5|6;8:|0|7|8|9;9:|0|10|11|12;10:|0|13|14|15;1:|0;2:|0;
|
||||
- 获取不存在产品列表下的分支数据 @0
|
||||
- 获取空产品列表下的未关闭分支数据 @0
|
||||
- 获取产品列表下的未关闭分支数据 @6:|0|1|2;7:|0|5|4;8:|0|7|8;9:|0|11|10;10:|0|13|14;2:|0;1:|0;
|
||||
- 获取产品列表下的未关闭分支数据 @6:|0|1|2;7:|0|4|5;8:|0|7|8;9:|0|10|11;10:|0|13|14;1:|0;2:|0;
|
||||
- 获取不存在产品列表下的未关闭分支数据 @0
|
||||
- 获取空产品列表下的忽略正常分支数据 @0
|
||||
- 获取产品列表下的忽略正常分支数据 @6:|0|1|3|2;7:|0|5|4|6;8:|0|7|9|8;9:|0|11|10|12;10:|0|13|15|14;
|
||||
- 获取产品列表下的忽略正常分支数据 @6:|0|1|2|3;7:|0|4|5|6;8:|0|7|8|9;9:|0|10|11|12;10:|0|13|14|15;
|
||||
- 获取不存在产品列表下的忽略正常分支数据 @0
|
||||
- 获取空产品列表下的非主干分支数据 @0
|
||||
- 获取产品列表下的非主干分支数据 @6:|1|3|2;7:|5|4|6;8:|7|9|8;9:|11|10|12;10:|13|15|14;1:|0;2:|0;
|
||||
- 获取产品列表下的非主干分支数据 @6:|1|2|3;7:|4|5|6;8:|7|8|9;9:|10|11|12;10:|13|14|15;1:|0;2:|0;
|
||||
- 获取不存在产品列表下的非主干分支数据 @0
|
||||
- 获取空产品列表下的未关闭且忽略正常分支数据 @0
|
||||
- 获取产品列表下的未关闭且忽略正常分支数据 @6:|0|1|2;7:|0|5|4;8:|0|7|8;9:|0|11|10;10:|0|13|14;
|
||||
- 获取产品列表下的未关闭且忽略正常分支数据 @6:|0|1|2;7:|0|4|5;8:|0|7|8;9:|0|10|11;10:|0|13|14;
|
||||
- 获取不存在产品列表下的未关闭且忽略正常分支数据 @0
|
||||
- 获取空产品列表下的未关闭且忽略正常且非主干分支数据 @0
|
||||
- 获取产品列表下的未关闭且忽略正常且非主干分支数据 @6:|1|3|2;7:|5|4|6;8:|7|9|8;9:|11|10|12;10:|13|15|14;
|
||||
- 获取产品列表下的未关闭且忽略正常且非主干分支数据 @6:|1|2|3;7:|4|5|6;8:|7|8|9;9:|10|11|12;10:|13|14|15;
|
||||
- 获取不存在产品列表下的未关闭且忽略正常且非主干分支数据 @0
|
||||
- 获取追加分支20和21和空产品列表下的分支数据 @0
|
||||
- 获取追加分支20和21和产品列表下的分支数据 @6:|0|1|3|2;7:|0|5|4|6;8:|0|7|9|8;9:|0|11|10|12;10:|0|13|15|14;1:|0;2:|0;
|
||||
- 获取追加分支20和21和产品列表下的分支数据 @6:|0|1|2|3;7:|0|4|5|6;8:|0|7|8|9;9:|0|10|11|12;10:|0|13|14|15;1:|0;2:|0;
|
||||
- 获取追加分支20和21和不存在产品列表下的分支数据 @0
|
||||
- 获取追加分支20和21和空产品列表下的未关闭分支数据 @0
|
||||
- 获取追加分支20和21和产品列表下的未关闭分支数据 @6:|0|1|2;7:|0|5|4;8:|0|7|8;9:|0|11|10;10:|0|13|14;2:|0;1:|0;
|
||||
- 获取追加分支20和21和产品列表下的未关闭分支数据 @6:|0|1|2;7:|0|4|5;8:|0|7|8;9:|0|10|11;10:|0|13|14;1:|0;2:|0;
|
||||
- 获取追加分支20和21和不存在产品列表下的未关闭分支数据 @0
|
||||
- 获取追加分支20和21和空产品列表下的忽略正常分支数据 @0
|
||||
- 获取追加分支20和21和产品列表下的忽略正常分支数据 @6:|0|1|3|2;7:|0|5|4|6;8:|0|7|9|8;9:|0|11|10|12;10:|0|13|15|14;
|
||||
- 获取追加分支20和21和产品列表下的忽略正常分支数据 @6:|0|1|2|3;7:|0|4|5|6;8:|0|7|8|9;9:|0|10|11|12;10:|0|13|14|15;
|
||||
- 获取追加分支20和21和不存在产品列表下的忽略正常分支数据 @0
|
||||
- 获取追加分支20和21和空产品列表下的非主干分支数据 @0
|
||||
- 获取追加分支20和21和产品列表下的非主干分支数据 @6:|1|3|2;7:|5|4|6;8:|7|9|8;9:|11|10|12;10:|13|15|14;1:|0;2:|0;
|
||||
- 获取追加分支20和21和产品列表下的非主干分支数据 @6:|1|2|3;7:|4|5|6;8:|7|8|9;9:|10|11|12;10:|13|14|15;1:|0;2:|0;
|
||||
- 获取追加分支20和21和不存在产品列表下的非主干分支数据 @0
|
||||
- 获取追加分支20和21和空产品列表下的未关闭且忽略正常分支数据 @0
|
||||
- 获取追加分支20和21和产品列表下的未关闭且忽略正常分支数据 @6:|0|1|2;7:|0|5|4;8:|0|7|8;9:|0|11|10;10:|0|13|14;
|
||||
- 获取追加分支20和21和产品列表下的未关闭且忽略正常分支数据 @6:|0|1|2;7:|0|4|5;8:|0|7|8;9:|0|10|11;10:|0|13|14;
|
||||
- 获取追加分支20和21和不存在产品列表下的未关闭且忽略正常分支数据 @0
|
||||
- 获取追加分支20和21和空产品列表下的未关闭且忽略正常且非主干分支数据 @0
|
||||
- 获取追加分支20和21和产品列表下的未关闭且忽略正常且非主干分支数据 @6:|1|3|2;7:|5|4|6;8:|7|9|8;9:|11|10|12;10:|13|15|14;
|
||||
- 获取追加分支20和21和产品列表下的未关闭且忽略正常且非主干分支数据 @6:|1|2|3;7:|4|5|6;8:|7|8|9;9:|10|11|12;10:|13|14|15;
|
||||
- 获取不存在产20和21和品列表下的未关闭且忽略正常且非主干分支数据 @0
|
||||
|
||||
*/
|
||||
@@ -64,39 +64,39 @@ $appendBranchList[1] = array(20, 21);
|
||||
|
||||
$branchTester = new branchTest();
|
||||
r($branchTester->getByProductsTest($productIdList[0], $paramList[0], $appendBranchList[0])) && p() && e('0'); // 获取空产品列表下的分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[0], $appendBranchList[0])) && p() && e('6:|0|1|3|2;7:|0|5|4|6;8:|0|7|9|8;9:|0|11|10|12;10:|0|13|15|14;1:|0;2:|0;'); // 获取产品列表下的分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[0], $appendBranchList[0])) && p() && e('6:|0|1|2|3;7:|0|4|5|6;8:|0|7|8|9;9:|0|10|11|12;10:|0|13|14|15;1:|0;2:|0;'); // 获取产品列表下的分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[2], $paramList[0], $appendBranchList[0])) && p() && e('0'); // 获取不存在产品列表下的分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[0], $paramList[1], $appendBranchList[0])) && p() && e('0'); // 获取空产品列表下的未关闭分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[1], $appendBranchList[0])) && p() && e('6:|0|1|2;7:|0|5|4;8:|0|7|8;9:|0|11|10;10:|0|13|14;2:|0;1:|0;'); // 获取产品列表下的未关闭分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[1], $appendBranchList[0])) && p() && e('6:|0|1|2;7:|0|4|5;8:|0|7|8;9:|0|10|11;10:|0|13|14;1:|0;2:|0;'); // 获取产品列表下的未关闭分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[2], $paramList[1], $appendBranchList[0])) && p() && e('0'); // 获取不存在产品列表下的未关闭分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[0], $paramList[2], $appendBranchList[0])) && p() && e('0'); // 获取空产品列表下的忽略正常分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[2], $appendBranchList[0])) && p() && e('6:|0|1|3|2;7:|0|5|4|6;8:|0|7|9|8;9:|0|11|10|12;10:|0|13|15|14;'); // 获取产品列表下的忽略正常分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[2], $appendBranchList[0])) && p() && e('6:|0|1|2|3;7:|0|4|5|6;8:|0|7|8|9;9:|0|10|11|12;10:|0|13|14|15;'); // 获取产品列表下的忽略正常分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[2], $paramList[2], $appendBranchList[0])) && p() && e('0'); // 获取不存在产品列表下的忽略正常分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[0], $paramList[3], $appendBranchList[0])) && p() && e('0'); // 获取空产品列表下的非主干分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[3], $appendBranchList[0])) && p() && e('6:|1|3|2;7:|5|4|6;8:|7|9|8;9:|11|10|12;10:|13|15|14;1:|0;2:|0;'); // 获取产品列表下的非主干分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[3], $appendBranchList[0])) && p() && e('6:|1|2|3;7:|4|5|6;8:|7|8|9;9:|10|11|12;10:|13|14|15;1:|0;2:|0;'); // 获取产品列表下的非主干分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[2], $paramList[3], $appendBranchList[0])) && p() && e('0'); // 获取不存在产品列表下的非主干分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[0], $paramList[4], $appendBranchList[0])) && p() && e('0'); // 获取空产品列表下的未关闭且忽略正常分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[4], $appendBranchList[0])) && p() && e('6:|0|1|2;7:|0|5|4;8:|0|7|8;9:|0|11|10;10:|0|13|14;'); // 获取产品列表下的未关闭且忽略正常分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[4], $appendBranchList[0])) && p() && e('6:|0|1|2;7:|0|4|5;8:|0|7|8;9:|0|10|11;10:|0|13|14;'); // 获取产品列表下的未关闭且忽略正常分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[2], $paramList[4], $appendBranchList[0])) && p() && e('0'); // 获取不存在产品列表下的未关闭且忽略正常分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[0], $paramList[5], $appendBranchList[0])) && p() && e('0'); // 获取空产品列表下的未关闭且忽略正常且非主干分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[5], $appendBranchList[0])) && p() && e('6:|1|3|2;7:|5|4|6;8:|7|9|8;9:|11|10|12;10:|13|15|14;'); // 获取产品列表下的未关闭且忽略正常且非主干分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[5], $appendBranchList[0])) && p() && e('6:|1|2|3;7:|4|5|6;8:|7|8|9;9:|10|11|12;10:|13|14|15;'); // 获取产品列表下的未关闭且忽略正常且非主干分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[2], $paramList[5], $appendBranchList[0])) && p() && e('0'); // 获取不存在产品列表下的未关闭且忽略正常且非主干分支数据
|
||||
|
||||
r($branchTester->getByProductsTest($productIdList[0], $paramList[0], $appendBranchList[1])) && p() && e('0'); // 获取追加分支20和21和空产品列表下的分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[0], $appendBranchList[1])) && p() && e('6:|0|1|3|2;7:|0|5|4|6;8:|0|7|9|8;9:|0|11|10|12;10:|0|13|15|14;1:|0;2:|0;'); // 获取追加分支20和21和产品列表下的分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[0], $appendBranchList[1])) && p() && e('6:|0|1|2|3;7:|0|4|5|6;8:|0|7|8|9;9:|0|10|11|12;10:|0|13|14|15;1:|0;2:|0;'); // 获取追加分支20和21和产品列表下的分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[2], $paramList[0], $appendBranchList[1])) && p() && e('0'); // 获取追加分支20和21和不存在产品列表下的分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[0], $paramList[1], $appendBranchList[1])) && p() && e('0'); // 获取追加分支20和21和空产品列表下的未关闭分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[1], $appendBranchList[1])) && p() && e('6:|0|1|2;7:|0|5|4;8:|0|7|8;9:|0|11|10;10:|0|13|14;2:|0;1:|0;'); // 获取追加分支20和21和产品列表下的未关闭分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[1], $appendBranchList[1])) && p() && e('6:|0|1|2;7:|0|4|5;8:|0|7|8;9:|0|10|11;10:|0|13|14;1:|0;2:|0;'); // 获取追加分支20和21和产品列表下的未关闭分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[2], $paramList[1], $appendBranchList[1])) && p() && e('0'); // 获取追加分支20和21和不存在产品列表下的未关闭分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[0], $paramList[2], $appendBranchList[1])) && p() && e('0'); // 获取追加分支20和21和空产品列表下的忽略正常分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[2], $appendBranchList[1])) && p() && e('6:|0|1|3|2;7:|0|5|4|6;8:|0|7|9|8;9:|0|11|10|12;10:|0|13|15|14;'); // 获取追加分支20和21和产品列表下的忽略正常分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[2], $appendBranchList[1])) && p() && e('6:|0|1|2|3;7:|0|4|5|6;8:|0|7|8|9;9:|0|10|11|12;10:|0|13|14|15;'); // 获取追加分支20和21和产品列表下的忽略正常分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[2], $paramList[2], $appendBranchList[1])) && p() && e('0'); // 获取追加分支20和21和不存在产品列表下的忽略正常分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[0], $paramList[3], $appendBranchList[1])) && p() && e('0'); // 获取追加分支20和21和空产品列表下的非主干分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[3], $appendBranchList[1])) && p() && e('6:|1|3|2;7:|5|4|6;8:|7|9|8;9:|11|10|12;10:|13|15|14;1:|0;2:|0;'); // 获取追加分支20和21和产品列表下的非主干分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[3], $appendBranchList[1])) && p() && e('6:|1|2|3;7:|4|5|6;8:|7|8|9;9:|10|11|12;10:|13|14|15;1:|0;2:|0;'); // 获取追加分支20和21和产品列表下的非主干分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[2], $paramList[3], $appendBranchList[1])) && p() && e('0'); // 获取追加分支20和21和不存在产品列表下的非主干分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[0], $paramList[4], $appendBranchList[1])) && p() && e('0'); // 获取追加分支20和21和空产品列表下的未关闭且忽略正常分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[4], $appendBranchList[1])) && p() && e('6:|0|1|2;7:|0|5|4;8:|0|7|8;9:|0|11|10;10:|0|13|14;'); // 获取追加分支20和21和产品列表下的未关闭且忽略正常分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[4], $appendBranchList[1])) && p() && e('6:|0|1|2;7:|0|4|5;8:|0|7|8;9:|0|10|11;10:|0|13|14;'); // 获取追加分支20和21和产品列表下的未关闭且忽略正常分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[2], $paramList[4], $appendBranchList[1])) && p() && e('0'); // 获取追加分支20和21和不存在产品列表下的未关闭且忽略正常分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[0], $paramList[5], $appendBranchList[1])) && p() && e('0'); // 获取追加分支20和21和空产品列表下的未关闭且忽略正常且非主干分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[5], $appendBranchList[1])) && p() && e('6:|1|3|2;7:|5|4|6;8:|7|9|8;9:|11|10|12;10:|13|15|14;'); // 获取追加分支20和21和产品列表下的未关闭且忽略正常且非主干分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[1], $paramList[5], $appendBranchList[1])) && p() && e('6:|1|2|3;7:|4|5|6;8:|7|8|9;9:|10|11|12;10:|13|14|15;'); // 获取追加分支20和21和产品列表下的未关闭且忽略正常且非主干分支数据
|
||||
r($branchTester->getByProductsTest($productIdList[2], $paramList[5], $appendBranchList[1])) && p() && e('0'); // 获取不存在产20和21和品列表下的未关闭且忽略正常且非主干分支数据
|
||||
|
||||
@@ -11,3 +11,5 @@ fields:
|
||||
range: 6-10{3},11-15{2},1-5{3}
|
||||
- field: status
|
||||
range: 'active{2},closed{1}'
|
||||
- field: order
|
||||
range: 1-100
|
||||
|
||||
@@ -248,7 +248,8 @@ class bug extends control
|
||||
|
||||
if(!empty($_POST))
|
||||
{
|
||||
$formData = form::data($this->config->bug->form->create);
|
||||
if(!empty($params['productID'])) $product = $this->bug->fetchByID((int)$params['productID'], 'product');
|
||||
$formData = form::data($this->config->bug->form->create, 0, !empty($product->workflowGroup) ? $product->workflowGroup : 0);
|
||||
$bug = $this->bugZen->prepareCreateExtras($formData);
|
||||
|
||||
$bugID = $this->bug->create($bug, $from);
|
||||
@@ -268,8 +269,9 @@ class bug extends control
|
||||
|
||||
/* 初始化一个bug对象,尽可能把属性都绑定到bug对象上,extract() 出来的变量除外。 */
|
||||
/* Init bug, give bug as many variables as possible, except for extract variables. */
|
||||
$fields = array('productID' => $productID, 'branch' => $branch, 'title' => ($from == 'sonarqube' ? $_COOKIE['sonarqubeIssue'] : ''), 'assignedTo' => (isset($product->QD) ? $product->QD : ''));
|
||||
$fields = array('id' => 0, 'productID' => $productID, 'branch' => $branch, 'title' => ($from == 'sonarqube' ? $_COOKIE['sonarqubeIssue'] : ''), 'assignedTo' => (isset($product->QD) ? $product->QD : ''));
|
||||
$fields = array_merge($fields, $params);
|
||||
$fields['product'] = $fields['productID'];
|
||||
|
||||
$bug = $this->bugZen->initBug($fields);
|
||||
|
||||
@@ -664,9 +666,7 @@ class bug extends control
|
||||
{
|
||||
/* 设置模块数据源。 */
|
||||
/* Set module data source. */
|
||||
$this->config->bug->dtable->fieldList['module']['dataSource']['module'] = 'tree';
|
||||
$this->config->bug->dtable->fieldList['module']['dataSource']['method'] = 'getAllModulePairs';
|
||||
$this->config->bug->dtable->fieldList['module']['dataSource']['params'] = 'bug';
|
||||
$this->config->bug->dtable->fieldList['module']['dataSource'] = array('module' => 'tree', 'method' => 'getAllModulePairs', 'params' => 'bug');
|
||||
|
||||
/* 如果导出执行的bug,设置数据源。 */
|
||||
/* In execution, set data source. */
|
||||
@@ -678,6 +678,7 @@ class bug extends control
|
||||
$this->config->bug->dtable->fieldList['execution']['dataSource'] = array('module' => 'execution', 'method' => 'getPairs', 'params' => $projectID);
|
||||
}
|
||||
}
|
||||
$this->config->bug->dtable->fieldList['assignedTo']['dataSource'] = array('module' => 'user', 'method' => 'getPairs', 'params' => array());
|
||||
|
||||
$this->loadModel('transfer')->export('bug');
|
||||
$this->fetch('file', 'export2' . $this->post->fileType, $_POST);
|
||||
|
||||
@@ -70,8 +70,11 @@ window.renderRowData = function($row, index, row)
|
||||
let $assignedTo = $row.find('.form-batch-input[data-name="assignedTo"]');
|
||||
if(row.status == 'closed')
|
||||
{
|
||||
/* If the status of the bug is closed, assigner of the bug is closed. */
|
||||
$assignedTo.replaceWith('<input class="form-control form-batch-input" name="assignedTo[' + row.id + ']" id="assignedTo_0" data-name="assignedTo" disabled="disabled" value="' + row.assignedTo.slice(0, 1).toUpperCase() + row.assignedTo.slice(1) + '">');
|
||||
$row.find('[data-name="assignedTo"]').find('.picker-box').on('inited', function(e, info)
|
||||
{
|
||||
let $plan = info[0];
|
||||
$plan.render({disabled: true});
|
||||
});
|
||||
}
|
||||
else if(tab == 'project' || tab == 'execution')
|
||||
{
|
||||
|
||||
@@ -342,10 +342,10 @@ function loadAssignedToByModule(moduleID, productID)
|
||||
}
|
||||
if(!isExist && account)
|
||||
{
|
||||
assignedToItems = {text: realName, value: account, keys: realName};
|
||||
$assignedToPicker.render({items: data, value: account});
|
||||
assignedToItems.push({text: realName, value: account, keys: realName, key: account});
|
||||
$assignedToPicker.render({items: Array.from(assignedToItems)});
|
||||
}
|
||||
$assignedToPicker.$.setValue(account);
|
||||
if(!bug.assignedTo) $assignedToPicker.$.setValue(account);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
window.reloadByProduct = function(e)
|
||||
{
|
||||
let extra = 'bugID=' + copyBugID + ',projectID=' + projectID + ',executionID=' + executionID;
|
||||
if(typeof fromID != 'undefined') extra += ',fromID=' + fromID;
|
||||
if(typeof fromType != 'undefined') extra += ',fromType=' + fromType;
|
||||
loadPage($.createLink('bug', 'create', 'productID=' + $(e.target).val() + '&branch=0&extra=' + extra));
|
||||
}
|
||||
|
||||
@@ -404,7 +404,7 @@ class bugModel extends model
|
||||
*/
|
||||
public function assign(object $bug, object $oldBug): bool
|
||||
{
|
||||
$this->dao->update(TABLE_BUG)->data($bug, 'comment')->autoCheck()->checkFlow()->where('id')->eq($bug->id)->exec();
|
||||
$this->dao->update(TABLE_BUG)->data($bug, 'comment')->autoCheck()->checkFlow()->where('id')->eq($bug->id)->andWhere('status')->ne('closed')->exec();
|
||||
if(dao::isError()) return false;
|
||||
|
||||
/* 记录指派动作。*/
|
||||
@@ -613,7 +613,7 @@ class bugModel extends model
|
||||
if($changes || $files)
|
||||
{
|
||||
$fileAction = !empty($files) ? $this->lang->addFiles . implode(',', $files) . "\n" : '';
|
||||
$actionID = $this->loadModel('action')->create('bug', $bug->id, 'Activated', $fileAction . isset($bug->comment) ? $bug->comment : $this->post->comment);
|
||||
$actionID = $this->loadModel('action')->create('bug', $bug->id, 'Activated', $fileAction . $this->post->comment);
|
||||
$this->action->logHistory($actionID, $changes);
|
||||
}
|
||||
if($this->config->edition != 'open' && $oldBug->feedback) $this->loadModel('feedback')->updateStatus('bug', $oldBug->feedback, $bug->status, $oldBug->status, $oldBug->id);
|
||||
|
||||
@@ -95,7 +95,7 @@ $bug6->comment = '';
|
||||
$bug = new bugTest();
|
||||
r($bug->assignTest($bug1)) && p('assignedTo;mailto', ';') && e('user2;user1,user3;'); // 指派bug状态为激活的bug 更改指派人
|
||||
r($bug->assignTest($bug2)) && p('assignedTo;mailto', ';') && e('user2;user2;'); // 指派bug状态为解决的bug 更改指派人
|
||||
r($bug->assignTest($bug3)) && p('assignedTo;mailto', ';') && e('user2;user3'); // 指派bug状态为关闭的bug 更改指派人
|
||||
r($bug->assignTest($bug3)) && p('assignedTo;mailto', ';') && e('user1;admin'); // 指派bug状态为关闭的bug 更改指派人
|
||||
r($bug->assignTest($bug4)) && p('assignedTo;mailto', ';') && e('user1;user1;'); // 指派bug状态为激活的bug 不更改指派人
|
||||
r($bug->assignTest($bug5)) && p('assignedTo;mailto', ';') && e('user1;user2;'); // 指派bug状态为解决的bug 不更改指派人
|
||||
r($bug->assignTest($bug6)) && p('assignedTo;mailto', ';') && e('user1;user3'); // 指派bug状态为关闭的bug 不更改指派人
|
||||
r($bug->assignTest($bug6)) && p('assignedTo;mailto', ';') && e('user1;admin'); // 指派bug状态为关闭的bug 不更改指派人
|
||||
|
||||
@@ -16,7 +16,9 @@ include($this->app->getModuleRoot() . 'ai/ui/inputinject.html.php');
|
||||
$fields = useFields('bug.create');
|
||||
if(!empty($executionType) && $executionType == 'kanban') $fields->merge('bug.kanban');
|
||||
|
||||
$fields->autoLoad('branch', 'module,execution,project,story,task,assignedTo')
|
||||
|
||||
$fields->autoLoad('product', array('items' => 'product,module,execution,project,story,task,assignedTo,' . (!empty($lang->bug->flowExtraFields) ? implode(',', $lang->bug->flowExtraFields) : ''), 'updateOrders' => true))
|
||||
->autoLoad('branch', 'module,execution,project,story,task,assignedTo')
|
||||
->autoLoad('module', 'assignedTo,story')
|
||||
->autoLoad('project', 'project,execution,story,task,assignedTo,injection,identify,openedBuild')
|
||||
->autoLoad('execution', 'execution,story,task,assignedTo,openedBuild')
|
||||
@@ -40,8 +42,7 @@ jsVar('projectExecutionPairs', $projectExecutionPairs);
|
||||
|
||||
formGridPanel
|
||||
(
|
||||
on::change('[name="product"]', 'reloadByProduct'),
|
||||
on::change('[name="branch"], [name="project"], [name="execution"]', 'loadBuilds'),
|
||||
on::change('[name="product"], [name="branch"], [name="project"], [name="execution"]', 'loadBuilds'),
|
||||
set::title($lang->bug->create),
|
||||
set::fields($fields),
|
||||
set::loadUrl($loadUrl)
|
||||
|
||||
@@ -520,7 +520,7 @@ detailBody
|
||||
picker
|
||||
(
|
||||
on::click('setDuplicateBug'),
|
||||
set::items(array()),
|
||||
set::items($duplicateBugs),
|
||||
set::name('duplicateBug'),
|
||||
set::value($bug->duplicateBug)
|
||||
)
|
||||
|
||||
@@ -1162,8 +1162,12 @@ class bugZen extends bug
|
||||
|
||||
$this->assignVarsForEdit($bug, $product);
|
||||
|
||||
$duplicateBugs = $this->bug->getProductBugPairs($bug->product, $bug->branch);
|
||||
unset($duplicateBugs[$bug->id]);
|
||||
|
||||
$this->view->title = $this->lang->bug->edit . "BUG #$bug->id $bug->title - " . $this->products[$bug->product];
|
||||
$this->view->bug = $bug;
|
||||
$this->view->duplicateBugs = $duplicateBugs;
|
||||
$this->view->product = $product;
|
||||
$this->view->moduleOptionMenu = $moduleOptionMenu;
|
||||
$this->view->projectID = $bug->project;
|
||||
|
||||
@@ -326,8 +326,11 @@ class caselibModel extends model
|
||||
$caseData->lib = $libID;
|
||||
if(!empty($data->id[$key]) && !$this->post->insert)
|
||||
{
|
||||
$oldCase = $oldCases[$data->id[$key]];
|
||||
if(!isset($oldCase->steps)) $oldCase->steps = zget($oldSteps, $data->id[$key], array());
|
||||
$caseID = $data->id[$key];
|
||||
if(!isset($oldCases[$caseID])) continue;
|
||||
|
||||
$oldCase = $oldCases[$caseID];
|
||||
if(!isset($oldCase->steps)) $oldCase->steps = zget($oldSteps, $caseID, array());
|
||||
$this->caselibTao->updateImportedCase($key, $caseData, $data, $forceNotReview, $oldCase);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -33,5 +33,6 @@ r($cneModel->backupDetailTest($instance, 0)) && p() && e('0'); // 错误的空
|
||||
$instance->spaceData->k8space = 'quickon-system';
|
||||
$instance->k8name = 'zentaopaas';
|
||||
|
||||
r($cneModel->backupDetailTest($instance, 1)->db) && p('0:db_type,status') && e('mysql,completed'); // 备份的数据库
|
||||
r($cneModel->backupDetailTest($instance, 2)->volume) && p('0:volume,status') && e('data,completed'); // 备份的数据
|
||||
$backupDetail = $cneModel->backupDetailTest($instance, 1);
|
||||
r($backupDetail->db) && p('0:db_type,status') && e('mysql,completed'); // 备份的数据库
|
||||
r($backupDetail->volume) && p('0:volume,status') && e('data,completed'); // 备份的数据
|
||||
|
||||
@@ -149,6 +149,7 @@ $lang->noticeOkFile = 'Aus Sicherheitsgrü nden muss Ihr Adminkonto bestätig
|
||||
$lang->noticeDrag = 'Click to add or drag to upload, no more than %s';
|
||||
$lang->allProgress = 'All Progress';
|
||||
$lang->hasReviewed = 'The content has already been reviewed and does not need further review !';
|
||||
$lang->appNotFound = 'Sie haben keine Berechtigung, um auf diese Anwendung zuzugreifen. Bitte überprüfen Sie die Berechtigungseinstellungen.';
|
||||
|
||||
$lang->fieldDisplaySetting = 'Field Display Setting';
|
||||
$lang->fieldSettingTip = 'The following fields are collapsed by default, click on the "Show more fields" button to expand the view, if you want some fields not to be collapsed by default, you can fix it with a pin.';
|
||||
|
||||
@@ -149,6 +149,7 @@ $lang->noticeOkFile = 'For security reasons, your Admin account has to be con
|
||||
$lang->noticeDrag = 'Click to add or drag to upload, no more than %s';
|
||||
$lang->allProgress = 'All Progress';
|
||||
$lang->hasReviewed = 'The content has already been reviewed and does not need further review !';
|
||||
$lang->appNotFound = 'You do not have permission to access this application. Please check the permission settings.';
|
||||
|
||||
$lang->fieldDisplaySetting = 'Field Display Setting';
|
||||
$lang->fieldSettingTip = 'The following fields are collapsed by default, click on the "Show more fields" button to expand the view, if you want some fields not to be collapsed by default, you can fix it with a pin.';
|
||||
|
||||
@@ -149,6 +149,7 @@ $lang->noticeOkFile = 'For security reasons, your Admin account has to be con
|
||||
$lang->noticeDrag = 'Click to add or drag to upload, no more than %s';
|
||||
$lang->allProgress = 'All Progress';
|
||||
$lang->hasReviewed = 'The content has already been reviewed and does not need further review !';
|
||||
$lang->appNotFound = 'Vous n\'avez pas les permissions pour accéder à cette application. Veuillez vérifier les paramètres de permissions.';
|
||||
|
||||
$lang->fieldDisplaySetting = 'Field Display Setting';
|
||||
$lang->fieldSettingTip = 'The following fields are collapsed by default, click on the "Show more fields" button to expand the view, if you want some fields not to be collapsed by default, you can fix it with a pin.';
|
||||
|
||||
@@ -149,6 +149,7 @@ $lang->noticeOkFile = '为了安全起见,系统需要确认您的管理员
|
||||
$lang->noticeDrag = '可点击添加或拖拽上传, 不超过 %s';
|
||||
$lang->allProgress = '总进度';
|
||||
$lang->hasReviewed = '该内容已经评审过,无需重复评审!';
|
||||
$lang->appNotFound = '无权访问该应用,请检查权限设置。';
|
||||
|
||||
$lang->fieldDisplaySetting = '字段显示设置';
|
||||
$lang->fieldSettingTip = '以下字段默认收起,点击「显示更多字段」按钮展开查看,若您希望某些字段默认不收起,通过图钉固定即可。';
|
||||
|
||||
+17
-9
@@ -1456,28 +1456,36 @@ eof;
|
||||
}
|
||||
}
|
||||
|
||||
$recomputedRights = array_merge($rights, $programRightGroup);
|
||||
foreach($programRightGroup as $module => $methods)
|
||||
{
|
||||
foreach($methods as $method => $label)
|
||||
{
|
||||
$module = strtolower($module);
|
||||
$method = strtolower($method);
|
||||
$rights[$module][$method] = $label;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set base priv for project. */
|
||||
$projectRights = zget($this->app->user->rights['rights'], 'project', array());
|
||||
if(isset($projectRights['browse']) and !isset($recomputedRights['project']['browse'])) $recomputedRights['project']['browse'] = 1;
|
||||
if(isset($projectRights['kanban']) and !isset($recomputedRights['project']['kanban'])) $recomputedRights['project']['kanban'] = 1;
|
||||
if(isset($projectRights['index']) and !isset($recomputedRights['project']['index'])) $recomputedRights['project']['index'] = 1;
|
||||
if(isset($projectRights['browse']) and !isset($rights['project']['browse'])) $rights['project']['browse'] = 1;
|
||||
if(isset($projectRights['kanban']) and !isset($rights['project']['kanban'])) $rights['project']['kanban'] = 1;
|
||||
if(isset($projectRights['index']) and !isset($rights['project']['index'])) $rights['project']['index'] = 1;
|
||||
|
||||
if(isset($projectPrivs->execution->linkStory)) $recomputedRights['execution']['linkstory'] = 1;
|
||||
if(isset($projectPrivs->execution->batchLinkStory)) $recomputedRights['execution']['batchlinkstory'] = 1;
|
||||
if(isset($projectPrivs->execution->unLinkStory)) $recomputedRights['execution']['unlinkstory'] = 1;
|
||||
if(isset($projectPrivs->execution->linkStory)) $rights['execution']['linkstory'] = 1;
|
||||
if(isset($projectPrivs->execution->batchLinkStory)) $rights['execution']['batchlinkstory'] = 1;
|
||||
if(isset($projectPrivs->execution->unLinkStory)) $rights['execution']['unlinkstory'] = 1;
|
||||
|
||||
if(isset($projectPrivs->story))
|
||||
{
|
||||
foreach($projectPrivs->story as $method => $label)
|
||||
{
|
||||
$method = strtolower($method);
|
||||
$recomputedRights['story'][$method] = 1;
|
||||
$rights['story'][$method] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
$this->app->user->rights['rights'] = $recomputedRights;
|
||||
$this->app->user->rights['rights'] = $rights;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ timeout=0
|
||||
cid=1
|
||||
|
||||
- 查看重设权限之前user1的权限数量 @2
|
||||
- 查看重设权限之后user1的权限数量 @4
|
||||
- 重设权限后,查看user1的权限第index条的index属性 @1
|
||||
- 重设权限后,查看user1的权限第my条的index属性 @1
|
||||
- 重设权限后,查看user1的权限第story条的edit属性 @1
|
||||
- 重设权限后,查看user1的权限第bug条的delete属性 @1
|
||||
|
||||
@@ -28,5 +31,9 @@ r(count($app->user->rights['rights'])) && p() && e(2); // 查看重设权限之
|
||||
|
||||
$tester->common->resetProjectPriv(12);
|
||||
|
||||
r($app->user->rights['rights']) && p('story:edit') && e(1); // 重设权限后,查看user1的权限
|
||||
r($app->user->rights['rights']) && p('bug:delete') && e(1); // 重设权限后,查看user1的权限
|
||||
r(count($app->user->rights['rights'])) && p() && e(4); // 查看重设权限之后user1的权限数量
|
||||
|
||||
r($app->user->rights['rights']) && p('index:index') && e(1); // 重设权限后,查看user1的权限
|
||||
r($app->user->rights['rights']) && p('my:index') && e(1); // 重设权限后,查看user1的权限
|
||||
r($app->user->rights['rights']) && p('story:edit') && e(1); // 重设权限后,查看user1的权限
|
||||
r($app->user->rights['rights']) && p('bug:delete') && e(1); // 重设权限后,查看user1的权限
|
||||
|
||||
@@ -270,7 +270,7 @@ class cronModel extends model
|
||||
->where('owner')->eq('system')
|
||||
->andWhere('module')->eq('cron')
|
||||
->andWhere('section')->eq('scheduler')
|
||||
->andWhere('`key`')->eq($execId)
|
||||
->andWhere('`key`')->eq('execId')
|
||||
->exec();
|
||||
$this->dao->delete()->from(TABLE_QUEUE)->where('createdDate')->le(date("Y-m-d H:i:s", strtotime("-1 week")))->exec();
|
||||
|
||||
|
||||
@@ -76,6 +76,18 @@ class designModel extends model
|
||||
}
|
||||
|
||||
$designID = $this->dao->lastInsertID();
|
||||
if($this->config->edition != 'open' && !empty($design->story))
|
||||
{
|
||||
$relation = new stdClass();
|
||||
$relation->AID = $design->story;
|
||||
$relation->AType = $stories[$design->story]->type;
|
||||
$relation->relation = 'generated';
|
||||
$relation->BID = $designID;
|
||||
$relation->BType = 'design';
|
||||
$relation->product = 0;
|
||||
$this->dao->replace(TABLE_RELATION)->data($relation)->exec();
|
||||
}
|
||||
|
||||
$this->action->create('design', $designID, 'created');
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,9 @@ class linkCommitTester extends tester
|
||||
$form->wait(1);
|
||||
$form->dom->selectAllBtn->click();
|
||||
$form->dom->saveBtn->click();
|
||||
$form->wait(3);
|
||||
$form->dom->viewCommitBtn->click();
|
||||
$form->wait(3);
|
||||
$string = $form->dom->commitNum->getText();
|
||||
$commitNum = preg_replace('/\D/', '', $string); //从字符串中提取数字部分
|
||||
if($commitNum == $expectNum) return $this->success('关联提交成功');
|
||||
|
||||
@@ -970,8 +970,16 @@ class doc extends control
|
||||
$currentAccount = $this->app->user->account;
|
||||
$isAuthorOrAdmin = $doc->acl == 'private' && ($doc->addedBy == $currentAccount || $this->app->user->admin);
|
||||
$isInEditUsers = strpos(",$doc->users,", ",$currentAccount,") !== false;
|
||||
$isInEditGroups = false;
|
||||
if(!empty($doc->groups))
|
||||
{
|
||||
foreach($this->app->user->groups as $groupID)
|
||||
{
|
||||
if(strpos(",$doc->groups,", ",$groupID,") !== false) $isInEditGroups = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!$isOpen && !$isAuthorOrAdmin && !$isInEditUsers) return $this->send(array('result' => 'fail', 'message' => $this->lang->doc->needEditable));
|
||||
if(!$isOpen && !$isAuthorOrAdmin && !$isInEditUsers && !$isInEditGroups) return $this->send(array('result' => 'fail', 'message' => $this->lang->doc->needEditable));
|
||||
|
||||
if($doc->type == 'chapter') $this->lang->doc->title = $this->lang->doc->chapterName;
|
||||
|
||||
|
||||
@@ -2298,6 +2298,8 @@ class docModel extends model
|
||||
if($append && !isset($products[$append])) $products[$append] = $this->product->getByID($append);
|
||||
foreach($products as $id => $product)
|
||||
{
|
||||
if(!$product) continue;
|
||||
|
||||
if($product->status != 'closed' && $product->PO == $this->app->user->account)
|
||||
{
|
||||
$myObjects[$id] = $product->name;
|
||||
|
||||
+1
-1
@@ -272,7 +272,7 @@ class docTao extends docModel
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
public function doUpdateDoc(int $docID, object $doc, $basicInfoChanged = false)
|
||||
public function doUpdateDoc(int $docID, object $doc, bool $basicInfoChanged = false)
|
||||
{
|
||||
$this->dao->update(TABLE_DOC)->data($doc)->autoCheck()->where('id')->eq($docID)->exec();
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . '/test/lib/ui.php';
|
||||
class createSpaceTester extends tester
|
||||
{
|
||||
/**
|
||||
* 创建空间。
|
||||
* Create space.
|
||||
*
|
||||
* @param string $spaceType
|
||||
* @param string $name
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function createSpace($spaceType, $name)
|
||||
{
|
||||
$form = $this->initForm('doc', $spaceType, '', 'appIframe-doc');
|
||||
$form->wait(1);
|
||||
$form->dom->btn($this->lang->doc->createSpace)->click();
|
||||
$form->wait(1);
|
||||
$form->dom->name->setValue($name);
|
||||
$form->dom->xpath['saveBtn'] = "//button[@type='submit']";
|
||||
$form->dom->saveBtn->click();
|
||||
$form->wait(1);
|
||||
|
||||
if(empty($name))
|
||||
{
|
||||
$form = $this->loadPage();
|
||||
if(!is_object($form->dom->nameTip)) return $this->failed('空间名称为空时没有提示');
|
||||
if($form->dom->nameTip->getText() != sprintf($this->lang->error->notempty, $this->lang->doclib->spaceName)) return $this->failed('空间名称为空时提示正确');
|
||||
return $this->success('空间名称为空时提示正确');
|
||||
}
|
||||
|
||||
$form = $this->initForm('doc', $spaceType, '', 'appIframe-doc');
|
||||
$form->wait(1);
|
||||
$form->dom->xpath['newSpaceName'] = "//*[@id='mainContent']/div/div/div[2]/div[last()]//strong";
|
||||
|
||||
if(is_object($form->dom->newSpaceName) && $form->dom->newSpaceName->getText() == $name) return $this->success('空间创建成功');
|
||||
return $this->failed('空间创建失败');
|
||||
}
|
||||
}
|
||||
@@ -1372,12 +1372,12 @@ class docTest
|
||||
* 获取文档动态。
|
||||
* Get document dynamic.
|
||||
*
|
||||
* @param int $recPerPage
|
||||
* @param int $pageID
|
||||
* @param int $recPerPage
|
||||
* @param int $pageID
|
||||
* @access public
|
||||
* @return array|string
|
||||
* @return array|int
|
||||
*/
|
||||
public function getDynamicTest(int $recPerPage, int $pageID): array|string
|
||||
public function getDynamicTest(int $recPerPage, int $pageID): array|int
|
||||
{
|
||||
$this->objectModel->app->loadClass('pager', true);
|
||||
$pager = new pager(0, $recPerPage, $pageID);
|
||||
@@ -1385,10 +1385,9 @@ class docTest
|
||||
$actions = $this->objectModel->getDynamic($pager);
|
||||
$idList = array();
|
||||
foreach($actions as $action) $idList []= $action->id;
|
||||
sort($idList);
|
||||
|
||||
if(dao::isError()) return dao::getError();
|
||||
return implode(';', $idList);
|
||||
return count($idList);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
/**
|
||||
|
||||
title=测试 docModel->checkOtherEditing();
|
||||
timeout=0
|
||||
cid=1
|
||||
|
||||
- 测试空数据 @0
|
||||
- 测试文档1是否有在编辑的人 @1
|
||||
- 测试文档2是否有在编辑的人 @0
|
||||
- 测试文档3是否有在编辑的人 @1
|
||||
- 测试不存在的文档是否有在编辑的人 @0
|
||||
|
||||
*/
|
||||
@@ -18,13 +20,14 @@ include dirname(__FILE__, 2) . '/lib/doc.unittest.class.php';
|
||||
$timestamp = time();
|
||||
$docTable = zenData('doc')->loadYaml('doc');
|
||||
$docTable->editingDate->range("`{\"user1\": $timestamp}`,[]");
|
||||
$docTable->gen(2);
|
||||
$docTable->gen(3);
|
||||
zenData('user')->gen(5);
|
||||
|
||||
$docIds = array(0, 1, 2, 3);
|
||||
$docIds = array(0, 1, 2, 3, 100);
|
||||
|
||||
$docTester = new docTest();
|
||||
r($docTester->checkOtherEditingTest($docIds[0])) && p() && e('0'); // 测试空数据
|
||||
r($docTester->checkOtherEditingTest($docIds[1])) && p() && e('1'); // 测试文档1是否有在编辑的人
|
||||
r($docTester->checkOtherEditingTest($docIds[2])) && p() && e('0'); // 测试文档2是否有在编辑的人
|
||||
r($docTester->checkOtherEditingTest($docIds[3])) && p() && e('0'); // 测试不存在的文档是否有在编辑的人
|
||||
r($docTester->checkOtherEditingTest($docIds[3])) && p() && e('1'); // 测试文档3是否有在编辑的人
|
||||
r($docTester->checkOtherEditingTest($docIds[4])) && p() && e('0'); // 测试不存在的文档是否有在编辑的人
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
title=测试 docModel->getDocQuery();
|
||||
cid=1
|
||||
|
||||
- 测试只查询所有文档库 @1 AND `lib` IN ('11','21','12','22','13','14','15','25','6','16','26','7','17','27','8','18','28','9','19','29','10','20','30')
|
||||
- 测试只查询所有文档库 @1 AND `lib` IN ('6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22','25','26','27','28','29','30')
|
||||
|
||||
- 测试只查询所有项目 @1 AND `project` in (11,60,61,100)
|
||||
|
||||
@@ -14,11 +14,11 @@ cid=1
|
||||
|
||||
- 测试只查询所有执行 @1 AND `execution` IN ('104','103','106','105','102','101')
|
||||
|
||||
- 测试只查询所有文档库和所有项目 @1 AND 1 AND `lib` IN ('11','21','12','22','13','14','15','25','6','16','26','7','17','27','8','18','28','9','19','29','10','20','30') AND `project` in (11,60,61,100)
|
||||
- 测试只查询所有文档库和所有项目 @1 AND 1 AND `lib` IN ('6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22','25','26','27','28','29','30') AND `project` in (11,60,61,100)
|
||||
|
||||
- 测试只查询所有文档库和所有产品 @1 AND 1 AND `lib` IN ('11','21','12','22','13','14','15','25','6','16','26','7','17','27','8','18','28','9','19','29','10','20','30') AND `product` IN ('1','2','3','4','5')
|
||||
- 测试只查询所有文档库和所有产品 @1 AND 1 AND `lib` IN ('6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22','25','26','27','28','29','30') AND `product` IN ('1','2','3','4','5')
|
||||
|
||||
- 测试只查询所有文档库和所有执行 @1 AND 1 AND `lib` IN ('11','21','12','22','13','14','15','25','6','16','26','7','17','27','8','18','28','9','19','29','10','20','30') AND `execution` IN ('104','103','106','105','102','101')
|
||||
- 测试只查询所有文档库和所有执行 @1 AND 1 AND `lib` IN ('6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22','25','26','27','28','29','30') AND `execution` IN ('104','103','106','105','102','101')
|
||||
|
||||
- 测试只查询所有项目和所有产品 @1 AND 1 AND `project` in (11,60,61,100) AND `product` IN ('1','2','3','4','5')
|
||||
|
||||
@@ -26,15 +26,14 @@ cid=1
|
||||
|
||||
- 测试只查询所有产品和所有执行 @1 AND 1 AND `product` IN ('1','2','3','4','5') AND `execution` IN ('104','103','106','105','102','101')
|
||||
|
||||
- 测试只查询所有文档库和所有项目和所有产品 @1 AND 1 AND 1 AND `lib` IN ('11','21','12','22','13','14','15','25','6','16','26','7','17','27','8','18','28','9','19','29','10','20','30') AND `project` in (11,60,61,100) AND `product` IN ('1','2','3','4','5')
|
||||
- 测试只查询所有文档库和所有项目和所有产品 @1 AND 1 AND 1 AND `lib` IN ('6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22','25','26','27','28','29','30') AND `project` in (11,60,61,100) AND `product` IN ('1','2','3','4','5')
|
||||
|
||||
- 测试只查询所有文档库和所有项目和所有执行 @1 AND 1 AND 1 AND `lib` IN ('11','21','12','22','13','14','15','25','6','16','26','7','17','27','8','18','28','9','19','29','10','20','30') AND `project` in (11,60,61,100) AND `execution` IN ('104','103','106','105','102','101')
|
||||
- 测试只查询所有文档库和所有项目和所有执行 @1 AND 1 AND 1 AND `lib` IN ('6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22','25','26','27','28','29','30') AND `project` in (11,60,61,100) AND `execution` IN ('104','103','106','105','102','101')
|
||||
|
||||
- 测试只查询所有文档库和所有产品和所有执行 @1 AND 1 AND 1 AND `lib` IN ('11','21','12','22','13','14','15','25','6','16','26','7','17','27','8','18','28','9','19','29','10','20','30') AND `product` IN ('1','2','3','4','5') AND `execution` IN ('104','103','106','105','102','101')
|
||||
- 测试只查询所有文档库和所有产品和所有执行 @1 AND 1 AND 1 AND `lib` IN ('6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22','25','26','27','28','29','30') AND `product` IN ('1','2','3','4','5') AND `execution` IN ('104','103','106','105','102','101')
|
||||
|
||||
- 测试只查询所有项目和所有产品和所有执行 @1 AND 1 AND 1 AND `project` in (11,60,61,100) AND `product` IN ('1','2','3','4','5') AND `execution` IN ('104','103','106','105','102','101')
|
||||
|
||||
|
||||
*/
|
||||
|
||||
include dirname(__FILE__, 5) . '/test/lib/init.php';
|
||||
@@ -69,17 +68,17 @@ $queries[] = "{$allProject} AND {$allProduct} AND {$allExecution}";
|
||||
$queries[] = "{$allLib} AND {$allProject} AND {$allProduct} AND {$allExecution}";
|
||||
|
||||
$docTester = new docTest();
|
||||
r($docTester->getDocQueryTest($queries[0])) && p() && e("1 AND `lib` IN ('11','21','12','22','13','14','15','25','6','16','26','7','17','27','8','18','28','9','19','29','10','20','30')"); // 测试只查询所有文档库
|
||||
r($docTester->getDocQueryTest($queries[0])) && p() && e("1 AND `lib` IN ('6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22','25','26','27','28','29','30')"); // 测试只查询所有文档库
|
||||
r($docTester->getDocQueryTest($queries[1])) && p() && e("1 AND `project` in (11,60,61,100)"); // 测试只查询所有项目
|
||||
r($docTester->getDocQueryTest($queries[2])) && p() && e("1 AND `product` IN ('1','2','3','4','5')"); // 测试只查询所有产品
|
||||
r($docTester->getDocQueryTest($queries[3])) && p() && e("1 AND `execution` IN ('104','103','106','105','102','101')"); // 测试只查询所有执行
|
||||
r($docTester->getDocQueryTest($queries[4])) && p() && e("1 AND 1 AND `lib` IN ('11','21','12','22','13','14','15','25','6','16','26','7','17','27','8','18','28','9','19','29','10','20','30') AND `project` in (11,60,61,100)"); // 测试只查询所有文档库和所有项目
|
||||
r($docTester->getDocQueryTest($queries[5])) && p() && e("1 AND 1 AND `lib` IN ('11','21','12','22','13','14','15','25','6','16','26','7','17','27','8','18','28','9','19','29','10','20','30') AND `product` IN ('1','2','3','4','5')"); // 测试只查询所有文档库和所有产品
|
||||
r($docTester->getDocQueryTest($queries[6])) && p() && e("1 AND 1 AND `lib` IN ('11','21','12','22','13','14','15','25','6','16','26','7','17','27','8','18','28','9','19','29','10','20','30') AND `execution` IN ('104','103','106','105','102','101')"); // 测试只查询所有文档库和所有执行
|
||||
r($docTester->getDocQueryTest($queries[4])) && p() && e("1 AND 1 AND `lib` IN ('6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22','25','26','27','28','29','30') AND `project` in (11,60,61,100)"); // 测试只查询所有文档库和所有项目
|
||||
r($docTester->getDocQueryTest($queries[5])) && p() && e("1 AND 1 AND `lib` IN ('6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22','25','26','27','28','29','30') AND `product` IN ('1','2','3','4','5')"); // 测试只查询所有文档库和所有产品
|
||||
r($docTester->getDocQueryTest($queries[6])) && p() && e("1 AND 1 AND `lib` IN ('6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22','25','26','27','28','29','30') AND `execution` IN ('104','103','106','105','102','101')"); // 测试只查询所有文档库和所有执行
|
||||
r($docTester->getDocQueryTest($queries[7])) && p() && e("1 AND 1 AND `project` in (11,60,61,100) AND `product` IN ('1','2','3','4','5')"); // 测试只查询所有项目和所有产品
|
||||
r($docTester->getDocQueryTest($queries[8])) && p() && e("1 AND 1 AND `project` in (11,60,61,100) AND `execution` IN ('104','103','106','105','102','101')"); // 测试只查询所有项目和所有执行
|
||||
r($docTester->getDocQueryTest($queries[9])) && p() && e("1 AND 1 AND `product` IN ('1','2','3','4','5') AND `execution` IN ('104','103','106','105','102','101')"); // 测试只查询所有产品和所有执行
|
||||
r($docTester->getDocQueryTest($queries[10])) && p() && e("1 AND 1 AND 1 AND `lib` IN ('11','21','12','22','13','14','15','25','6','16','26','7','17','27','8','18','28','9','19','29','10','20','30') AND `project` in (11,60,61,100) AND `product` IN ('1','2','3','4','5')"); // 测试只查询所有文档库和所有项目和所有产品
|
||||
r($docTester->getDocQueryTest($queries[11])) && p() && e("1 AND 1 AND 1 AND `lib` IN ('11','21','12','22','13','14','15','25','6','16','26','7','17','27','8','18','28','9','19','29','10','20','30') AND `project` in (11,60,61,100) AND `execution` IN ('104','103','106','105','102','101')"); // 测试只查询所有文档库和所有项目和所有执行
|
||||
r($docTester->getDocQueryTest($queries[12])) && p() && e("1 AND 1 AND 1 AND `lib` IN ('11','21','12','22','13','14','15','25','6','16','26','7','17','27','8','18','28','9','19','29','10','20','30') AND `product` IN ('1','2','3','4','5') AND `execution` IN ('104','103','106','105','102','101')"); // 测试只查询所有文档库和所有产品和所有执行
|
||||
r($docTester->getDocQueryTest($queries[10])) && p() && e("1 AND 1 AND 1 AND `lib` IN ('6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22','25','26','27','28','29','30') AND `project` in (11,60,61,100) AND `product` IN ('1','2','3','4','5')"); // 测试只查询所有文档库和所有项目和所有产品
|
||||
r($docTester->getDocQueryTest($queries[11])) && p() && e("1 AND 1 AND 1 AND `lib` IN ('6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22','25','26','27','28','29','30') AND `project` in (11,60,61,100) AND `execution` IN ('104','103','106','105','102','101')"); // 测试只查询所有文档库和所有项目和所有执行
|
||||
r($docTester->getDocQueryTest($queries[12])) && p() && e("1 AND 1 AND 1 AND `lib` IN ('6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22','25','26','27','28','29','30') AND `product` IN ('1','2','3','4','5') AND `execution` IN ('104','103','106','105','102','101')"); // 测试只查询所有文档库和所有产品和所有执行
|
||||
r($docTester->getDocQueryTest($queries[13])) && p() && e("1 AND 1 AND 1 AND `project` in (11,60,61,100) AND `product` IN ('1','2','3','4','5') AND `execution` IN ('104','103','106','105','102','101')"); // 测试只查询所有项目和所有产品和所有执行
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
title=测试 docModel->getDynamic();
|
||||
cid=1
|
||||
|
||||
- 获取每页5条,第1页动态数据 @31;30;29;28;27;
|
||||
- 获取每页5条,第2页动态数据 @26;25;24;23;22;
|
||||
- 获取每页10条,第1页动态数据 @31;30;29;28;27;26;25;24;23;22;
|
||||
- 获取每页10条,第2页动态数据 @21;20;19;50;18;49;17;48;16;47;
|
||||
- 获取每页20条,第1页动态数据 @31;30;29;28;27;26;25;24;23;22;21;20;19;50;18;49;17;48;16;47;
|
||||
- 获取每页20条,第2页动态数据 @15;46;14;45;13;44;12;43;11;42;10;41;9;40;8;39;7;38;6;37;
|
||||
- 获取每页5条,第1页动态数据 @5
|
||||
- 获取每页5条,第2页动态数据 @5
|
||||
- 获取每页10条,第1页动态数据 @10
|
||||
- 获取每页10条,第2页动态数据 @10
|
||||
- 获取每页20条,第1页动态数据 @20
|
||||
- 获取每页20条,第2页动态数据 @20
|
||||
|
||||
*/
|
||||
|
||||
@@ -27,9 +27,9 @@ $recPerPages = array(5, 10, 20);
|
||||
$pagerIds = array(1, 2);
|
||||
|
||||
$docTester = new docTest();
|
||||
r($docTester->getDynamicTest($recPerPages[0], $pagerIds[0])) && p() && e('27;28;29;30;31'); // 获取每页5条,第1页动态数据
|
||||
r($docTester->getDynamicTest($recPerPages[0], $pagerIds[1])) && p() && e('22;23;24;25;26'); // 获取每页5条,第2页动态数据
|
||||
r($docTester->getDynamicTest($recPerPages[1], $pagerIds[0])) && p() && e('22;23;24;25;26;27;28;29;30;31'); // 获取每页10条,第1页动态数据
|
||||
r($docTester->getDynamicTest($recPerPages[1], $pagerIds[1])) && p() && e('16;17;18;19;20;21;47;48;49;50'); // 获取每页10条,第2页动态数据
|
||||
r($docTester->getDynamicTest($recPerPages[2], $pagerIds[0])) && p() && e('16;17;18;19;20;21;22;23;24;25;26;27;28;29;30;31;47;48;49;50'); // 获取每页20条,第1页动态数据
|
||||
r($docTester->getDynamicTest($recPerPages[2], $pagerIds[1])) && p() && e('6;7;8;9;10;11;12;13;14;15;37;38;39;40;41;42;43;44;45;46'); // 获取每页20条,第2页动态数据
|
||||
r($docTester->getDynamicTest($recPerPages[0], $pagerIds[0])) && p() && e('5'); // 获取每页5条,第1页动态数据
|
||||
r($docTester->getDynamicTest($recPerPages[0], $pagerIds[1])) && p() && e('5'); // 获取每页5条,第2页动态数据
|
||||
r($docTester->getDynamicTest($recPerPages[1], $pagerIds[0])) && p() && e('10'); // 获取每页10条,第1页动态数据
|
||||
r($docTester->getDynamicTest($recPerPages[1], $pagerIds[1])) && p() && e('10'); // 获取每页10条,第2页动态数据
|
||||
r($docTester->getDynamicTest($recPerPages[2], $pagerIds[0])) && p() && e('20'); // 获取每页20条,第1页动态数据
|
||||
r($docTester->getDynamicTest($recPerPages[2], $pagerIds[1])) && p() && e('20'); // 获取每页20条,第2页动态数据
|
||||
|
||||
@@ -39,12 +39,15 @@ su('admin');
|
||||
|
||||
title=测试 docModel->getModuleTree();
|
||||
cid=1
|
||||
pid=1
|
||||
|
||||
检查产品库高亮 >> 1
|
||||
检查产品库子模块树的数量 >> 2
|
||||
检查接口库高亮 >> 1
|
||||
检查接口库子模块树的数量 >> 2
|
||||
- 检查产品库高亮第0条的active属性 @1
|
||||
- 检查产品库是否有同时高亮问题第1条的active属性 @0
|
||||
- 检查产品库是否有同时高亮问题第2条的active属性 @0
|
||||
- 检查产品库子模块树的数量 @2
|
||||
- 检查接口库高亮第0条的active属性 @1
|
||||
- 检查接口库是否有同时高亮问题第1条的active属性 @0
|
||||
- 检查接口库是否有同时高亮问题第2条的active属性 @0
|
||||
- 检查接口库子模块树的数量 @2
|
||||
|
||||
*/
|
||||
|
||||
@@ -58,6 +61,10 @@ $docChildrenModule = $tester->loadModel('doc')->getModuleTree($rootIDList[0], $m
|
||||
$apiChildrenModule = $tester->loadModel('doc')->getModuleTree($rootIDList[1], $moduleIDList[2], $typeList[1]);
|
||||
|
||||
r($docChildrenModule) && p('0:active') && e('1'); // 检查产品库高亮
|
||||
r($docChildrenModule) && p('1:active') && e('0'); // 检查产品库是否有同时高亮问题
|
||||
r($docChildrenModule) && p('2:active') && e('0'); // 检查产品库是否有同时高亮问题
|
||||
r(count($docChildrenModule[0]->children)) && p() && e('2'); // 检查产品库子模块树的数量
|
||||
r($apiChildrenModule) && p('0:active') && e('1'); // 检查接口库高亮
|
||||
r($apiChildrenModule) && p('1:active') && e('0'); // 检查接口库是否有同时高亮问题
|
||||
r($apiChildrenModule) && p('2:active') && e('0'); // 检查接口库是否有同时高亮问题
|
||||
r(count($apiChildrenModule[0]->children)) && p() && e('2'); // 检查接口库子模块树的数量
|
||||
|
||||
@@ -7,8 +7,10 @@ cid=1
|
||||
|
||||
- 获取系统中已排序的产品第0条的1属性 @产品1
|
||||
- 获取系统中包括ID=1已排序的产品第0条的1属性 @产品1
|
||||
- 获取系统中包括不存在ID已排序的产品第0条的1属性 @产品1
|
||||
- 获取系统中已排序的产品数量 @3
|
||||
- 获取系统中包括ID=1已排序的产品数量 @3
|
||||
- 获取系统中包括不存在ID已排序的产品数量 @3
|
||||
|
||||
*/
|
||||
|
||||
@@ -19,11 +21,13 @@ zenData('product')->loadYaml('product')->gen(20);
|
||||
zenData('user')->gen(5);
|
||||
su('admin');
|
||||
|
||||
$appends = array(0, 1);
|
||||
$appends = array(0, 1, 100);
|
||||
|
||||
$docTester = new docTest();
|
||||
r($docTester->getOrderedProductsTest($appends[0])) && p('0:1') && e('产品1'); // 获取系统中已排序的产品
|
||||
r($docTester->getOrderedProductsTest($appends[1])) && p('0:1') && e('产品1'); // 获取系统中包括ID=1已排序的产品
|
||||
r($docTester->getOrderedProductsTest($appends[2])) && p('0:1') && e('产品1'); // 获取系统中包括不存在ID已排序的产品
|
||||
|
||||
r(count($docTester->getOrderedProductsTest($appends[0]))) && p() && e('3'); // 获取系统中已排序的产品数量
|
||||
r(count($docTester->getOrderedProductsTest($appends[1]))) && p() && e('3'); // 获取系统中包括ID=1已排序的产品数量
|
||||
r(count($docTester->getOrderedProductsTest($appends[2]))) && p() && e('3'); // 获取系统中包括不存在ID已排序的产品数量
|
||||
|
||||
@@ -6,6 +6,7 @@ title=测试 docModel->getOrderedProjects();
|
||||
cid=1
|
||||
|
||||
- 获取系统中已排序的项目第1条的11属性 @项目11
|
||||
- 获取系统中包括ID=11已排序的项目第2条的16属性 @项目16
|
||||
- 获取系统中包括ID=11已排序的项目第1条的11属性 @项目11
|
||||
- 获取系统中已排序的项目数量 @3
|
||||
- 获取系统中包括ID=11已排序的项目数量 @3
|
||||
@@ -21,9 +22,13 @@ su('admin');
|
||||
|
||||
$appends = array(0, 11);
|
||||
|
||||
$docTester = new docTest();
|
||||
r($docTester->getOrderedProjectsTest($appends[0])) && p('1:11') && e('项目11'); // 获取系统中已排序的项目
|
||||
r($docTester->getOrderedProjectsTest($appends[1])) && p('1:11') && e('项目11'); // 获取系统中包括ID=11已排序的项目
|
||||
$docTester = new docTest();
|
||||
$allProjects = $docTester->getOrderedProjectsTest($appends[0]);
|
||||
$appendProjects = $docTester->getOrderedProjectsTest($appends[1]);
|
||||
|
||||
r(count($docTester->getOrderedProjectsTest($appends[0]))) && p() && e('3'); // 获取系统中已排序的项目数量
|
||||
r(count($docTester->getOrderedProjectsTest($appends[1]))) && p() && e('3'); // 获取系统中包括ID=11已排序的项目数量
|
||||
r($allProjects) && p('1:11') && e('项目11'); // 获取系统中已排序的项目
|
||||
r($appendProjects) && p('2:16') && e('项目16'); // 获取系统中包括ID=11已排序的项目
|
||||
r($appendProjects) && p('1:11') && e('项目11'); // 获取系统中包括ID=11已排序的项目
|
||||
|
||||
r(count($allProjects)) && p() && e('3'); // 获取系统中已排序的项目数量
|
||||
r(count($appendProjects)) && p() && e('3'); // 获取系统中包括ID=11已排序的项目数量
|
||||
|
||||
@@ -8,6 +8,7 @@ cid=1
|
||||
- 测试空数据 @0
|
||||
- 测试移除当前用户的正在编辑信息属性editing @~~
|
||||
- 测试当前用户不在正在编辑列表中 @0
|
||||
- 测试当前用户创建的文档属性editing @~~
|
||||
- 测试不存在的文档信息 @0
|
||||
|
||||
*/
|
||||
@@ -18,14 +19,16 @@ include dirname(__FILE__, 2) . '/lib/doc.unittest.class.php';
|
||||
$timestamp = time();
|
||||
$docTable = zenData('doc')->loadYaml('doc');
|
||||
$docTable->editingDate->range("`{\"admin\": $timestamp}`, `{\"user1\": $timestamp}`");
|
||||
$docTable->gen(2);
|
||||
$docTable->addedBy->range('user1{2},admin');
|
||||
$docTable->gen(3);
|
||||
|
||||
zenData('user')->gen(5);
|
||||
|
||||
$docIds = array(0, 1, 2, 3);
|
||||
$docIds = array(0, 1, 2, 3, 100);
|
||||
|
||||
$docTester = new docTest();
|
||||
r($docTester->removeEditingTest($docIds[0])) && p() && e('0'); // 测试空数据
|
||||
r($docTester->removeEditingTest($docIds[1])) && p('editing') && e('~~'); // 测试移除当前用户的正在编辑信息
|
||||
r($docTester->removeEditingTest($docIds[2])) && p() && e('0'); // 测试当前用户不在正在编辑列表中
|
||||
r($docTester->removeEditingTest($docIds[3])) && p() && e('0'); // 测试不存在的文档信息
|
||||
r($docTester->removeEditingTest($docIds[3])) && p('editing') && e('~~'); // 测试当前用户创建的文档
|
||||
r($docTester->removeEditingTest($docIds[4])) && p() && e('0'); // 测试不存在的文档信息
|
||||
|
||||
@@ -9,6 +9,7 @@ cid=1
|
||||
- 测试有附件的情况 @本页共 <strong>10</strong> 个附件,共计 <strong>159.2K</strong>,其中<strong>txt 1个、doc 1个、docx 1个、dot 1个、wps 1个、wri 1个、pdf 1个、ppt 1个、pptx 1个、xls 1个</strong>。
|
||||
- 测试附件ID有不存在附件的情况 @本页共 <strong>10</strong> 个附件,共计 <strong>159.2K</strong>,其中<strong>txt 1个、doc 1个、docx 1个、dot 1个、wps 1个、wri 1个、pdf 1个、ppt 1个、pptx 1个、xls 1个</strong>。
|
||||
- 测试附件ID不存在附件的情况 @本页共 <strong>0</strong> 个附件,共计 <strong>0B</strong>,其中<strong></strong>。
|
||||
- 测试有附件ID不存在的情况 @本页共 <strong>10</strong> 个附件,共计 <strong>159.2K</strong>,其中<strong>txt 1个、doc 1个、docx 1个、dot 1个、wps 1个、wri 1个、pdf 1个、ppt 1个、pptx 1个、xls 1个</strong>。
|
||||
|
||||
*/
|
||||
include dirname(__FILE__, 5) . '/test/lib/init.php';
|
||||
@@ -23,9 +24,11 @@ $fileIds[0] = array();
|
||||
$fileIds[1] = range(1, 10);
|
||||
$fileIds[2] = range(1, 20);
|
||||
$fileIds[3] = range(11, 20);
|
||||
$fileIds[4] = range(0, 20);
|
||||
|
||||
$docTester = new docTest();
|
||||
r($docTester->summaryTest($fileIds[0])) && p() && e('本页共 <strong>0</strong> 个附件,共计 <strong>0B</strong>,其中<strong></strong>。'); // 测试没有附件的情况
|
||||
r($docTester->summaryTest($fileIds[1])) && p() && e('本页共 <strong>10</strong> 个附件,共计 <strong>159.2K</strong>,其中<strong>txt 1个、doc 1个、docx 1个、dot 1个、wps 1个、wri 1个、pdf 1个、ppt 1个、pptx 1个、xls 1个</strong>。'); // 测试有附件的情况
|
||||
r($docTester->summaryTest($fileIds[2])) && p() && e('本页共 <strong>10</strong> 个附件,共计 <strong>159.2K</strong>,其中<strong>txt 1个、doc 1个、docx 1个、dot 1个、wps 1个、wri 1个、pdf 1个、ppt 1个、pptx 1个、xls 1个</strong>。'); // 测试附件ID有不存在附件的情况
|
||||
r($docTester->summaryTest($fileIds[3])) && p() && e('本页共 <strong>0</strong> 个附件,共计 <strong>0B</strong>,其中<strong></strong>。'); // 测试附件ID不存在附件的情况
|
||||
r($docTester->summaryTest($fileIds[4])) && p() && e('本页共 <strong>10</strong> 个附件,共计 <strong>159.2K</strong>,其中<strong>txt 1个、doc 1个、docx 1个、dot 1个、wps 1个、wri 1个、pdf 1个、ppt 1个、pptx 1个、xls 1个</strong>。'); // 测试有附件ID不存在的情况
|
||||
|
||||
@@ -6,7 +6,9 @@ title=测试 docModel->getOrderedExecutions();
|
||||
cid=1
|
||||
|
||||
- 获取系统中已排序的执行第0条的102属性 @敏捷项目1 / 迭代6
|
||||
- 获取系统中已排序的执行第1条的101属性 @敏捷项目1 / 迭代5
|
||||
- 获取系统中包括ID=101已排序的执行第0条的102属性 @敏捷项目1 / 迭代6
|
||||
- 获取系统中包括ID=101已排序的执行第1条的101属性 @敏捷项目1 / 迭代5
|
||||
- 获取系统中已排序的执行数量 @3
|
||||
- 获取系统中包括ID=101已排序的执行数量 @3
|
||||
|
||||
@@ -22,8 +24,12 @@ su('admin');
|
||||
$appends = array(0, 101);
|
||||
|
||||
$docTester = new docTest();
|
||||
r($docTester->getOrderedExecutionsTest($appends[0])) && p('0:102') && e('敏捷项目1 / 迭代6'); // 获取系统中已排序的执行
|
||||
r($docTester->getOrderedExecutionsTest($appends[1])) && p('0:102') && e('敏捷项目1 / 迭代6'); // 获取系统中包括ID=101已排序的执行
|
||||
$orderedExecutions = $docTester->getOrderedExecutionsTest($appends[0]);
|
||||
$appendOrderedExecutions = $docTester->getOrderedExecutionsTest($appends[1]);
|
||||
r($orderedExecutions) && p('0:102') && e('敏捷项目1 / 迭代6'); // 获取系统中已排序的执行
|
||||
r($orderedExecutions) && p('1:101') && e('敏捷项目1 / 迭代5'); // 获取系统中已排序的执行
|
||||
r($appendOrderedExecutions) && p('0:102') && e('敏捷项目1 / 迭代6'); // 获取系统中包括ID=101已排序的执行
|
||||
r($appendOrderedExecutions) && p('1:101') && e('敏捷项目1 / 迭代5'); // 获取系统中包括ID=101已排序的执行
|
||||
|
||||
r(count($docTester->getOrderedExecutionsTest($appends[0]))) && p() && e('3'); // 获取系统中已排序的执行数量
|
||||
r(count($docTester->getOrderedExecutionsTest($appends[1]))) && p() && e('3'); // 获取系统中包括ID=101已排序的执行数量
|
||||
r(count($orderedExecutions)) && p() && e('3'); // 获取系统中已排序的执行数量
|
||||
r(count($appendOrderedExecutions)) && p() && e('3'); // 获取系统中包括ID=101已排序的执行数量
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
title=创建空间
|
||||
timeout=0
|
||||
cid=1
|
||||
|
||||
- 执行tester模块的createSpace方法,参数是'myspace', ''▫
|
||||
- 最终测试状态 @SUCCESS
|
||||
- 测试结果 @空间名称为空时提示正确
|
||||
- 执行tester模块的createSpace方法,参数是'myspace', '空间1'▫
|
||||
- 最终测试状态 @SUCCESS
|
||||
- 测试结果 @空间创建成功
|
||||
- 执行tester模块的createSpace方法,参数是'teamspace', ''▫
|
||||
- 最终测试状态 @SUCCESS
|
||||
- 测试结果 @空间名称为空时提示正确
|
||||
- 执行tester模块的createSpace方法,参数是'teamspace', '空间2'▫
|
||||
- 最终测试状态 @SUCCESS
|
||||
- 测试结果 @空间创建成功
|
||||
|
||||
*/
|
||||
|
||||
chdir(__DIR__);
|
||||
include '../lib/createspace.ui.class.php';
|
||||
|
||||
$product = zenData('product');
|
||||
$product->id->range('1-100');
|
||||
$product->name->range('产品1, 产品2');
|
||||
$product->type->range('normal');
|
||||
$product->PO->range('admin, []');
|
||||
$product->gen(2);
|
||||
|
||||
$project = zenData('project');
|
||||
$project->id->range('1-10');
|
||||
$project->project->range('0');
|
||||
$project->model->range('scrum');
|
||||
$project->type->range('project');
|
||||
$project->auth->range('extend');
|
||||
$project->storyType->range('story');
|
||||
$project->parent->range('0');
|
||||
$project->path->range('`,1,`, `,2,`, `,3,`');
|
||||
$project->grade->range('1');
|
||||
$project->name->range('项目1, 项目2, 项目3');
|
||||
$project->begin->range('(-6M)-(-5M):1D')->type('timestamp')->format('YY/MM/DD');
|
||||
$project->end->range('(+5M)-(+6M):1D')->type('timestamp')->format('YY/MM/DD');
|
||||
$project->openedBy->range('admin, user1, user2');
|
||||
$project->acl->range('open');
|
||||
$project->status->range('wait');
|
||||
$project->gen(3);
|
||||
|
||||
$projectProduct = zenData('projectproduct');
|
||||
$projectProduct->project->range('1-3');
|
||||
$projectProduct->product->range('1');
|
||||
$projectProduct->branch->range('0');
|
||||
$projectProduct->plan->range('0');
|
||||
$projectProduct->gen(3);
|
||||
|
||||
$tester = new createSpaceTester();
|
||||
$tester->login();
|
||||
|
||||
r($tester->createSpace('myspace', '')) && p('status,message') && e('SUCCESS,空间名称为空时提示正确');
|
||||
r($tester->createSpace('myspace', '空间1')) && p('status,message') && e('SUCCESS,空间创建成功');
|
||||
r($tester->createSpace('teamspace', '')) && p('status,message') && e('SUCCESS,空间名称为空时提示正确');
|
||||
r($tester->createSpace('teamspace', '空间2')) && p('status,message') && e('SUCCESS,空间创建成功');
|
||||
$tester->closeBrowser();
|
||||
@@ -27,3 +27,5 @@ fields:
|
||||
range: "0{15},1,0,1{13}"
|
||||
- field: addedBy
|
||||
range: "admin"
|
||||
- field: order
|
||||
range: 1-100
|
||||
|
||||
@@ -197,8 +197,6 @@ class execution extends control
|
||||
/* Build the search form. */
|
||||
$modules = $this->loadModel('tree')->getTaskOptionMenu($executionID, 0, $showModule);
|
||||
$actionURL = $this->createLink('execution', 'task', "executionID=$executionID&status=bySearch¶m=myQueryID&orderBy=$orderBy&recTotal=0&recPerPage=100&pageID=1&from=$from&blockID=$blockID");
|
||||
$this->config->execution->search['onMenuBar'] = 'yes';
|
||||
if(!$execution->multiple) unset($this->config->execution->search['fields']['execution']);
|
||||
$this->execution->buildTaskSearchForm($executionID, $this->executions, $queryID, $actionURL);
|
||||
|
||||
$this->view->title = $execution->name . $this->lang->hyphen . $this->lang->execution->task;
|
||||
|
||||
+47
-14
@@ -1204,14 +1204,16 @@ class executionModel extends model
|
||||
*
|
||||
* @param array $executionIdList
|
||||
* @param string $mode all
|
||||
* @param string $orderBy
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getByIdList(array $executionIdList = array(), string $mode = ''): array
|
||||
public function getByIdList(array $executionIdList = array(), string $mode = '', string $orderBy = 'id_asc'): array
|
||||
{
|
||||
return $this->dao->select('*,whitelist')->from(TABLE_EXECUTION)
|
||||
->where('id')->in($executionIdList)
|
||||
->beginIF($mode != 'all')->andWhere('deleted')->eq(0)->fi()
|
||||
->orderBy($orderBy)
|
||||
->fetchAll('id');
|
||||
}
|
||||
|
||||
@@ -2642,6 +2644,8 @@ class executionModel extends model
|
||||
$bug = $task->bug;
|
||||
unset($task->bug);
|
||||
|
||||
if(isset($task->estimate)) $task->estimate = round((float)$task->estimate, 2);
|
||||
if(isset($task->left)) $task->left = round((float)$task->left, 2);
|
||||
if(!$bug->confirmed) $this->dao->update(TABLE_BUG)->set('confirmed')->eq(1)->where('id')->eq($bug->id)->exec();
|
||||
$this->dao->insert(TABLE_TASK)->data($task)->exec();
|
||||
if(dao::isError()) return false;
|
||||
@@ -4172,12 +4176,24 @@ class executionModel extends model
|
||||
* @param array $executions
|
||||
* @param int $queryID
|
||||
* @param string $actionURL
|
||||
* @param bool $cacheSearchFunc 是否缓存构造搜索参数的方法。默认缓存可以提高性能,构造搜索表单时再加载真实值。
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function buildTaskSearchForm(int $executionID, array $executions, int $queryID, string $actionURL)
|
||||
public function buildTaskSearchForm(int $executionID, array $executions, int $queryID, string $actionURL, string $module = 'task', bool $cacheSearchFunc = true)
|
||||
{
|
||||
$showAll = empty($executionID) && empty($executions) ? true : false;
|
||||
$searchConfig = $this->config->execution->search;
|
||||
if($cacheSearchFunc)
|
||||
{
|
||||
$this->cacheSearchFunc($module, __METHOD__, func_get_args());
|
||||
return $searchConfig;
|
||||
}
|
||||
|
||||
$searchConfig['module'] = $module;
|
||||
$searchConfig['actionURL'] = $actionURL;
|
||||
$searchConfig['queryID'] = $queryID;
|
||||
|
||||
$showAll = empty($executionID) && empty($executions);
|
||||
if($showAll)
|
||||
{
|
||||
$executions = $this->getPairs(0, 'all', "nocode,noprefix,multiple");
|
||||
@@ -4185,27 +4201,44 @@ class executionModel extends model
|
||||
}
|
||||
$execution = $this->getByID($executionID);
|
||||
|
||||
$this->config->execution->search['actionURL'] = $actionURL;
|
||||
$this->config->execution->search['queryID'] = $queryID;
|
||||
$this->config->execution->search['params']['story']['values'] = $this->loadModel('story')->getExecutionStoryPairs($executionID, 0, 'all', '', 'full', 'unclosed', 'story', false);
|
||||
$searchConfig['params']['story']['values'] = $this->loadModel('story')->getExecutionStoryPairs($executionID, 0, 'all', '', 'full', 'unclosed', 'story', false);
|
||||
|
||||
if($module == 'task')
|
||||
{
|
||||
$searchConfig['onMenuBar'] = 'yes';
|
||||
if(!$execution->multiple) unset($searchConfig['fields']['execution']);
|
||||
}
|
||||
elseif($module == 'projectTask')
|
||||
{
|
||||
unset($searchConfig['fields']['project']);
|
||||
unset($searchConfig['fields']['module']);
|
||||
}
|
||||
|
||||
if(isset($execution->type) && $execution->type == 'project')
|
||||
{
|
||||
unset($this->config->execution->search['fields']['project']);
|
||||
$this->config->execution->search['params']['execution']['values'] = array('' => '') + $executions;
|
||||
unset($searchConfig['fields']['project']);
|
||||
if(isset($searchConfig['fields']['execution'])) $searchConfig['params']['execution']['values'] = array('' => '') + $executions;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->config->execution->search['params']['execution']['values'] = $showAll ? $executions : array(''=>'', $executionID => zget($executions, $executionID, ''), 'all' => $this->lang->execution->allExecutions);
|
||||
if(isset($searchConfig['fields']['execution'])) $searchConfig['params']['execution']['values'] = $showAll ? $executions : array(''=>'', $executionID => zget($executions, $executionID, ''), 'all' => $this->lang->execution->allExecutions);
|
||||
}
|
||||
|
||||
$projects = $this->loadModel('project')->getPairsByProgram();
|
||||
$this->config->execution->search['params']['project']['values'] = $projects + array('all' => $this->lang->project->allProjects);
|
||||
if(isset($searchConfig['fields']['project']))
|
||||
{
|
||||
$projects = $this->loadModel('project')->getPairsByProgram();
|
||||
$searchConfig['params']['project']['values'] = $projects + array('all' => $this->lang->project->allProjects);
|
||||
}
|
||||
|
||||
$showAllModule = isset($this->config->execution->task->allModule) ? $this->config->execution->task->allModule : '';
|
||||
$this->config->execution->search['params']['module']['values'] = $this->loadModel('tree')->getTaskOptionMenu($executionID, 0, $showAllModule ? 'allModule' : '');
|
||||
if(isset($searchConfig['fields']['module']))
|
||||
{
|
||||
$showAllModule = $this->config->execution->task->allModule ?? '';
|
||||
$searchConfig['params']['module']['values'] = $this->loadModel('tree')->getTaskOptionMenu($executionID, 0, $showAllModule ? 'allModule' : '');
|
||||
}
|
||||
|
||||
$this->loadModel('search')->setSearchParams($this->config->execution->search);
|
||||
$this->loadModel('search')->setSearchParams($searchConfig);
|
||||
|
||||
return $searchConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -837,6 +837,7 @@ class executionTao extends executionModel
|
||||
$this->dao->insert(TABLE_TEAM)->data($member)->exec();
|
||||
$teamMembers[$account] = $member;
|
||||
}
|
||||
if($execution->acl != 'open') $this->updateUserView($executionID, 'sprint');
|
||||
$this->addProjectMembers($execution->project, $teamMembers);
|
||||
}
|
||||
|
||||
|
||||
@@ -2481,15 +2481,17 @@ class executionTest
|
||||
* Test build task search form.
|
||||
*
|
||||
* @param int $executionID
|
||||
* @param array $executions
|
||||
* @param int $queryID
|
||||
* @param string $actionURL
|
||||
* @param string $module
|
||||
* @param bool $cacheSearchFunc
|
||||
* @access public
|
||||
* @return int
|
||||
* @return array
|
||||
*/
|
||||
public function buildTaskSearchFormTest($executionID, $queryID)
|
||||
public function buildTaskSearchFormTest($executionID, $executions, $queryID, $actionURL, $module, $cacheSearchFunc): array
|
||||
{
|
||||
$this->executionModel->buildTaskSearchForm($executionID, array($executionID => 'yes'), $queryID, 'searchTask');
|
||||
|
||||
return $_SESSION['tasksearchParams']['queryID'];
|
||||
return $this->executionModel->buildTaskSearchForm($executionID, $executions, $queryID, $actionURL, $module, $cacheSearchFunc);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
class executionZenTest
|
||||
{
|
||||
public $executionZenTest;
|
||||
public $tester;
|
||||
function __construct()
|
||||
{
|
||||
global $tester;
|
||||
$this->tester = $tester;
|
||||
$this->objectModel = $tester->loadModel('execution');
|
||||
$tester->app->setModuleName('execution');
|
||||
|
||||
$this->executionZenTest = initReference('execution');
|
||||
}
|
||||
|
||||
/**
|
||||
* 将导入的Bug转为任务。
|
||||
*
|
||||
* @param string $mode normal|emptyData|errorEstimate|errorDeadline
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function buildTasksForImportBugTest(string $mode = 'normal'): array
|
||||
{
|
||||
$method = $this->executionZenTest->getMethod('buildTasksForImportBug');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$postData = array();
|
||||
$execution = $this->objectModel->fetchByID(3);
|
||||
if($mode != 'emptyData')
|
||||
{
|
||||
$tasks = $this->objectModel->dao->select('*')->from(TABLE_TASK)->fetchAll('id');
|
||||
foreach($tasks as $taskID => $task)
|
||||
{
|
||||
if($mode == 'errorEstimate') $task->estimate = -1;
|
||||
if($mode == 'errorDeadline')
|
||||
{
|
||||
$task->deadline = '2025-08-25';
|
||||
$task->estStarted = '2025-08-26';
|
||||
}
|
||||
$postData[$taskID] = $task;
|
||||
}
|
||||
}
|
||||
|
||||
$result = $method->invokeArgs($this->executionZenTest->newInstance(), [$execution, $postData]);
|
||||
if(dao::isError()) return dao::getError();
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -4,17 +4,18 @@ include dirname(__FILE__, 5) . '/test/lib/init.php';
|
||||
include dirname(__FILE__, 2) . '/lib/execution.unittest.class.php';
|
||||
|
||||
$execution = zenData('project');
|
||||
$execution->id->range('1-5');
|
||||
$execution->name->range('项目1,项目2,迭代1,迭代2,迭代3');
|
||||
$execution->type->range('project{2},sprint,stage,kanban');
|
||||
$execution->id->range('1-8');
|
||||
$execution->name->range('项目1,项目2,迭代1,迭代2,迭代3,阶段1,阶段2,阶段3');
|
||||
$execution->type->range('project{2},sprint,stage,kanban,stage{3}');
|
||||
$execution->model->range('scrum,waterfall,``{6}');
|
||||
$execution->status->range('doing');
|
||||
$execution->parent->range('0,0,1,1,2');
|
||||
$execution->project->range('0,0,1,1,2');
|
||||
$execution->grade->range('2{2},1{3}');
|
||||
$execution->path->range('1,2,`1,3`,`1,4`,`2,5`')->prefix(',')->postfix(',');
|
||||
$execution->parent->range('0,0,1{3},2{3}');
|
||||
$execution->project->range('0,0,1{3},2{3}');
|
||||
$execution->grade->range('2{2},1{6}');
|
||||
$execution->path->range('1,2,`1,3`,`1,4`,`1,5`,`2,6`,`2,7`,`2,8`')->prefix(',')->postfix(',');
|
||||
$execution->begin->range('20230102 000000:0')->type('timestamp')->format('YY/MM/DD');
|
||||
$execution->end->range('20230212 000000:0')->type('timestamp')->format('YY/MM/DD');
|
||||
$execution->gen(5);
|
||||
$execution->gen(8);
|
||||
|
||||
$task = zenData('task');
|
||||
$task->id->range('1-10');
|
||||
@@ -27,14 +28,9 @@ $task->left->range('1-10');
|
||||
$task->consumed->range('1-10');
|
||||
$task->gen(10);
|
||||
|
||||
$query = zenData('userquery');
|
||||
$query->id->range('1');
|
||||
$query->account->range('admin');
|
||||
$query->module->range('task');
|
||||
$query->title->range('搜索条件1');
|
||||
$query->form->range('`a:59:{s:9:"fieldname";s:0:"";s:11:"fieldstatus";s:0:"";s:9:"fielddesc";s:0:"";s:15:"fieldassignedTo";s:0:"";s:8:"fieldpri";s:1:"0";s:14:"fieldexecution";s:0:"";s:11:"fieldmodule";s:4:"ZERO";s:13:"fieldestimate";s:0:"";s:9:"fieldleft";s:0:"";s:13:"fieldconsumed";s:0:"";s:9:"fieldtype";s:0:"";s:12:"fieldfromBug";s:0:"";s:17:"fieldclosedReason";s:0:"";s:13:"fieldopenedBy";s:0:"";s:15:"fieldfinishedBy";s:0:"";s:13:"fieldclosedBy";s:0:"";s:15:"fieldcanceledBy";s:0:"";s:17:"fieldlastEditedBy";s:0:"";s:11:"fieldmailto";s:0:"";s:15:"fieldopenedDate";s:0:"";s:13:"fielddeadline";s:0:"";s:15:"fieldestStarted";s:0:"";s:16:"fieldrealStarted";s:0:"";s:17:"fieldassignedDate";s:0:"";s:17:"fieldfinishedDate";s:0:"";s:15:"fieldclosedDate";s:0:"";s:17:"fieldcanceledDate";s:0:"";s:19:"fieldlastEditedDate";s:0:"";s:18:"fieldactivatedDate";s:0:"";s:7:"fieldid";s:0:"";s:6:"andOr1";s:3:"AND";s:6:"field1";s:4:"name";s:9:"operator1";s:7:"include";s:6:"value1";s:6:"任务";s:6:"andOr2";s:3:"and";s:6:"field2";s:2:"id";s:9:"operator2";s:1:"=";s:6:"value2";s:0:"";s:6:"andOr3";s:3:"and";s:6:"field3";s:6:"status";s:9:"operator3";s:1:"=";s:6:"value3";s:0:"";s:10:"groupAndOr";s:3:"and";s:6:"andOr4";s:3:"AND";s:6:"field4";s:4:"desc";s:9:"operator4";s:7:"include";s:6:"value4";s:0:"";s:6:"andOr5";s:3:"and";s:6:"field5";s:10:"assignedTo";s:9:"operator5";s:1:"=";s:6:"value5";s:0:"";s:6:"andOr6";s:3:"and";s:6:"field6";s:3:"pri";s:9:"operator6";s:1:"=";s:6:"value6";s:1:"0";s:6:"module";s:4:"task";s:9:"actionURL";s:41:"/execution-task-3-bySearch-myQueryID.html";s:10:"groupItems";s:1:"3";s:8:"formType";s:4:"lite";}`');
|
||||
$query->sql->range("`(( 1 AND `name` LIKE '%任务%' ) AND ( 1 )) AND deleted = '0'`");
|
||||
$query->gen(1);
|
||||
$module = zenData('module');
|
||||
$module->name->range('1,2,3,4,5')->prefix('模块');
|
||||
$module->gen(5);
|
||||
|
||||
su('admin');
|
||||
|
||||
@@ -44,20 +40,143 @@ title=测试executionModel->buildTaskSearchForm();
|
||||
timeout=0
|
||||
cid=1
|
||||
|
||||
- 正确的执行,正确的queryID @1
|
||||
- 错误的执行,正确的queryID @1
|
||||
- 正确的执行,错误的queryID @0
|
||||
- 错误的执行,错误的queryID @0
|
||||
- 正确的执行,正确的queryID @2
|
||||
- module 为 projectTask,缓存查询参数,查询参数中 queryID 为空。 @0
|
||||
- module 为 projectTask,缓存查询参数,查询参数中 actionURL 为空。 @0
|
||||
- module 为 projectTask,缓存查询参数,查询参数中有 project 字段。 @1
|
||||
- module 为 projectTask,缓存查询参数,查询参数中有 module 字段。 @1
|
||||
- module 为 projectTask,缓存查询参数,打印 module 的值。属性module @task
|
||||
- module 为 projectTask,缓存查询参数,打印所属执行列表。
|
||||
- 属性3 @0
|
||||
- 属性4 @0
|
||||
- 属性5 @0
|
||||
- module 为 projectTask,不缓存查询参数,查询参数中 queryID 有值。 @1
|
||||
- module 为 projectTask,不缓存查询参数,查询参数中 actionURL 有值。 @1
|
||||
- module 为 projectTask,不缓存查询参数,查询参数中没有 project 字段。 @0
|
||||
- module 为 projectTask,不缓存查询参数,查询参数中没有 module 字段。 @0
|
||||
- module 为 projectTask,不缓存查询参数,打印 module 的值。属性module @projectTask
|
||||
- module 为 projectTask,不缓存查询参数,打印 queryID 的值。属性queryID @1
|
||||
- module 为 projectTask,不缓存查询参数,打印 actionURL 的值。属性actionURL @/project-execution-bySearch-1-order_desc-0-0-100-1-myQueryID.html
|
||||
- module 为 projectTask,不缓存查询参数,打印所属执行列表。
|
||||
- 属性3 @迭代1
|
||||
- 属性4 @迭代2
|
||||
- 属性5 @迭代3
|
||||
- module 为 projectTask,缓存查询参数,查询参数中 queryID 为空。 @0
|
||||
- module 为 projectTask,缓存查询参数,查询参数中 actionURL 为空。 @0
|
||||
- module 为 projectTask,缓存查询参数,查询参数中有 project 字段。 @1
|
||||
- module 为 projectTask,缓存查询参数,打印 module 的值。属性module @task
|
||||
- module 为 projectTask,缓存查询参数,打印所属执行列表。
|
||||
- 属性6 @0
|
||||
- 属性7 @0
|
||||
- 属性8 @0
|
||||
- module 为 projectTask,不缓存查询参数,查询参数中 queryID 有值。 @1
|
||||
- module 为 projectTask,不缓存查询参数,查询参数中 actionURL 有值。 @1
|
||||
- module 为 projectTask,不缓存查询参数,查询参数中没有 project 字段。 @0
|
||||
- module 为 projectTask,不缓存查询参数,打印 module 的值。属性module @projectTask
|
||||
- module 为 projectTask,不缓存查询参数,打印 queryID 的值。属性queryID @1
|
||||
- module 为 projectTask,不缓存查询参数,打印 actionURL 的值。属性actionURL @/programplan-browse-2-0-gantt-id_asc-0-bysearch-myQueryID.html
|
||||
- module 为 projectTask,不缓存查询参数,打印所属执行列表。
|
||||
- 属性6 @/阶段1
|
||||
- 属性7 @/阶段2
|
||||
- 属性8 @/阶段3
|
||||
- module 为 task,缓存查询参数,查询参数中 queryID 为空。 @0
|
||||
- module 为 task,缓存查询参数,查询参数中 actionURL 为空。 @0
|
||||
- module 为 task,缓存查询参数,查询参数中 onMenuBar 为空。 @0
|
||||
- module 为 task,缓存查询参数,打印所属项目列表。
|
||||
- 属性1 @0
|
||||
- 属性2 @0
|
||||
- 属性all @0
|
||||
- module 为 task,缓存查询参数,打印所属执行列表。
|
||||
- @0
|
||||
- 属性3 @0
|
||||
- 属性all @0
|
||||
- module 为 task,不缓存查询参数,查询参数中 queryID 有值。 @1
|
||||
- module 为 task,不缓存查询参数,查询参数中 actionURL 有值。 @1
|
||||
- module 为 task,不缓存查询参数,查询参数中 onMenuBar 有值。 @1
|
||||
- module 为 task,不缓存查询参数,打印 module 的值。属性module @task
|
||||
- module 为 task,不缓存查询参数,打印 queryID 的值。属性queryID @1
|
||||
- module 为 task,不缓存查询参数,打印 actionURL 的值。属性actionURL @/execution-task-3-bySearch-myQueryID.html
|
||||
- module 为 task,不缓存查询参数,打印所属项目列表。
|
||||
- 属性1 @项目1
|
||||
- 属性2 @项目2
|
||||
- 属性all @所有项目
|
||||
- module 为 task,不缓存查询参数,打印所属执行列表。
|
||||
- @~~
|
||||
- 属性3 @迭代1
|
||||
- 属性all @所有执行
|
||||
|
||||
*/
|
||||
|
||||
$executionIDList = array('3', '0');
|
||||
$queryIDList = array('0', '1', '2');
|
||||
|
||||
$execution = new executionTest();
|
||||
r($execution->buildTaskSearchFormTest($executionIDList[0], $queryIDList[1])) && p() && e('1'); // 正确的执行,正确的queryID
|
||||
r($execution->buildTaskSearchFormTest($executionIDList[1], $queryIDList[1])) && p() && e('1'); // 错误的执行,正确的queryID
|
||||
r($execution->buildTaskSearchFormTest($executionIDList[0], $queryIDList[0])) && p() && e('0'); // 正确的执行,错误的queryID
|
||||
r($execution->buildTaskSearchFormTest($executionIDList[1], $queryIDList[0])) && p() && e('0'); // 错误的执行,错误的queryID
|
||||
r($execution->buildTaskSearchFormTest($executionIDList[1], $queryIDList[2])) && p() && e('2'); // 正确的执行,正确的queryID
|
||||
$queryID = 1;
|
||||
|
||||
/**
|
||||
* 测试为项目迭代列表页面构造搜索参数功能。
|
||||
*/
|
||||
$executionID = 1;
|
||||
$executions = [3 => '迭代1', 4 => '迭代2', 5 => '迭代3'];
|
||||
$actionURL = '/project-execution-bySearch-1-order_desc-0-0-100-1-myQueryID.html';
|
||||
$module = 'projectTask';
|
||||
$searchConfig = $execution->buildTaskSearchFormTest($executionID, $executions, $queryID, $actionURL, $module, true);
|
||||
r(isset($searchConfig['queryID'])) && p() && e(0); // module 为 projectTask,缓存查询参数,查询参数中 queryID 为空。
|
||||
r(isset($searchConfig['actionURL'])) && p() && e(0); // module 为 projectTask,缓存查询参数,查询参数中 actionURL 为空。
|
||||
r(isset($searchConfig['fields']['project'])) && p() && e(1); // module 为 projectTask,缓存查询参数,查询参数中有 project 字段。
|
||||
r(isset($searchConfig['fields']['module'])) && p() && e(1); // module 为 projectTask,缓存查询参数,查询参数中有 module 字段。
|
||||
r($searchConfig) && p('module') && e('task'); // module 为 projectTask,缓存查询参数,打印 module 的值。
|
||||
r($searchConfig['params']['execution']['values']) && p('3,4,5') && e('0,0,0'); // module 为 projectTask,缓存查询参数,打印所属执行列表。
|
||||
|
||||
$searchConfig = $execution->buildTaskSearchFormTest($executionID, $executions, $queryID, $actionURL, $module, false);
|
||||
r(isset($searchConfig['queryID'])) && p() && e(1); // module 为 projectTask,不缓存查询参数,查询参数中 queryID 有值。
|
||||
r(isset($searchConfig['actionURL'])) && p() && e(1); // module 为 projectTask,不缓存查询参数,查询参数中 actionURL 有值。
|
||||
r(isset($searchConfig['fields']['project'])) && p() && e(0); // module 为 projectTask,不缓存查询参数,查询参数中没有 project 字段。
|
||||
r(isset($searchConfig['fields']['module'])) && p() && e(0); // module 为 projectTask,不缓存查询参数,查询参数中没有 module 字段。
|
||||
r($searchConfig) && p('module') && e('projectTask'); // module 为 projectTask,不缓存查询参数,打印 module 的值。
|
||||
r($searchConfig) && p('queryID') && e('1'); // module 为 projectTask,不缓存查询参数,打印 queryID 的值。
|
||||
r($searchConfig) && p('actionURL') && e('/project-execution-bySearch-1-order_desc-0-0-100-1-myQueryID.html'); // module 为 projectTask,不缓存查询参数,打印 actionURL 的值。
|
||||
r($searchConfig['params']['execution']['values']) && p('3,4,5') && e('迭代1,迭代2,迭代3'); // module 为 projectTask,不缓存查询参数,打印所属执行列表。
|
||||
|
||||
/**
|
||||
* 测试为瀑布项目阶段甘特图页面构造搜索参数功能。
|
||||
*/
|
||||
$executionID = 2;
|
||||
$executions = [6 => '/阶段1', 7 => '/阶段2', 8 => '/阶段3'];
|
||||
$actionURL = '/programplan-browse-2-0-gantt-id_asc-0-bysearch-myQueryID.html';
|
||||
$module = 'projectTask';
|
||||
$searchConfig = $execution->buildTaskSearchFormTest($executionID, $executions, $queryID, $actionURL, $module, true);
|
||||
r(isset($searchConfig['queryID'])) && p() && e(0); // module 为 projectTask,缓存查询参数,查询参数中 queryID 为空。
|
||||
r(isset($searchConfig['actionURL'])) && p() && e(0); // module 为 projectTask,缓存查询参数,查询参数中 actionURL 为空。
|
||||
r(isset($searchConfig['fields']['project'])) && p() && e(1); // module 为 projectTask,缓存查询参数,查询参数中有 project 字段。
|
||||
r($searchConfig) && p('module') && e('task'); // module 为 projectTask,缓存查询参数,打印 module 的值。
|
||||
r($searchConfig['params']['execution']['values']) && p('6,7,8') && e('0,0,0'); // module 为 projectTask,缓存查询参数,打印所属执行列表。
|
||||
|
||||
$searchConfig = $execution->buildTaskSearchFormTest($executionID, $executions, $queryID, $actionURL, $module, false);
|
||||
r(isset($searchConfig['queryID'])) && p() && e(1); // module 为 projectTask,不缓存查询参数,查询参数中 queryID 有值。
|
||||
r(isset($searchConfig['actionURL'])) && p() && e(1); // module 为 projectTask,不缓存查询参数,查询参数中 actionURL 有值。
|
||||
r(isset($searchConfig['fields']['project'])) && p() && e(0); // module 为 projectTask,不缓存查询参数,查询参数中没有 project 字段。
|
||||
r($searchConfig) && p('module') && e('projectTask'); // module 为 projectTask,不缓存查询参数,打印 module 的值。
|
||||
r($searchConfig) && p('queryID') && e('1'); // module 为 projectTask,不缓存查询参数,打印 queryID 的值。
|
||||
r($searchConfig) && p('actionURL') && e('/programplan-browse-2-0-gantt-id_asc-0-bysearch-myQueryID.html'); // module 为 projectTask,不缓存查询参数,打印 actionURL 的值。
|
||||
r($searchConfig['params']['execution']['values']) && p('6,7,8') && e('/阶段1,/阶段2,/阶段3'); // module 为 projectTask,不缓存查询参数,打印所属执行列表。
|
||||
|
||||
/**
|
||||
* 测试为执行任务列表页面构造搜索参数功能。
|
||||
*/
|
||||
$executionID = 3;
|
||||
$executions = [3 => '迭代1', 4 => '迭代2', 5 => '迭代3'];
|
||||
$actionURL = '/execution-task-3-bySearch-myQueryID.html';
|
||||
$module = 'task';
|
||||
$searchConfig = $execution->buildTaskSearchFormTest($executionID, $executions, $queryID, $actionURL, $module, true);
|
||||
r(isset($searchConfig['queryID'])) && p() && e(0); // module 为 task,缓存查询参数,查询参数中 queryID 为空。
|
||||
r(isset($searchConfig['actionURL'])) && p() && e(0); // module 为 task,缓存查询参数,查询参数中 actionURL 为空。
|
||||
r(isset($searchConfig['onMenuBar'])) && p() && e(0); // module 为 task,缓存查询参数,查询参数中 onMenuBar 为空。
|
||||
r($searchConfig['params']['project']['values']) && p('1,2,all') && e('0,0,0'); // module 为 task,缓存查询参数,打印所属项目列表。
|
||||
r($searchConfig['params']['execution']['values']) && p(',3,all') && e('0,0,0'); // module 为 task,缓存查询参数,打印所属执行列表。
|
||||
|
||||
$searchConfig = $execution->buildTaskSearchFormTest($executionID, $executions, $queryID, $actionURL, $module, false);
|
||||
r(isset($searchConfig['queryID'])) && p() && e(1); // module 为 task,不缓存查询参数,查询参数中 queryID 有值。
|
||||
r(isset($searchConfig['actionURL'])) && p() && e(1); // module 为 task,不缓存查询参数,查询参数中 actionURL 有值。
|
||||
r(isset($searchConfig['onMenuBar'])) && p() && e(1); // module 为 task,不缓存查询参数,查询参数中 onMenuBar 有值。
|
||||
r($searchConfig) && p('module') && e('task'); // module 为 task,不缓存查询参数,打印 module 的值。
|
||||
r($searchConfig) && p('queryID') && e('1'); // module 为 task,不缓存查询参数,打印 queryID 的值。
|
||||
r($searchConfig) && p('actionURL') && e('/execution-task-3-bySearch-myQueryID.html'); // module 为 task,不缓存查询参数,打印 actionURL 的值。
|
||||
r($searchConfig['params']['project']['values']) && p('1,2,all') && e('项目1,项目2,所有项目'); // module 为 task,不缓存查询参数,打印所属项目列表。
|
||||
r($searchConfig['params']['execution']['values']) && p(',3,all') && e('~~,迭代1,所有执行'); // module 为 task,不缓存查询参数,打印所属执行列表。
|
||||
@@ -9,9 +9,6 @@ timeout=0
|
||||
cid=1
|
||||
|
||||
- 获取所有看板执行的累计卡片个数 @6
|
||||
- 获取看板1的累计流图信息
|
||||
- 属性execution @3
|
||||
- 属性type @task
|
||||
- 获取看板1的累计流图信息
|
||||
- 第7条的execution属性 @3
|
||||
- 第7条的type属性 @task
|
||||
@@ -61,8 +58,7 @@ $executionTester = new executionTest();
|
||||
$allExecutionCFDList = $executionTester->computeCFDTest();
|
||||
$singleExecutionCFDList = $executionTester->computeCFDTest(3);
|
||||
|
||||
r(count($allExecutionCFDList)) && p() && e('6'); // 获取所有看板执行的累计卡片个数
|
||||
r(current($singleExecutionCFDList)) && p('execution,type') && e('3,task'); // 获取看板1的累计流图信息
|
||||
r($singleExecutionCFDList) && p('7:execution,type') && e('3,task'); // 获取看板1的累计流图信息
|
||||
r($singleExecutionCFDList) && p('8:execution,type') && e('3,bug'); // 获取看板1的累计流图信息
|
||||
r($singleExecutionCFDList) && p('9:execution,type') && e('3,story'); // 获取看板1的累计流图信息
|
||||
r(count($allExecutionCFDList)) && p() && e('6'); // 获取所有看板执行的累计卡片个数
|
||||
r($singleExecutionCFDList) && p('7:execution,type') && e('3,task'); // 获取看板1的累计流图信息
|
||||
r($singleExecutionCFDList) && p('8:execution,type') && e('3,bug'); // 获取看板1的累计流图信息
|
||||
r($singleExecutionCFDList) && p('9:execution,type') && e('3,story'); // 获取看板1的累计流图信息
|
||||
|
||||
@@ -16,6 +16,12 @@ title=productModel->addExecutionMembers();
|
||||
cid=1
|
||||
pid=1
|
||||
|
||||
- 空的执行ID,空的团队 @0
|
||||
- 空的执行ID,有团队 @0
|
||||
- 不存在的执行ID,有团队 @0
|
||||
- 存在的执行ID,空团队 @0
|
||||
- 存在的执行ID,有团队 @3
|
||||
|
||||
*/
|
||||
|
||||
$execution = new executionTest('admin');
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
|
||||
title=测试 executionZen::buildTasksForImportBug();
|
||||
timeout=0
|
||||
cid=1
|
||||
|
||||
- 测试空数据 @0
|
||||
- 测试预估工时为负数的情况属性type[1] @ID: 1"最初预计"必须为正数
|
||||
- 测试截止日期小于实际开始日期的情况属性deadline[1] @ID: 1"截止日期"必须大于"预计开始"
|
||||
- 测试正常的情况
|
||||
- 第1条的name属性 @BUG1
|
||||
- 第1条的execution属性 @3
|
||||
- 第1条的type属性 @devel
|
||||
|
||||
*/
|
||||
include dirname(__FILE__, 5) . '/test/lib/init.php';
|
||||
include dirname(__FILE__, 2) . '/lib/executionzen.unittest.class.php';
|
||||
|
||||
$bugTable = zenData('bug');
|
||||
$bugTable->project->range('3');
|
||||
$bugTable->gen(10);
|
||||
|
||||
zenData('project')->loadYaml('project')->gen(3);
|
||||
zenData('task')->loadYaml('task')->gen(10);
|
||||
zenData('user')->gen(5);
|
||||
su('admin');
|
||||
|
||||
$modeList = array('emptyData', 'errorEstimate', 'errorDeadline', 'normal');
|
||||
|
||||
global $tester;
|
||||
$execution = new executionZenTest();
|
||||
r($execution->buildTasksForImportBugTest($modeList[0])) && p() && e('0'); // 测试空数据
|
||||
r($execution->buildTasksForImportBugTest($modeList[1])) && p('type[1]') && e('ID: 1"最初预计"必须为正数'); // 测试预估工时为负数的情况
|
||||
r($execution->buildTasksForImportBugTest($modeList[2])) && p('deadline[1]') && e('ID: 1"截止日期"必须大于"预计开始"'); // 测试截止日期小于实际开始日期的情况
|
||||
r($execution->buildTasksForImportBugTest($modeList[3])) && p('1:name,execution,type') && e('BUG1,3,devel'); // 测试正常的情况
|
||||
@@ -101,6 +101,7 @@ $fields->field('percent')
|
||||
->label($lang->stage->percent . sprintf($lang->execution->unitTemplate, '%'));
|
||||
|
||||
$fields->field('productsBox')
|
||||
->id('productsBox')
|
||||
->width('full')
|
||||
->control(array
|
||||
(
|
||||
|
||||
@@ -142,6 +142,7 @@ if($hasPercent && $project && $project->model != 'ipd')
|
||||
}
|
||||
|
||||
$fields->field('productsBox')
|
||||
->id('productsBox')
|
||||
->width('full')
|
||||
->className($hidden)
|
||||
->control(array(
|
||||
|
||||
@@ -31,7 +31,7 @@ featureBar
|
||||
|
||||
/* zin: Define the toolbar on main menu. */
|
||||
$canCreateTestcase = hasPriv('testcase', 'create') && common::canModify('execution', $execution);
|
||||
if($canCreateTestcase) $createItem = array('icon' => 'plus', 'class' => 'primary', 'text' => $lang->testcase->create, 'url' => $this->createLink('testcase', 'create', "productID={$productID}&branch=0&moduleID=0&from=execution¶m={$execution->id}"), 'data-app' => 'execution');
|
||||
if($canCreateTestcase) $createItem = array('icon' => 'plus', 'class' => 'primary', 'text' => $lang->testcase->create, 'url' => $this->createLink('testcase', 'create', "productID={$productID}&branch=0&moduleID=0&from=execution¶m={$execution->id}"), 'data-app' => 'execution', 'id' => 'createTestCaseBtn');
|
||||
$viewItems = array(array('text' => $lang->testcase->listView, 'url' => createLink('execution', 'testcase', "executionID={$executionID}&productID={$productID}&branchID={$branchID}"), 'active' => true));
|
||||
if(hasPriv('testcase', 'groupcase'))
|
||||
{
|
||||
|
||||
@@ -749,7 +749,7 @@ class executionZen extends execution
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!preg_match("/^[0-9]+(.[0-9]{1,3})?$/", (string)$task->estimate) and !empty($task->estimate))
|
||||
if(!preg_match("/^[0-9]+(.[0-9]+)?$/", (string)$task->estimate) and !empty($task->estimate))
|
||||
{
|
||||
dao::$errors["{$field}[{$bugID}]"] = 'ID: ' . $bugID . $this->lang->task->error->estimateNumber;
|
||||
return false;
|
||||
|
||||
@@ -162,7 +162,7 @@ class file extends control
|
||||
if($mode != 'open')
|
||||
{
|
||||
$fileName = $file->title;
|
||||
if(!preg_match("/\.{$file->extension}$/", $fileName)) $fileName .= '.' . $file->extension;
|
||||
if(!preg_match("/\.{$file->extension}$/", strtolower($fileName))) $fileName .= '.' . $file->extension;
|
||||
return $this->sendDownHeader($fileName, $file->extension, $file->realPath, 'file');
|
||||
}
|
||||
|
||||
|
||||
+59
-29
@@ -56,6 +56,31 @@ class fileModel extends model
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按照objectID分组获取附件列表。
|
||||
* Get files of an object group by objectID.
|
||||
*
|
||||
* @param string $objectType
|
||||
* @param string|array $objectID
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function groupByObjectID(string $objectType, string|array $objectID): array
|
||||
{
|
||||
$fileGroup = $this->dao->select('*')->from(TABLE_FILE)
|
||||
->where('objectType')->eq($objectType)
|
||||
->andWhere('objectID')->in($objectID)
|
||||
->andWhere('deleted')->eq('0')
|
||||
->orderBy('id')
|
||||
->fetchGroup('objectID', 'id');
|
||||
|
||||
foreach($fileGroup as $objectID => $files)
|
||||
{
|
||||
foreach($files as $file) $this->setFileWebAndRealPaths($file);
|
||||
}
|
||||
return $fileGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete files by object.
|
||||
*
|
||||
@@ -544,10 +569,14 @@ class fileModel extends model
|
||||
$content = (string)$content;
|
||||
if(empty($content)) return $content;
|
||||
|
||||
$readLinkReg = str_replace(array('%fileID%', '/', '.', '?'), array('[0-9]+', '\/', '\.', '\?'), helper::createLink('file', 'read', 'fileID=(%fileID%)', '\w+'));
|
||||
static $fileLink, $imageLink;
|
||||
if(empty($fileLink)) $fileLink = helper::createLink('file', 'read', 'fileID=(%fileID%)', '\w+');
|
||||
if(empty($imageLink)) $imageLink = helper::createLink('file', 'read', "fileID=$1", "$3");
|
||||
|
||||
$readLinkReg = str_replace(array('%fileID%', '/', '.', '?'), array('[0-9]+', '\/', '\.', '\?'), $fileLink);
|
||||
|
||||
$content = preg_replace('/ src="(' . $readLinkReg . ')" /', ' onload="setImageSize(this,' . $maxSize . ')" src="$1" ', $content);
|
||||
$content = preg_replace('/ src="{([0-9]+)(\.(\w+))?}" /', ' onload="setImageSize(this,' . $maxSize . ')" src="' . helper::createLink('file', 'read', "fileID=$1", "$3") . '" ', $content);
|
||||
$content = preg_replace('/ src="{([0-9]+)(\.(\w+))?}" /', ' onload="setImageSize(this,' . $maxSize . ')" src="' . $imageLink . '" ', $content);
|
||||
|
||||
return str_replace(' src="data/upload', ' onload="setImageSize(this,' . $maxSize . ')" src="data/upload', $content);
|
||||
}
|
||||
@@ -1028,7 +1057,7 @@ class fileModel extends model
|
||||
|
||||
/* Append the extension name auto. */
|
||||
$extension = $fileType ? ('.' . $fileType) : '';
|
||||
if($extension && strpos($fileName, $extension) === false) $fileName .= $extension;
|
||||
if($extension && strpos(strtolower($fileName), $extension) === false) $fileName .= $extension;
|
||||
|
||||
/* Judge the content type. */
|
||||
$mimes = $this->config->file->mimes;
|
||||
@@ -1276,20 +1305,21 @@ class fileModel extends model
|
||||
$html .= '</span>';
|
||||
$html .= '</li>';
|
||||
|
||||
if(strrpos($file->title, '.') !== false)
|
||||
$fileTitle = $file->title;
|
||||
if(strrpos($fileTitle, '.') !== false)
|
||||
{
|
||||
/* Fix the file name exe.exe */
|
||||
$title = explode('.', $file->title);
|
||||
$title = explode('.', $fileTitle);
|
||||
$extension = end($title);
|
||||
if($file->extension == 'txt' && $extension != $file->extension) $file->extension = $extension;
|
||||
array_pop($title);
|
||||
$file->title = join('.', $title);
|
||||
$fileTitle = implode('.', $title);
|
||||
}
|
||||
|
||||
$html .= "<li class='file hidden'><div>";
|
||||
$html .= "<div class='renameFile w-300px' id='renameBox{$file->id}'><i class='icon icon-file-text'></i>";
|
||||
$html .= "<div class='input-group'>";
|
||||
$html .= "<input type='text' id='fileName{$file->id}' value='{$file->title}' class='form-control'/>";
|
||||
$html .= "<input type='text' id='fileName{$file->id}' value='{$fileTitle}' class='form-control'/>";
|
||||
$html .= "<input type='hidden' id='extension{$file->id}' value='{$file->extension}'/>";
|
||||
$html .= "<strong class='input-group-addon'>.{$file->extension}</strong></div>";
|
||||
$html .= "<div class='input-group-btn'>";
|
||||
@@ -1354,31 +1384,31 @@ class fileModel extends model
|
||||
*/
|
||||
public function saveDefaultFiles(array $fileList, string $objectType, int|array $objectIdList, string|int $extra = '')
|
||||
{
|
||||
if(is_int($objectIdList)) $objectIdList = array($objectIdList);
|
||||
if(!empty($fileList))
|
||||
{
|
||||
if(!empty($_POST['deleteFiles']))
|
||||
{
|
||||
foreach($this->post->deleteFiles as $deletedFileID) unset($fileList[$deletedFileID]);
|
||||
}
|
||||
if(!empty($_POST['renameFiles']))
|
||||
{
|
||||
foreach($this->post->renameFiles as $renamedFileID => $newName) $fileList[$renamedFileID]['title'] = $newName;
|
||||
}
|
||||
if(empty($fileList)) return;
|
||||
|
||||
foreach($objectIdList as $objectID)
|
||||
if(is_int($objectIdList)) $objectIdList = array($objectIdList);
|
||||
|
||||
if(!empty($_POST['deleteFiles']))
|
||||
{
|
||||
foreach($this->post->deleteFiles as $deletedFileID) unset($fileList[$deletedFileID]);
|
||||
}
|
||||
if(!empty($_POST['renameFiles']))
|
||||
{
|
||||
foreach($this->post->renameFiles as $renamedFileID => $newName) $fileList[$renamedFileID]['title'] = $newName;
|
||||
}
|
||||
|
||||
foreach($objectIdList as $objectID)
|
||||
{
|
||||
$fileIdList = '';
|
||||
foreach($fileList as $file)
|
||||
{
|
||||
$fileIdList = '';
|
||||
foreach($fileList as $file)
|
||||
{
|
||||
unset($file['id']);
|
||||
$file['objectType'] = $objectType;
|
||||
$file['objectID'] = $objectID;
|
||||
$file['extra'] = $extra;
|
||||
$fileIdList .= ',' . $this->fileTao->saveFile($file, 'url,deleted,realPath,webPath,name,url');
|
||||
}
|
||||
if($objectType == 'story') $this->dao->update(TABLE_STORYSPEC)->set("files = CONCAT(files, '{$fileIdList}')")->where('story')->eq($objectID)->exec();
|
||||
unset($file['id']);
|
||||
$file['objectType'] = $objectType;
|
||||
$file['objectID'] = $objectID;
|
||||
$file['extra'] = $extra;
|
||||
$fileIdList .= ',' . $this->fileTao->saveFile($file, 'url,deleted,realPath,webPath,name,url');
|
||||
}
|
||||
if($objectType == 'story') $this->dao->update(TABLE_STORYSPEC)->set("files = CONCAT(files, '{$fileIdList}')")->where('story')->eq($objectID)->exec();
|
||||
}
|
||||
|
||||
foreach($objectIdList as $objectID)
|
||||
|
||||
@@ -460,4 +460,20 @@ class fileTest
|
||||
$file = $this->objectModel->dao->select('*')->from(TABLE_FILE)->where('id')->eq($fileID)->fetch();
|
||||
return $this->objectModel->printFile($file, $method, $showDelete, $showEdit, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build file actions.
|
||||
*
|
||||
* @param int $fileID
|
||||
* @param bool $showEdit
|
||||
* @param bool $showDelete
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function buildFileActionsTest(int $fileID, bool $showEdit, bool $showDelete): string
|
||||
{
|
||||
$file = $this->objectModel->dao->select('*')->from(TABLE_FILE)->where('id')->eq($fileID)->fetch();
|
||||
$downloadLink = helper::createLink('file', 'download', "fileID=$fileID");
|
||||
return $this->objectModel->buildFileActions('', $downloadLink, 0, $showEdit, $showDelete, $file, null);
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
|
||||
title=测试 fileModel->printFile();
|
||||
timeout=0
|
||||
cid=0
|
||||
|
||||
- 测试非详情/编辑页面的情况 @1
|
||||
- 测试编辑页面不展示删除按钮的情况 @0
|
||||
- 测试编辑页面不展示编辑按钮的情况 @1
|
||||
- 测试详情页面不展示删除按钮的情况 @0
|
||||
- 测试详情页面不展示编辑按钮的情况 @1
|
||||
|
||||
*/
|
||||
|
||||
include dirname(__FILE__, 5) . '/test/lib/init.php';
|
||||
include dirname(__FILE__, 2) . '/lib/file.unittest.class.php';
|
||||
su('admin');
|
||||
|
||||
zenData('file')->gen(5);
|
||||
|
||||
$fileIdList = range(1, 5);
|
||||
$showDeleteList = array(true, false);
|
||||
$showEdit = array(true, false);
|
||||
|
||||
$fileTester = new fileTest();
|
||||
|
||||
$result1 = $fileTester->buildFileActionsTest($fileIdList[0], $showDeleteList[0], $showEdit[0]);
|
||||
$result2 = $fileTester->buildFileActionsTest($fileIdList[1], $showDeleteList[1], $showEdit[0]);
|
||||
$result3 = $fileTester->buildFileActionsTest($fileIdList[2], $showDeleteList[0], $showEdit[1]);
|
||||
$result4 = $fileTester->buildFileActionsTest($fileIdList[3], $showDeleteList[1], $showEdit[0]);
|
||||
$result5 = $fileTester->buildFileActionsTest($fileIdList[4], $showDeleteList[0], $showEdit[1]);
|
||||
|
||||
r(strpos($result1, '文件标题1') !== false) && p() && e('1'); // 测试非详情/编辑页面的情况
|
||||
r(strpos($result2, '删除') === false) && p() && e('0'); // 测试编辑页面不展示删除按钮的情况
|
||||
r(strpos($result3, '编辑') === false) && p() && e('1'); // 测试编辑页面不展示编辑按钮的情况
|
||||
r(strpos($result4, '删除') === false) && p() && e('0'); // 测试详情页面不展示删除按钮的情况
|
||||
r(strpos($result5, '编辑') === false) && p() && e('1'); // 测试详情页面不展示编辑按钮的情况
|
||||
Executable
+70
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
|
||||
title=测试 fileModel->groupByObjectID();
|
||||
timeout=0
|
||||
cid=0
|
||||
|
||||
- 获取 traincourse id 5 的文件
|
||||
- 第5条的title属性 @文件标题5
|
||||
- 第5条的extension属性 @wps
|
||||
- 第5条的objectType属性 @traincourse
|
||||
- 第5条的objectID属性 @5
|
||||
- 第5条的webPath属性 @/data/course/202305/0414225006610009
|
||||
- 获取 traincontents id 6 的文件
|
||||
- 第6条的title属性 @文件标题6
|
||||
- 第6条的extension属性 @wri
|
||||
- 第6条的objectType属性 @traincontents
|
||||
- 第6条的objectID属性 @6
|
||||
- 第6条的webPath属性 @/data/course/202305/0414225006610010
|
||||
- 获取 task id 7 的文件
|
||||
- 第7条的title属性 @文件标题7
|
||||
- 第7条的extension属性 @pdf
|
||||
- 第7条的objectType属性 @task
|
||||
- 第7条的objectID属性 @7
|
||||
- 第7条的webPath属性 @/data/upload/1/202305/0414225006610011
|
||||
- 获取 bug id 8 的文件
|
||||
- 第8条的title属性 @文件标题8
|
||||
- 第8条的extension属性 @ppt
|
||||
- 第8条的objectType属性 @bug
|
||||
- 第8条的objectID属性 @8
|
||||
- 第8条的webPath属性 @/data/upload/1/202305/0414225006610005
|
||||
- 获取 story id 9 的文件
|
||||
- 第9条的title属性 @文件标题9
|
||||
- 第9条的extension属性 @pptx
|
||||
- 第9条的objectType属性 @story
|
||||
- 第9条的objectID属性 @9
|
||||
- 第9条的webPath属性 @/data/upload/1/202305/0414225006610006
|
||||
- 获取 testcase id 10 的文件
|
||||
- 第10条的title属性 @文件标题10
|
||||
- 第10条的extension属性 @xls
|
||||
- 第10条的objectType属性 @testcase
|
||||
- 第10条的objectID属性 @10
|
||||
- 第10条的webPath属性 @/data/upload/1/202305/0414225006610007
|
||||
- 获取 testcase id 5 的文件 @0
|
||||
|
||||
*/
|
||||
include dirname(__FILE__, 5) . '/test/lib/init.php';
|
||||
su('admin');
|
||||
|
||||
$file = zenData('file');
|
||||
$file->pathname->range('202305/0414225006610005,202305/0414225006610006,202305/0414225006610007,202305/0414225006610008,202305/0414225006610009,202305/0414225006610010,202305/0414225006610011');
|
||||
$file->gen(20);
|
||||
$objectType = array('traincourse', 'traincontents', 'task', 'bug', 'story', 'testcase');
|
||||
$objectID = array(5, 6, 7, 8, 9, 10);
|
||||
$extra = 0;
|
||||
|
||||
global $tester;
|
||||
$tester->loadModel('file');
|
||||
$tester->file->webPath = '/data/upload/1/';
|
||||
$tester->file->config->webRoot = '/';
|
||||
|
||||
r($tester->file->groupByObjectID('traincourse', '5')[5]) && p('5:title,extension,objectType,objectID,webPath') && e('文件标题5,wps,traincourse,5,/data/course/202305/0414225006610009'); // 获取 traincourse id 5 的文件
|
||||
r($tester->file->groupByObjectID('traincontents', '6')[6]) && p('6:title,extension,objectType,objectID,webPath') && e('文件标题6,wri,traincontents,6,/data/course/202305/0414225006610010'); // 获取 traincontents id 6 的文件
|
||||
r($tester->file->groupByObjectID('task', '7')[7]) && p('7:title,extension,objectType,objectID,webPath') && e('文件标题7,pdf,task,7,/data/upload/1/202305/0414225006610011'); // 获取 task id 7 的文件
|
||||
r($tester->file->groupByObjectID('bug', '8')[8]) && p('8:title,extension,objectType,objectID,webPath') && e('文件标题8,ppt,bug,8,/data/upload/1/202305/0414225006610005'); // 获取 bug id 8 的文件
|
||||
r($tester->file->groupByObjectID('story', '9')[9]) && p('9:title,extension,objectType,objectID,webPath') && e('文件标题9,pptx,story,9,/data/upload/1/202305/0414225006610006'); // 获取 story id 9 的文件
|
||||
r($tester->file->groupByObjectID('testcase', '10')[10]) && p('10:title,extension,objectType,objectID,webPath') && e('文件标题10,xls,testcase,10,/data/upload/1/202305/0414225006610007'); // 获取 testcase id 10 的文件
|
||||
r($tester->file->groupByObjectID('testcase', 5)) && p() && e('0'); // 获取 testcase id 5 的文件
|
||||
@@ -9,6 +9,8 @@ cid=0
|
||||
- 根据内容下载。 @test
|
||||
- 文件路径是非法路径。 @0
|
||||
- 根据文件路径下载。 @test1
|
||||
- 测试扩展名大写。 @test2
|
||||
- 测试没有扩展名。 @test3
|
||||
|
||||
*/
|
||||
include dirname(__FILE__, 5) . '/test/lib/init.php';
|
||||
@@ -36,6 +38,8 @@ try
|
||||
{
|
||||
r($fileModel->sendDownHeader('test.txt', 'txt', 'test.txt', 'file')) && p() && e('0'); //文件路径是非法路径。
|
||||
r($fileModel->sendDownHeader('test.txt', 'txt', $downloadFile, 'file')) && p() && e('test1'); //根据文件路径下载。
|
||||
r($fileModel->sendDownHeader('test.TXT', 'txt', $downloadFile, 'file')) && p() && e('test2'); //测试扩展名大写。
|
||||
r($fileModel->sendDownHeader('test', 'txt', $downloadFile, 'file')) && p() && e('test3'); //测试没有扩展名。
|
||||
}
|
||||
catch(EndResponseException $e){}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user