Merge pull request #724 from getrebuild/fix-3.6-beta1

Fix 3.6-beta1
This commit is contained in:
REBUILD 企业管理系统 2024-03-06 15:14:48 +08:00 committed by GitHub
commit 976a4d48f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 230 additions and 136 deletions

2
@rbv

@ -1 +1 @@
Subproject commit c7521f5df907ed9500d65dd8487b6517cfd7b21d
Subproject commit e26090af686fd25f9071b1776b303b3e8ae9491a

View file

@ -297,7 +297,7 @@
<dependency>
<groupId>com.github.devezhao</groupId>
<artifactId>persist4j</artifactId>
<version>1.7.6</version>
<version>1.7.7</version>
<exclusions>
<exclusion>
<groupId>com.alibaba</groupId>

View file

@ -78,7 +78,7 @@ public class Application implements ApplicationListener<ApplicationStartedEvent>
/**
* Rebuild Build [MAJOR]{1}[MINOR]{2}[PATCH]{2}[BUILD]{2}
*/
public static final int BUILD = 3060001;
public static final int BUILD = 3060002;
static {
// Driver for DB

View file

@ -21,9 +21,12 @@ import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import org.apache.commons.lang3.StringUtils;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -50,16 +53,16 @@ public class TransformManager implements ConfigManager {
*/
public JSONArray getTransforms(String sourceEntity, ID user) {
JSONArray data = new JSONArray();
for (ConfigBean c : getRawTransforms(sourceEntity)) {
JSONObject config = (JSONObject) c.getJSON("config");
for (ConfigBean cb : getRawTransforms(sourceEntity)) {
JSONObject config = (JSONObject) cb.getJSON("config");
// 过滤尚未配置或禁用的
if (config == null || c.getBoolean("disabled")) continue;
if (config == null || cb.getBoolean("disabled")) continue;
// 无字段映射
JSONObject fieldsMapping = config.getJSONObject("fieldsMapping");
if (fieldsMapping == null || fieldsMapping.isEmpty()) continue;
String target = c.getString("target");
String target = cb.getString("target");
Entity targetEntity = MetadataHelper.getEntity(target);
if (targetEntity.getMainEntity() == null) {
@ -74,8 +77,8 @@ public class TransformManager implements ConfigManager {
}
JSONObject item = EasyMetaFactory.toJSON(targetEntity);
item.put("transid", c.getID("id"));
item.put("transName", c.getString("name")); // v3.6 重启用
item.put("transid", cb.getID("id"));
item.put("transName", cb.getString("name"));
item.put("previewMode", config.getIntValue("transformMode") == 2);
data.add(item);
}
@ -104,9 +107,7 @@ public class TransformManager implements ConfigManager {
public List<ConfigBean> getRawTransforms(String sourceEntity) {
final String cKey = "TransformManager31-" + sourceEntity;
Object cached = Application.getCommonsCache().getx(cKey);
if (cached != null) {
return (List<ConfigBean>) cached;
}
if (cached != null) return (List<ConfigBean>) cached;
Object[][] array = Application.createQueryNoFilter(
"select belongEntity,targetEntity,configId,config,isDisabled,name from TransformConfig where belongEntity = ?")
@ -131,6 +132,7 @@ public class TransformManager implements ConfigManager {
entries.add(entry);
}
sortByName(entries);
Application.getCommonsCache().putx(cKey, entries);
return entries;
}
@ -184,10 +186,21 @@ public class TransformManager implements ConfigManager {
}
}
sortByName(imports);
WEAK_CACHED.put(targetEntity, imports);
return imports;
}
// v3.6 排序
private void sortByName(List<ConfigBean> list) {
if (list == null || list.isEmpty()) return;
Comparator<Object> comparator = Collator.getInstance(Locale.CHINESE);
list.sort((o1, o2) -> comparator.compare(
org.apache.commons.lang3.ObjectUtils.defaultIfNull(o1.getString("name"), ""),
org.apache.commons.lang3.ObjectUtils.defaultIfNull(o2.getString("name"), "")));
}
@Override
public void clean(Object cfgid) {
final String cKey = "TransformManager31-" + getBelongEntity((ID) cfgid);

View file

@ -438,27 +438,37 @@ public class Field2Schema extends SetUser {
meta.setString("displayType", toType.name());
Application.getCommonsService().update(meta, false);
Dialect dialect = Application.getPersistManagerFactory().getDialect();
final Table table = new Table(field.getOwnEntity(), dialect);
StringBuilder ddl = new StringBuilder();
table.generateFieldDDL(field, ddl);
String alterSql = String.format("alter table `%s` change column `%s` ",
field.getOwnEntity().getPhysicalName(), field.getPhysicalName());
alterSql += ddl.toString().trim();
// 类型生效
DynamicMetadataContextHolder.setSkipLanguageRefresh();
MetadataHelper.getMetadataFactory().refresh();
field = MetadataHelper.getField(field.getOwnEntity().getName(), field.getName());
String alterTypeSql = null;
try {
Application.getSqlExecutor().executeBatch(new String[]{alterSql}, DDL_TIMEOUT);
Dialect dialect = Application.getPersistManagerFactory().getDialect();
final Table table = new Table(field.getOwnEntity(), dialect);
StringBuilder ddl = new StringBuilder();
table.generateFieldDDL(field, ddl);
alterTypeSql = String.format("alter table `%s` change column `%s` ",
field.getOwnEntity().getPhysicalName(), field.getPhysicalName());
alterTypeSql += ddl.toString().trim().replace(" ", "");
Application.getSqlExecutor().executeBatch(new String[]{alterTypeSql}, DDL_TIMEOUT);
log.info("Cast field type : {}", alterTypeSql);
} catch (Throwable ex) {
// 还原
meta.setString("displayType", EasyMetaFactory.getDisplayType(field).name());
Application.getCommonsService().update(meta, false);
log.error("DDL ERROR : \n" + alterSql, ex);
log.error("DDL ERROR : \n" + alterTypeSql, ex);
throw new MetadataModificationException(ThrowableUtils.getRootCause(ex).getLocalizedMessage());
} finally {
MetadataHelper.getMetadataFactory().refresh();
DynamicMetadataContextHolder.isSkipLanguageRefresh(true);
}
MetadataHelper.getMetadataFactory().refresh();
return true;
}
}

View file

@ -217,6 +217,7 @@ public class DataReportManager implements ConfigManager {
else if (fileName.endsWith(".doc")) name += ".doc";
else if (fileName.endsWith(".xlsx")) name += ".xlsx";
else if (fileName.endsWith(".xls")) name += ".xls";
else if (fileName.endsWith(".csv")) name += ".csv";
break;
}
}

View file

@ -81,7 +81,8 @@ import static com.rebuild.core.service.datareport.TemplateExtractor33.NROW_PREFI
@Slf4j
public class EasyExcelGenerator extends SetUser {
final protected static String MDATA_KEY = ".";
final protected static String REFKEY_RECORD_MAIN = ".";
final protected static String REFKEY_LIST = REFKEY_RECORD_MAIN + "36LIST";
protected File templateFile;
protected Integer writeSheetAt = null;
@ -110,10 +111,11 @@ public class EasyExcelGenerator extends SetUser {
Map<String, List<Map<String, Object>>> datas = buildData();
if (datas.isEmpty()) throw new DefinedException(Language.L("暂无数据"));
// 记录模板-主记录
Map<String, Object> main = null;
if (datas.containsKey(MDATA_KEY)) {
main = datas.get(MDATA_KEY).get(0);
datas.remove(MDATA_KEY);
if (datas.containsKey(REFKEY_RECORD_MAIN)) {
main = datas.get(REFKEY_RECORD_MAIN).get(0);
datas.remove(REFKEY_RECORD_MAIN);
}
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
@ -124,18 +126,20 @@ public class EasyExcelGenerator extends SetUser {
.registerWriteHandler(new FormulaCellWriteHandler())
.build();
// 一个列表
if (datas.size() == 1) {
Object o = datas.values().iterator().next();
excelWriter.fill(o, fillConfig, writeSheet);
int datasLen = datas.size();
boolean useRefKeys = datasLen > 1;
if (datasLen == 1) {
String refKey = datas.keySet().iterator().next();
useRefKeys = refKey.startsWith("$");
}
// fix: v3.6 多个列表
else if (datas.size() > 1) {
// fix: v3.6 多个列表 $前缀
if (useRefKeys) {
for (Map.Entry<String, List<Map<String, Object>>> e : datas.entrySet()) {
final String refKey = NROW_PREFIX2 + e.getKey().substring(1);
final List<Map<String, Object>> refDatas = e.getValue();
List<Map<String, Object>> refDatasNew = new ArrayList<>();
List<Map<String, Object>> refDatas2New = new ArrayList<>();
for (Map<String, Object> map : refDatas) {
Map<String, Object> mapNew = new HashMap<>();
for (Map.Entry<String, Object> ee : map.entrySet()) {
@ -143,12 +147,17 @@ public class EasyExcelGenerator extends SetUser {
if (keyNew.startsWith("$") || keyNew.startsWith(".")) keyNew = keyNew.substring(1);
mapNew.put(keyNew, ee.getValue());
}
refDatasNew.add(mapNew);
refDatas2New.add(mapNew);
}
excelWriter.fill(new FillWrapper(refKey, refDatasNew), fillConfig, writeSheet);
excelWriter.fill(new FillWrapper(refKey, refDatas2New), fillConfig, writeSheet);
}
}
// 一个列表/列表模板
else if (datasLen == 1) {
Object datas35 = datas.values().iterator().next();
excelWriter.fill(datas35, fillConfig, writeSheet);
}
// 主记录
if (main != null) {
@ -253,7 +262,7 @@ public class EasyExcelGenerator extends SetUser {
Assert.notNull(record, "No record found : " + recordId);
Map<String, Object> d = buildData(record, varsMapOfMain);
datas.put(MDATA_KEY, Collections.singletonList(d));
datas.put(REFKEY_RECORD_MAIN, Collections.singletonList(d));
}
// 明细
@ -274,7 +283,7 @@ public class EasyExcelGenerator extends SetUser {
detailList.add(buildData(c, varsMapOfDetail));
phNumber++;
}
datas.put(MDATA_KEY + "detail", detailList);
datas.put(REFKEY_RECORD_MAIN + "detail", detailList);
}
// 审批
@ -293,7 +302,7 @@ public class EasyExcelGenerator extends SetUser {
approvalList.add(buildData(c, varsMapOfApproval));
phNumber++;
}
datas.put(MDATA_KEY + "approval", approvalList);
datas.put(REFKEY_RECORD_MAIN + "approval", approvalList);
}
return datas;

View file

@ -65,7 +65,7 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
final private List<ID> recordIdMultiple;
private Set<String> inShapeVars;
private Map<String, Object> mdataHolder;
private Map<String, Object> recordMainHolder;
protected EasyExcelGenerator33(File templateFile, ID recordId) {
super(templateFile, recordId);
@ -98,9 +98,9 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
String refKey = null;
if (varName.startsWith(NROW_PREFIX) || varName.startsWith(NROW_PREFIX2)) {
if (varName.startsWith(APPROVAL_PREFIX) || varName.startsWith(APPROVAL_PREFIX2)) {
refKey = APPROVAL_PREFIX;
refKey = varName.startsWith(NROW_PREFIX) ? APPROVAL_PREFIX : APPROVAL_PREFIX2;
} else if (varName.startsWith(DETAIL_PREFIX) || varName.startsWith(DETAIL_PREFIX2)) {
refKey = DETAIL_PREFIX;
refKey = varName.startsWith(NROW_PREFIX) ? DETAIL_PREFIX : DETAIL_PREFIX2;
} else {
// 在客户中导出订单下列 AccountId 为订单中引用客户的引用字段
// .AccountId.SalesOrder.SalesOrderName or $AccountId$SalesOrder$SalesOrderName
@ -120,7 +120,9 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
}
// 占位字段
if (TemplateExtractor33.isPlaceholder(varName)) continue;
if (TemplateExtractor33.isPlaceholder(varName)) {
continue;
}
// 无效字段
if (fieldName == null) {
log.warn("Invalid field `{}` in template : {}", e.getKey(), templateFile);
@ -155,14 +157,15 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
Assert.notNull(record, "No record found : " + recordId);
Map<String, Object> d = buildData(record, varsMapOfMain);
datas.put(MDATA_KEY, Collections.singletonList(d));
mdataHolder = d;
datas.put(REFKEY_RECORD_MAIN, Collections.singletonList(d));
recordMainHolder = d;
}
// 相关记录含明细审批
for (Map.Entry<String, List<String>> e : fieldsOfRefs.entrySet()) {
final String refKey = e.getKey();
final boolean isApproval = refKey.startsWith(APPROVAL_PREFIX);
final boolean isApproval = refKey.startsWith(APPROVAL_PREFIX) || refKey.startsWith(APPROVAL_PREFIX2);
final boolean isDetail = refKey.startsWith(DETAIL_PREFIX) || refKey.startsWith(DETAIL_PREFIX2);
String querySql = baseSql;
if (isApproval) {
@ -170,12 +173,11 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
querySql = String.format(querySql, StringUtils.join(e.getValue(), ","),
"createdOn,recordId,state,stepId", "RobotApprovalStep", "recordId");
} else if (refKey.startsWith(DETAIL_PREFIX)) {
Entity de = entity.getDetailEntity();
} else if (isDetail) {
String sortField = templateExtractor33.getSortField(DETAIL_PREFIX);
querySql += " order by " + StringUtils.defaultIfBlank(sortField, "createdOn asc");
Entity de = entity.getDetailEntity();
querySql = String.format(querySql, StringUtils.join(e.getValue(), ","),
de.getPrimaryField().getName(), de.getName(), MetadataHelper.getDetailToMainField(de).getName());
@ -312,7 +314,7 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
private File superGenerate() {
File file = super.generate();
if (inShapeVars.isEmpty() || mdataHolder == null) return file;
if (inShapeVars.isEmpty() || recordMainHolder == null) return file;
// v3.6 提取文本框
try (Workbook wb = WorkbookFactory.create(file)) {
@ -324,7 +326,7 @@ public class EasyExcelGenerator33 extends EasyExcelGenerator {
while (matcher.find()) {
String varName = matcher.group(1);
if (StringUtils.isNotBlank(varName)) {
shapeText = shapeText.replace("{" + varName +"}", String.valueOf(mdataHolder.get(varName)));
shapeText = shapeText.replace("{" + varName +"}", String.valueOf(recordMainHolder.get(varName)));
}
}

View file

@ -97,7 +97,7 @@ public class EasyExcelListGenerator extends EasyExcelGenerator {
if (varsMap.containsKey(PH__CURRENTDATETIME)) phValues.put(PH__CURRENTDATETIME, getPhValue(PH__CURRENTDATETIME));
Map<String, List<Map<String, Object>>> datasMap = new HashMap<>();
datasMap.put(MDATA_KEY, datas);
datasMap.put(REFKEY_LIST, datas);
return datasMap;
}

View file

@ -44,7 +44,7 @@ public abstract class BaseFeedsService extends ObservableService {
@Override
public Record create(Record record) {
record = super.create(converContent(record));
record = super.create(converContent4Mentions(record));
awareMention(record, true);
return record;
@ -52,7 +52,7 @@ public abstract class BaseFeedsService extends ObservableService {
@Override
public Record update(Record record) {
record = super.update(converContent((record)));
record = super.update(converContent4Mentions((record)));
awareMention(record, false);
return record;
@ -174,7 +174,7 @@ public abstract class BaseFeedsService extends ObservableService {
* @param record
* @return
*/
private Record converContent(Record record) {
private Record converContent4Mentions(Record record) {
String content = record.getString("content");
if (StringUtils.isBlank(content)) return record;

View file

@ -14,10 +14,13 @@ import com.googlecode.aviator.exception.ExpressionSyntaxErrorException;
import com.googlecode.aviator.lexer.token.OperatorType;
import com.googlecode.aviator.runtime.function.system.AssertFunction;
import com.googlecode.aviator.runtime.type.AviatorFunction;
import com.googlecode.aviator.runtime.type.Sequence;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
/**
@ -138,4 +141,16 @@ public class AviatorUtils {
public static AviatorEvaluatorInstance getInstance() {
return AVIATOR;
}
/**
* @param value
* @return
*/
@SuppressWarnings("unchecked")
public static Iterator<Object> toIterator(Object value) {
if (value instanceof Collection) return ((Collection<Object>) value).iterator();
if (value instanceof Sequence) return ((Sequence<Object>) value).iterator();
throw new UnsupportedOperationException("Unsupport type : " + value);
}
}

View file

@ -15,6 +15,7 @@ import com.googlecode.aviator.runtime.type.AviatorJavaType;
import com.googlecode.aviator.runtime.type.AviatorNil;
import com.googlecode.aviator.runtime.type.AviatorObject;
import com.googlecode.aviator.runtime.type.AviatorString;
import com.googlecode.aviator.runtime.type.Sequence;
import com.rebuild.core.Application;
import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.support.general.FieldValueHelper;
@ -24,11 +25,12 @@ import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Usage: TEXT($id|$id[], [$defaultValue], [$separator], [$fieldName])
* Usage: TEXT($id|$id[], [$defaultValue], [$separator], [$labelFieldName])
* Return: String
*
* @author RB
@ -78,16 +80,14 @@ public class TextFunction extends AbstractFunction {
// 多引用 ID[]
Object idArray = $id;
if ($id instanceof Collection) {
List<ID> list = null;
for (Object o : (Collection<?>) $id) {
if (o instanceof ID) {
if (list == null) list = new ArrayList<>();
list.add((ID) o);
}
if (idArray instanceof Collection || idArray instanceof Sequence) {
Iterator<?> iter = AviatorUtils.toIterator(idArray);
List<ID> list = new ArrayList<>();
while (iter.hasNext()) {
Object o = iter.next();
if (o instanceof ID) list.add((ID) o);
}
if (list != null) idArray = list.toArray(new ID[0]);
if (!list.isEmpty()) idArray = list.toArray(new ID[0]);
}
if (idArray instanceof ID[]) {

View file

@ -218,7 +218,7 @@ public class AggregationEvaluator {
Map<Object, Integer> countList = null;
if (mode == 2 || mode == 3) {
nvList = new LinkedHashSet<>();
if (mode == 3) countList = new HashMap<>(); // 针对文本
if (mode == 3) countList = new HashMap<>(); // 仅文本有效 *N
} else {
nvList = new ArrayList<>();
}

View file

@ -11,6 +11,7 @@ import com.rebuild.core.Application;
import com.rebuild.core.BootEnvironmentPostProcessor;
import com.rebuild.core.RebuildException;
import com.rebuild.core.service.PerHourJob;
import com.rebuild.utils.CommonsUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.BooleanUtils;
@ -40,9 +41,7 @@ public class RebuildConfiguration extends KVStorage {
* @return
*/
public static File getFileOfData(String filepath) {
if (filepath != null && filepath.contains("../")) {
throw new SecurityException("Attack path detected : " + filepath);
}
CommonsUtils.checkFilePathAttack(filepath);
String d = get(ConfigurationItem.DataDirectory);
File datadir = null;
@ -77,9 +76,7 @@ public class RebuildConfiguration extends KVStorage {
* @see PerHourJob#doCleanTempFiles()
*/
public static File getFileOfTemp(String filepath) {
if (filepath != null && filepath.contains("../")) {
throw new SecurityException("Attack path detected : " + filepath);
}
CommonsUtils.checkFilePathAttack(filepath);
File temp = getFileOfData("temp");
if (!temp.exists()) {

View file

@ -395,29 +395,29 @@ public class QiniuCloud {
/**
* 读取文件
*
* @param filePath
* @param filepath
* @return
* @throws IOException
* @throws RebuildException If cannot read/download
*/
public static File getStorageFile(String filePath) throws IOException, RebuildException {
File file;
if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
String name = filePath.split("\\?")[0];
public static File getStorageFile(String filepath) throws IOException, RebuildException {
File file = null;
if (filepath.startsWith("http://") || filepath.startsWith("https://")) {
String name = filepath.split("\\?")[0];
name = name.substring(name.lastIndexOf("/") + 1);
file = RebuildConfiguration.getFileOfTemp("down" + System.nanoTime() + "." + name);
OkHttpUtils.readBinary(filePath, file, null);
OkHttpUtils.readBinary(filepath, file, null);
} else if (QiniuCloud.instance().available()) {
String name = parseFileName(filePath);
String name = parseFileName(filepath);
file = RebuildConfiguration.getFileOfTemp("down" + System.nanoTime() + "." + name);
instance().download(filePath, file);
instance().download(filepath, file);
} else {
file = RebuildConfiguration.getFileOfData(filePath);
} else if (filepath.startsWith("rb/") || filepath.startsWith("/rb/")) {
file = RebuildConfiguration.getFileOfData(filepath);
}
if (!file.exists()) throw new RebuildException("Cannot read file : " + filePath);
if (file == null || !file.exists()) throw new RebuildException("Cannot read file : " + filepath);
return file;
}
}

View file

@ -299,4 +299,15 @@ public class CommonsUtils {
int rnd = RandomUtils.nextInt(e);
return rnd < s ? rnd + s : rnd;
}
/**
* @param filepath
* @throws SecurityException
*/
public static void checkFilePathAttack(String filepath) throws SecurityException {
if (filepath == null) return;
if (filepath.contains("../") || filepath.contains("<") || filepath.contains(">")) {
throw new SecurityException("Attack path detected : " + filepath);
}
}
}

View file

@ -216,6 +216,7 @@ public abstract class BaseController extends Controller {
* @return
*/
protected ModelAndView createModelAndView(String view) {
view = view.replaceAll("[^a-zA-Z0-9_/\\-]", "");
return new ModelAndView(view);
}

View file

@ -21,6 +21,7 @@ import com.rebuild.core.support.RebuildConfiguration;
import com.rebuild.core.support.i18n.Language;
import com.rebuild.core.support.integration.QiniuCloud;
import com.rebuild.utils.AppUtils;
import com.rebuild.utils.CommonsUtils;
import com.rebuild.utils.MarkdownUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException;
@ -203,10 +204,10 @@ public class RebuildWebConfigurer implements WebMvcConfigurer, ErrorViewResolver
if (StringUtils.isBlank(errorMsg)) errorMsg = Language.L("系统繁忙,请稍后重试");
error.getModel().put("error_code", errorCode);
error.getModel().put("error_msg", errorMsg);
error.getModel().put("error_msg", CommonsUtils.escapeHtml(errorMsg));
if (ex != null && Application.devMode()) {
error.getModel().put("error_stack", ThrowableUtils.extractStackTrace(ex));
error.getModel().put("error_stack", CommonsUtils.escapeHtml(ThrowableUtils.extractStackTrace(ex)));
}
return error;

View file

@ -13,6 +13,7 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.Application;
import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.metadata.easymeta.EasyField;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
@ -87,6 +88,7 @@ public class RevisionHistoryController extends EntityController {
// 补充字段名称
private void paddingFieldsName(JSONArray contents, Entity entity) {
final int entityCode = entity.getEntityCode();
for (Iterator<Object> iter = contents.iterator(); iter.hasNext(); ) {
JSONObject item = (JSONObject) iter.next();
String fieldName = item.getString("field");
@ -95,8 +97,12 @@ public class RevisionHistoryController extends EntityController {
EasyField easyField = EasyMetaFactory.valueOf(entity.getField(fieldName));
// 排除不可查询字段
if (!easyField.isQueryable()) {
iter.remove();
continue;
if (fieldName.equalsIgnoreCase("contentMore") && entityCode == EntityHelper.Feeds) {
// 保留
} else {
iter.remove();
continue;
}
}
fieldName = easyField.getLabel();

View file

@ -268,8 +268,8 @@ public class FileDownloader extends BaseController {
filepath = CodecUtils.urlDecode(filepath);
filepath = filepath.replace("\\", "/");
if (filepath.contains("../")
|| filepath.startsWith("_log/") || filepath.contains("/_log/")
CommonsUtils.checkFilePathAttack(filepath);
if (filepath.startsWith("_log/") || filepath.contains("/_log/")
|| filepath.startsWith("_backups/") || filepath.contains("/_backups/")) {
throw new SecurityException("Attack path detected : " + filepath);
}

View file

@ -159,7 +159,7 @@ public class GeneralModelController extends EntityController {
trans.put("transName", cb.getString("name"));
detailImports.add(trans);
}
((JSONObject) model).put("detailImports", detailImports);
}
}

View file

@ -427,7 +427,7 @@
<field name="feedsId" type="primary"/>
<field name="type" type="small-int" nullable="false" updatable="false" default-value="1" description="类型"/>
<field name="content" type="text" nullable="false" description="内容"/>
<field name="contentMore" type="text" description="扩展内容 (JSON Map)" queryable="false"/>
<field name="contentMore" type="text" description="附加内容" queryable="false"/>
<field name="images" type="string" max-length="700" description="图片" extra-attrs="{displayType:'IMAGE'}"/>
<field name="attachments" type="string" max-length="700" description="附件" extra-attrs="{displayType:'FILE'}"/>
<field name="relatedRecord" type="any-reference" description="相关记录" cascade="ignore"/>

View file

@ -11,15 +11,21 @@
.mdi-monitor::before {
margin-left: -1px;
}
.badge.badge-info {
color: #555;
}
.badge.badge-info.excel {
background-color: #0f9d58;
background-color: #caead8;
}
.badge.badge-info.word {
background-color: #307cf1;
background-color: #d2e0f4;
}
.badge.badge-info.html5 {
background-color: #f16529;
background-color: #e44d26;
background-color: #f6dbd4;
}
</style>
</head>

View file

@ -122,7 +122,7 @@
</tbody>
</table>
<div th:if="${smsAccount == null}" class="alert alert-danger alert-icon mt-6 mb-6">
<div class="icon"><span class="zmdi zmdi-close-circle-o"></span></div>
<div class="icon"><span class="mdi mdi-message-alert-outline"></span></div>
<div class="message">[[${bundle.L('短信服务未配置,相关功能不可用')}]]</div>
</div>
<div class="edit-footer">

View file

@ -107,12 +107,14 @@
<span class="custom-control-label">[[${bundle.L('禁用详情页单字段编辑')}]] <i class="support-plat2 mdi mdi-monitor" th:title="${bundle.L('支持 PC')}"></i></span>
</label>
</div>
<div class="mt-2">
<label class="custom-control custom-control-sm custom-checkbox custom-control-inline">
<input class="custom-control-input" type="checkbox" id="enableRecordMerger" />
<span class="custom-control-label">[[${bundle.L('启用记录合并功能')}]] <i class="support-plat2 mdi mdi-monitor" th:title="${bundle.L('支持 PC')}"></i></span>
</label>
</div>
<th:block th:if="${currentEntity == mainEntity || detailEntity == null}">
<div class="mt-2">
<label class="custom-control custom-control-sm custom-checkbox custom-control-inline">
<input class="custom-control-input" type="checkbox" id="enableRecordMerger" />
<span class="custom-control-label">[[${bundle.L('启用记录合并功能')}]] <i class="support-plat2 mdi mdi-monitor" th:title="${bundle.L('支持 PC')}"></i></span>
</label>
</div>
</th:block>
<div class="mt-2">
<label class="custom-control custom-control-sm custom-checkbox custom-control-inline">
<input class="custom-control-input" type="checkbox" id="repeatFieldsCheckMode" />

View file

@ -298,8 +298,8 @@ a.ui-draggable.ui-draggable-dragging {
.data-info ul li i.icon,
.data-info ul.fields li a::before {
width: 18px;
display: inline-block;
width: 15px;
color: #4285f4;
}

View file

@ -79,9 +79,11 @@ code {
.dropdown-menu {
box-shadow: 0 3px 0.3077rem rgba(0, 0, 0, 0.1);
box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2);
border-width: 0;
border-radius: 0;
max-width: 300px;
user-select: none;
}
.dropdown-menu .dropdown-item,
@ -3203,7 +3205,7 @@ form {
border-radius: 2px;
padding: 15px;
background-color: rgba(230, 230, 230, 0.3);
box-shadow: inset 0px 0px 10px rgba(230, 230, 230, 0.6);
box-shadow: inset 0 0 10px rgba(230, 230, 230, 0.6);
}
.form.approval-form .approval-list {
@ -5483,8 +5485,8 @@ span.icon-append .icon {
text-align: center;
}
.media-capture.md video,
.media-capture.md canvas {
.media-capture.media-capture-md video,
.media-capture.media-capture-md canvas {
width: 768px;
height: 576px;
}

View file

@ -420,19 +420,28 @@ textarea.formula-code + .fields-vars {
color: #edebe6;
}
.dropdown-item > em,
.fields ul li.flag-DECIMAL > a::before,
.fields ul li.flag-NUMBER > a::before,
.fields ul li.flag-DATE > a::before,
.fields ul li.flag-DATETIME > a::before {
content: 'N';
.fields ul li.flag-DATETIME > a::before,
.fields ul li.flag-TIME > a::before {
content: '#';
width: 15px;
display: inline-block;
color: #4285f4;
font-size: 0.9rem;
font-style: normal;
}
.fields ul li.flag-DECIMAL > a::before,
.fields ul li.flag-NUMBER > a::before {
content: 'N';
}
.fields ul li.flag-DATE > a::before,
.fields ul li.flag-DATETIME > a::before {
.fields ul li.flag-DATETIME > a::before,
.fields ul li.flag-TIME > a::before{
content: 'D';
}

View file

@ -203,6 +203,8 @@ class RbViewModal extends React.Component {
* @param {Boolean} subView
*/
static create(props, subView) {
if (props.id) props.id = props.id.toLowerCase()
this.__HOLDERs = this.__HOLDERs || {}
this.__HOLDERsStack = this.__HOLDERsStack || []
const that = this

View file

@ -1590,7 +1590,7 @@ class RbFormImage extends RbFormElement {
// NOOP
} else {
if (!this._fieldValue__input) {
console.warn('No element `_fieldValue__input` defined')
console.log('No element `_fieldValue__input` defined :', this.props.field)
return
}

View file

@ -5,7 +5,6 @@ rebuild is dual-licensed under commercial and open source licenses (GPLv3).
See LICENSE and COMMERCIAL in the project root for license information.
*/
// css width
const _VIDEO_WIDTH = 1000
// eslint-disable-next-line no-unused-vars
@ -30,7 +29,7 @@ class MediaCapturer extends RbModal {
<canvas ref={(c) => (this._$resImage = c)} className={this.state.recType === 'image' ? '' : 'hide'}></canvas>
</div>
<div className="action" ref={(c) => (this._$btn = c)}>
<div className={`action ${this.state.unsupportted && 'hide'}`} ref={(c) => (this._$btn = c)}>
<input type="file" className="hide" ref={(c) => (this._$fileinput = c)} />
<button className="btn btn-primary J_used" type="button" onClick={() => this.handleConfirm()}>
<i className="icon mdi mdi-check" /> {$L('使用')}
@ -74,26 +73,27 @@ class MediaCapturer extends RbModal {
componentDidMount() {
super.componentDidMount()
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
this.initDevice(null, $storage.get('MediaCapturerDeviceId'))
navigator.mediaDevices
.enumerateDevices()
.then((devices) => {
const devices2 = devices.filter((device) => device.kind === 'videoinput')
const devices3 = []
devices2.forEach((device, idx) => {
devices3.push([device.deviceId, device.label || idx])
})
this.setState({ webcamList: devices3 })
})
.catch((err) => {
console.log(err)
})
} else {
this.setState({ initMsg: $L('你的浏览器不支持此功能') })
if (!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) {
this.setState({ initMsg: $L('你的浏览器不支持此功能'), unsupportted: true })
return
}
this.initDevice(null, $storage.get('MediaCapturerDeviceId'))
navigator.mediaDevices
.enumerateDevices()
.then((devices) => {
const devices2 = devices.filter((device) => device.kind === 'videoinput')
const devices3 = []
devices2.forEach((device, idx) => {
devices3.push([device.deviceId, device.label || idx])
})
this.setState({ webcamList: devices3 })
})
.catch((err) => {
console.log(err)
})
if (this.props.forceFile) {
$initUploader(
this._$fileinput,
@ -129,7 +129,8 @@ class MediaCapturer extends RbModal {
return
}
const ps = { video: true }
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
const ps = { video: true, audio: this.props.recordAudio || false }
if (deviceId) ps.video = { deviceId: deviceId }
navigator.mediaDevices
.getUserMedia(ps)
@ -143,8 +144,7 @@ class MediaCapturer extends RbModal {
})
this._mediaRecorder.addEventListener('stop', () => {
this._capturedData = new Blob(this._blobs, { type: 'video/mp4' })
const videoBlobURL = URL.createObjectURL(this._capturedData)
this._$resVideo.src = videoBlobURL
this._$resVideo.src = URL.createObjectURL(this._capturedData)
this.setState({ captured: true, initMsg: null })
})
}

View file

@ -6,8 +6,8 @@ See LICENSE and COMMERCIAL in the project root for license information.
*/
/* eslint-disable no-undef */
const isMulti = ['MULTISELECT'].includes($urlp('type'))
const maxOptions = 100
const isMulti = 'MULTISELECT' === $urlp('type')
const maxOptions = isMulti ? 32 : 200
$(document).ready(() => {
const query = `entity=${$urlp('entity')}&field=${$urlp('field')}`

View file

@ -139,6 +139,9 @@ $(function () {
typeof window.bosskeyTrigger === 'function' && window.bosskeyTrigger()
window.__BOSSKEY = true
}
setTimeout(function () {
--bosskey
}, 50000) // clean
}
})

View file

@ -497,6 +497,9 @@ class EditorWithFieldVars extends React.Component {
</a>
<div className="dropdown-menu auto-scroller dropdown-menu-right" ref={(c) => (this._$fieldVars = c)}>
{(this.state.fieldVars || []).map((item) => {
let typeMark = 'T'
if (['DATE', 'DATETIME', 'TIME'].includes(item.type)) typeMark = 'D'
else if (['NUMBER', 'DECIMAL'].includes(item.type)) typeMark = 'N'
return (
<a
className="dropdown-item"
@ -504,6 +507,7 @@ class EditorWithFieldVars extends React.Component {
onClick={() => {
$(this._$content).insertAtCursor(`{${item.name}}`)
}}>
<em>{typeMark}</em>
{item.label}
</a>
)

View file

@ -10,7 +10,7 @@ const CALC_MODES2 = {
...FormulaAggregation.CALC_MODES,
RBJOIN: $L('连接'),
RBJOIN2: $L('去重连接'),
// RBJOIN3: $L('去重连接*N'),
RBJOIN3: $L('去重连接*N'),
}
const __LAB_MATCHFIELDS = false
@ -288,7 +288,7 @@ class ContentFieldAggregation extends ActionContentSpec {
if ('RBJOIN' === cm || 'RBJOIN2' === cm || 'RBJOIN3' === cm) {
tfAllow = this.__targetFieldsCache.filter((x) => {
if ('NTEXT' === x.type) return true
if ('N2NREFERENCE' === x.type) return x.ref[0] === sf.ref[0]
if ('N2NREFERENCE' === x.type) return sf.ref && sf.ref[0] === x.ref[0]
if ('FILE' === x.type) return true
return false
})

View file

@ -10,7 +10,7 @@ const CALC_MODES2 = {
...FormulaAggregation.CALC_MODES,
RBJOIN: $L('连接'),
RBJOIN2: $L('去重连接'),
// RBJOIN3: $L('去重连接*N'),
RBJOIN3: $L('去重连接*N'),
}
// ~~ 分组聚合
@ -380,7 +380,7 @@ class ContentGroupAggregation extends ActionContentSpec {
if ('RBJOIN' === cm || 'RBJOIN2' === cm || 'RBJOIN3' === cm) {
tfAllow = this.__targetFieldsCache.filter((x) => {
if ('NTEXT' === x.type) return true
if ('N2NREFERENCE' === x.type) return x.ref[0] === sf.ref[0]
if ('N2NREFERENCE' === x.type) return sf.ref && sf.ref[0] === x.ref[0]
if ('FILE' === x.type) return true
return false
})

View file

@ -17,7 +17,7 @@
<ul class="list-unstyled esource">
<li>
<span class="text-bold">
<i class="zmdi icon" th:classappend="|zmdi-${entityIcon}|"></i>
<i class="zmdi icon fs-16 down-1" th:classappend="|zmdi-${entityIcon}|"></i>
[[${entityLabel}]]
</span>
</li>