form.class.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. <?php
  2. /**
  3. * The form class file of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
  6. * @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Lu Fei <lufei@easycorp.ltd>
  8. * @package form
  9. * @link https://www.zentao.net
  10. */
  11. helper::import(dirname(dirname(__FILE__)) . '/filter/filter.class.php');
  12. class form extends fixer
  13. {
  14. /**
  15. * 批量处理的数据。
  16. * The data to be fixed.
  17. *
  18. * @var object
  19. * @access public
  20. */
  21. public $dataList;
  22. /**
  23. * 原始POST数据。
  24. * The raw post data.
  25. *
  26. * @var object
  27. * @access public
  28. */
  29. public $rawdata;
  30. /**
  31. * 类型。single|batch
  32. * Type. single|batch
  33. *
  34. * @var string
  35. */
  36. protected $formType = 'single';
  37. /**
  38. * 原始配置。
  39. * The raw cofig.
  40. *
  41. * @var array
  42. */
  43. protected $rawconfig;
  44. /**
  45. * 错误信息列表。
  46. * Error list.
  47. *
  48. * @var array
  49. */
  50. public $errors;
  51. /**
  52. * 构造方法。
  53. * The construct function.
  54. *
  55. * @return void
  56. */
  57. public function __construct()
  58. {
  59. $this->rawdata = (object)$_POST;
  60. $this->data = (object)array();
  61. $this->errors = array();
  62. }
  63. /**
  64. * 获取表单数据。
  65. * Get the form data.
  66. *
  67. * @param array|null $configObject
  68. * @param int $objectID
  69. * @param int $flowGroupID
  70. * @return form
  71. */
  72. public static function data($configObject = null, $objectID = 0, $flowGroupID = 0)
  73. {
  74. global $app, $config;
  75. $form = new form;
  76. if($configObject === null) $configObject = $config->{$app->moduleName}->form->{$app->methodName};
  77. $configObject = $form->appendExtendFormConfig($configObject, '', '', $objectID, $flowGroupID);
  78. return $form->config($configObject);
  79. }
  80. /**
  81. * 获取批量表单数据。
  82. * Get the batch form data.
  83. *
  84. * @param array|null $configObject
  85. * @return form
  86. */
  87. public static function batchData($configObject = null)
  88. {
  89. global $app, $config;
  90. $form = new form;
  91. if($configObject === null) $configObject = $config->{$app->moduleName}->form->{$app->methodName};
  92. $configObject = $form->appendExtendFormConfig($configObject);
  93. return $form->config($configObject, 'batch');
  94. }
  95. /**
  96. * 追加工作流新增的字段到表单提交配置。
  97. * append workflow form config.
  98. *
  99. * @param array $config
  100. * @param string $moduleName
  101. * @param string $methodName
  102. * @param int $objectID
  103. * @param int $flowGroupID
  104. * @access public
  105. * @return array
  106. * @param mixed[] $configObject
  107. */
  108. public function appendExtendFormConfig($configObject, $moduleName = '', $methodName = '', $objectID = 0, $flowGroupID = 0)
  109. {
  110. global $app, $config;
  111. if($config->edition == 'open' || !empty($app->installing)) return $configObject;
  112. $moduleName = $moduleName ? $moduleName : $app->rawModule;
  113. $methodName = $methodName ? $moduleName : $app->rawMethod;
  114. /* 项目发布和项目版本用自己的工作流。 */
  115. if($moduleName == 'projectrelease') $moduleName = 'release';
  116. if($moduleName == 'projectbuild') $moduleName = 'build';
  117. if($moduleName == 'projectplan') $moduleName = 'productplan';
  118. /* 项目复制用项目创建的工作流。 */
  119. if($moduleName == 'project' && $methodName == 'copyconfirm') $methodName = 'create';
  120. if($moduleName == 'project' && $methodName == 'edittemplate') $methodName = 'edit';
  121. /* 用户需求和业务需求用自己的工作流。*/
  122. if($moduleName == 'story' && $app->rawModule == 'requirement') $moduleName = 'requirement';
  123. if($moduleName == 'story' && $app->rawModule == 'epic') $moduleName = 'epic';
  124. /* 复制项目使用项目创建的工作流。 */
  125. if($moduleName == 'project' && $methodName == 'copyconfirm') $methodName = 'create';
  126. if(empty($app->control)) return $configObject;
  127. $app->control->loadModel('workflowgroup');
  128. if($flowGroupID)
  129. {
  130. $group = $app->control->workflowgroup->fetchByID($flowGroupID);
  131. $groupID = (empty($group) || $group->main) ? 0 : $flowGroupID;
  132. }
  133. $groupID = isset($groupID) ? $groupID : $app->control->workflowgroup->getGroupIDByDataID($moduleName, $objectID);
  134. $flow = $app->control->loadModel('workflow')->getByModule($moduleName, false, $groupID);
  135. if(!$flow) return $configObject;
  136. $action = $app->control->loadModel('workflowaction')->getByModuleAndAction($flow->module, $methodName, $flow->group);
  137. if(!$action || $action->extensionType != 'extend') return $configObject;
  138. $uiID = $app->control->loadModel('workflowlayout')->getUIByDataID($flow->module, $action->action, $objectID);
  139. $fieldList = $app->control->workflowaction->getPageFields($flow->module, $action->action, true, null, $uiID, $flow->group);
  140. if($action->open != 'none')
  141. {
  142. /* Reset the show property of a field according by the linkages. */
  143. /* 根据界面联动设置重置字段的是否显示属性。*/
  144. $linkages = zget($action->linkages, $uiID, array());
  145. foreach($linkages as $linkage)
  146. {
  147. if(empty($linkage)) continue;
  148. $sources = zget($linkage, 'sources', array());
  149. $targets = zget($linkage, 'targets', array());
  150. if(empty($sources) || empty($targets)) continue;
  151. foreach($sources as $source)
  152. {
  153. if(!isset($fieldList[$source->field])) continue;
  154. if(!isset($this->rawdata->{$source->field})) continue;
  155. $dataValue = $this->rawdata->{$source->field};
  156. $condition = false;
  157. if($source->operator == '==') $condition = $dataValue == $source->value;
  158. if($source->operator == '!=') $condition = $dataValue != $source->value;
  159. if($source->operator == '>') $condition = $dataValue > $source->value;
  160. if($source->operator == '>=') $condition = $dataValue >= $source->value;
  161. if($source->operator == '<') $condition = $dataValue < $source->value;
  162. if($source->operator == '<=') $condition = $dataValue <= $source->value;
  163. foreach($targets as $target)
  164. {
  165. if(!isset($fieldList[$target->field])) continue;
  166. $targetField = $fieldList[$target->field];
  167. if($condition)
  168. {
  169. if($target->status == 'show') $targetField->show = 1;
  170. if($target->status == 'hide') $targetField->show = 0;
  171. }
  172. $fieldList[$target->field] = $targetField;
  173. }
  174. }
  175. }
  176. }
  177. $layouts = $app->control->workflowlayout->getFields($moduleName, $methodName, $uiID, $flow->group);
  178. $notEmptyRule = $app->control->loadModel('workflowrule')->getByTypeAndRule('system', 'notempty');
  179. if($layouts)
  180. {
  181. foreach($fieldList as $key => $field)
  182. {
  183. if($field->buildin || !$field->show || !isset($layouts[$field->field])) continue;
  184. if($field->control == 'file') continue;
  185. $required = !$field->readonly && $notEmptyRule && strpos(",$field->rules,", ",{$notEmptyRule->id},") !== false;
  186. if($field->control == 'multi-select' || $field->control == 'checkbox')
  187. {
  188. $configObject[$field->field] = array('required' => $required, 'type' => 'array', 'default' => array(''), 'filter' => 'join');
  189. }
  190. elseif($field->control == 'date' || $field->control == 'datetime')
  191. {
  192. $configObject[$field->field] = array('required' => $required, 'type' => $field->control, 'default' => null);
  193. }
  194. else
  195. {
  196. $type = 'string';
  197. if($field->type == 'int') $type = 'int';
  198. if($field->type == 'decimal') $type = 'float';
  199. $configObject[$field->field] = array('required' => $required, 'type' => $type, 'default' => $field->type == 'int' || $field->type == 'decimal' ? 0 : '');
  200. if($field->control == 'richtext') $configObject[$field->field]['control'] = 'editor';
  201. }
  202. }
  203. }
  204. return $configObject;
  205. }
  206. /**
  207. * 设置表单配置项。
  208. * Set form configuration.
  209. *
  210. * @param array $config
  211. * @param string $type single|batch
  212. * @return $this
  213. * @throws EndResponseException
  214. */
  215. public function config($config, $type = 'single')
  216. {
  217. $config = $this->processRequiredFields($config);
  218. $this->rawconfig = $config;
  219. $this->formType = $type;
  220. if($type == 'single')
  221. {
  222. foreach($this->rawconfig as $field => $fieldConfig)
  223. {
  224. if(isset($fieldConfig['control']) && in_array($fieldConfig['control'], array('textarea', 'richtext'))) $this->skipSpecial($field);
  225. $this->convertField($field, $fieldConfig);
  226. }
  227. }
  228. else
  229. {
  230. $this->batchConvertField($config);
  231. $this->skipRequiredCheck($config);
  232. }
  233. if(!empty($this->errors))
  234. {
  235. $response = array('result' => 'fail', 'message' => $this->errors);
  236. helper::end(json_encode($response, JSON_UNESCAPED_UNICODE));
  237. }
  238. return $this;
  239. }
  240. /**
  241. * 根据必填字段更新表单配置项中的必填属性。
  242. * Update the required attribute of the form configuration according to the required fields.
  243. *
  244. * @param array $configObject
  245. * @access private
  246. * @return array
  247. */
  248. private function processRequiredFields($configObject)
  249. {
  250. global $app, $config, $lang;
  251. $module = $app->getModuleName();
  252. $method = $app->getMethodName();
  253. if(in_array($app->rawMethod, array('requirement', 'torequirement')) && $module == 'story') $module = 'requirement';
  254. if(in_array($app->rawMethod, array('epic', 'toepic')) && $module == 'story') $module = 'epic';
  255. if($app->rawModule == 'requirement' && $module == 'story') $module = 'requirement';
  256. if($app->rawModule == 'epic' && $module == 'story') $module = 'epic';
  257. if($app->rawModule == 'feedback' && $app->rawMethod == 'touserstory') $module = 'requirement';
  258. if($app->rawModule == 'feedback' && $app->rawMethod == 'toepic') $module = 'epic';
  259. if($app->rawModule == 'projectrelease') $module = 'release';
  260. if($method == 'batchcreate') $method = 'create';
  261. if($method == 'batchedit') $method = 'edit';
  262. if($module == 'project' && $module == 'copyconfirm') $method = 'create';
  263. if(empty($config->$module->$method->requiredFields)) return $configObject;
  264. $requiredFields = array_unique(array_filter(explode(',', $config->$module->$method->requiredFields)));
  265. foreach($requiredFields as $field)
  266. {
  267. $field = trim($field);
  268. if(!isset($configObject[$field])) continue;
  269. if(!isset($configObject[$field]['skipRequired']))
  270. {
  271. $configObject[$field]['required'] = true;
  272. if(empty($lang->{$app->rawModule}->$field)) $lang->{$app->rawModule}->$field = $lang->$module->$field;
  273. }
  274. }
  275. return $configObject;
  276. }
  277. /**
  278. * 批量转换字段类型。
  279. * Batch convert the field type.
  280. *
  281. * @param array $config
  282. * @return void
  283. * @param mixed[] $fieldConfigs
  284. */
  285. public function batchConvertField($fieldConfigs)
  286. {
  287. global $app;
  288. $rowDataList = array();
  289. $baseField = '';
  290. foreach($fieldConfigs as $field => $config)
  291. {
  292. /* 在第一行提示类型未定义。 Display type error in the first row. */
  293. if(!isset($config['type']))
  294. {
  295. if(empty($this->errors)) $this->errors[1] = array();
  296. if(!isset($this->errors[1][$field])) $this->errors[1][$field] = array();
  297. $this->errors[1][$field][] = "Field '{$field}' need defined type";
  298. }
  299. /* 以该字段为标准,判断某一行是否要构造数据。 If the value of the field in a row is empty, skip that row. */
  300. if(!empty($config['base'])) $baseField = $field;
  301. if(isset($config['control']) && in_array($config['control'], array('textarea', 'richtext'))) $this->skipSpecial($field);
  302. }
  303. /* 在第一行提示标准字段不能为空。 Display the field error in the first row. */
  304. if(!isset($this->rawdata->$baseField))
  305. {
  306. if(empty($this->errors)) $this->errors[1] = array();
  307. if(!isset($this->errors[1][$field])) $this->errors[1][$field] = array();
  308. $this->errors[1][$field][] = "Field '{$field}' not empty";
  309. }
  310. /* 构造批量表单数据。Construct batch form data. */
  311. foreach($this->rawdata->$baseField as $rowIndex => $value)
  312. {
  313. if(empty($value)) continue;
  314. $rowData = new stdclass();
  315. foreach($fieldConfigs as $field => $config)
  316. {
  317. $defaultValue = zget($config, 'default', '');
  318. $rowData->$field = isset($this->rawdata->$field) ? zget($this->rawdata->$field, $rowIndex, $defaultValue) : $defaultValue;
  319. $rowData->$field = helper::convertType($rowData->$field, $config['type']);
  320. if(isset($config['filter'])) $rowData->$field = $this->filter($rowData->$field, $config['filter'], zget($config, 'separator', ','));
  321. /* 检查必填字段。Check required fields. */
  322. if(isset($config['required']) && $config['required'] && empty($rowData->$field))
  323. {
  324. if($app->moduleName == 'task' && $app->methodName == 'batchcreate' && $field == 'estimate' && $this->rawdata->isParent[$rowIndex] == '1') continue;
  325. $errorKey = isset($config['type']) && $config['type'] == 'array' ? "{$field}[{$rowIndex}][]" : "{$field}[{$rowIndex}]";
  326. $fieldName = isset($app->lang->{$app->rawModule}->$field) ? $app->lang->{$app->rawModule}->$field : $field;
  327. if(!isset($this->errors[$errorKey])) $this->errors[$errorKey] = array();
  328. $this->errors[$errorKey][] = sprintf($app->lang->error->notempty, $fieldName);
  329. }
  330. }
  331. $rowDataList[$rowIndex] = $rowData;
  332. }
  333. $this->dataList = $rowDataList;
  334. }
  335. /**
  336. * 跳过必填项检查。
  337. * Skip the required check.
  338. *
  339. * @param array $fieldConfigs
  340. * @access public
  341. * @return void
  342. */
  343. public function skipRequiredCheck($fieldConfigs)
  344. {
  345. foreach($this->dataList as $rowIndex => $rowData)
  346. {
  347. foreach($fieldConfigs as $field => $config)
  348. {
  349. if(empty($config['required']) || empty($config['skipRequired']) || !empty($rowData->$field)) continue;
  350. $skip = true;
  351. $errorKey = isset($config['type']) && $config['type'] == 'array' ? "{$field}[{$rowIndex}][]" : "{$field}[{$rowIndex}]";
  352. foreach($config['skipRequired'] as $conditionField => $conditionValue)
  353. {
  354. if($rowData->$conditionField != $conditionValue)
  355. {
  356. $skip = false;
  357. break;
  358. }
  359. }
  360. if($skip) unset($this->errors[$errorKey]);
  361. }
  362. }
  363. }
  364. /**
  365. * 获取$_POST的数据。
  366. * Get the data of $_POST.
  367. *
  368. * @param bool $isRaw 是否获取原始数据。Whether to get the raw data.
  369. * @return object
  370. */
  371. public function getAll($isRaw = false)
  372. {
  373. return $isRaw ? $this->rawdata : $this->data;
  374. }
  375. /**
  376. * 转换字段类型。
  377. * Convert the field type.
  378. *
  379. * @param string $field
  380. * @param array $config
  381. * @return void
  382. */
  383. public function convertField($field, $config)
  384. {
  385. global $app;
  386. if(!isset($config['type']))
  387. {
  388. if(!isset($this->errors[$field])) $this->errors[$field] = array();
  389. $this->errors[$field][] = "Field '{$field}' need defined type";
  390. }
  391. if(isset($this->rawdata->$field)) $data = $this->rawdata->$field;
  392. /* Assign the default value to the data if the default value exists and the data is not exist or null or empty string. */
  393. if(isset($config['default']) && (!isset($this->rawdata->$field) || is_null($this->rawdata->$field) || $this->rawdata->$field === '')) $data = $config['default'];
  394. if(isset($data)) $data = helper::convertType($data, $config['type']);
  395. if(isset($data) && isset($config['filter'])) $data = $this->filter($data, $config['filter'], zget($config, 'separator', ','));
  396. $emptyData = empty($data);
  397. if($config['type'] == 'int' || $config['type'] == 'float' || $config['type'] == 'string')
  398. {
  399. $emptyData = (isset($this->rawdata->$field) && $this->rawdata->$field === '') || (isset($data) && $data === '');
  400. }
  401. if(isset($config['required']) && $config['required'] && isset($this->rawdata->$field) && $emptyData)
  402. {
  403. $rawModule = $app->rawModule == 'feedback' && in_array($app->rawMethod, array('touserstory', 'toepic')) ? 'story' : $app->rawModule;
  404. $errorKey = isset($config['type']) && $config['type'] == 'array' ? "{$field}[]" : $field;
  405. if(isset($config['label']))
  406. {
  407. $fieldName = $config['label'];
  408. }
  409. else
  410. {
  411. $fieldName = isset($app->lang->{$rawModule}->$field) ? $app->lang->{$rawModule}->$field : $field;
  412. }
  413. if(!isset($this->errors[$errorKey])) $this->errors[$errorKey] = array();
  414. $this->errors[$errorKey][] = sprintf($app->lang->error->notempty, $fieldName);
  415. }
  416. $this->data->$field = isset($data) ? $data : null;
  417. }
  418. /**
  419. * Special array.
  420. *
  421. * @param mixed $data
  422. * @access public
  423. * @return mixed
  424. */
  425. public function specialArray($data)
  426. {
  427. if(!is_array($data))
  428. {
  429. if(is_string($data)) return htmlspecialchars($data, ENT_QUOTES);
  430. return $data;
  431. }
  432. foreach($data as &$value) $value = $this->specialArray($value);
  433. return $data;
  434. }
  435. /**
  436. * 过滤表单字段数据。
  437. * Filter the form field data.
  438. *
  439. * @param mixed $value
  440. * @param mixed $filter
  441. * @param string $separator
  442. * @access protected
  443. * @return string
  444. */
  445. protected function filter($value, $filter, $separator = ',')
  446. {
  447. switch($filter)
  448. {
  449. case 'trim':
  450. return trim($value);
  451. case 'join':
  452. return implode($separator, $value);
  453. default:
  454. return $value;
  455. }
  456. }
  457. /**
  458. * 过滤富文字段数据。
  459. * Filter the editor fields.
  460. *
  461. * @param string $fields
  462. * @access public
  463. * @return mixed
  464. */
  465. public function get($fields = '')
  466. {
  467. global $config;
  468. if($this->formType == 'single') return parent::get($fields);
  469. foreach($this->dataList as $rowIndex => $data)
  470. {
  471. $this->data = $data;
  472. $this->dataList[$rowIndex] = parent::get($fields);
  473. $this->stripedFields = array();
  474. }
  475. return $this->dataList;
  476. }
  477. }