model.class.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. <?php /**
  2. * ZenTaoPHP的model类。
  3. * The model class file of ZenTaoPHP framework.
  4. *
  5. * The author disclaims copyright to this source code. In place of
  6. * a legal notice, here is a blessing:
  7. *
  8. * May you do good and not evil.
  9. * May you find forgiveness for yourself and forgive others.
  10. * May you share freely, never taking more than you give.
  11. */
  12. /**
  13. * model基类。
  14. * The base class of model.
  15. *
  16. * @package framework
  17. */
  18. include __DIR__ . '/base/model.class.php';
  19. class model extends baseModel
  20. {
  21. /**
  22. * 企业版部分功能是从然之合并过来的。ZDOO代码中调用loadModel方法时传递了一个非空的appName,在禅道中会导致错误。
  23. * 调用父类的loadModel方法来避免这个错误。
  24. * Some codes merged from ZDOO called the function loadModel with a non-empty appName which causes an error in zentao.
  25. * Call the parent function with empty appName to avoid this error.
  26. *
  27. * @param string $moduleName 模块名,如果为空,使用当前模块。The module name, if empty, use current module's name.
  28. * @param string $appName 应用名,如果为空,使用当前应用。The app name, if empty, use current app's name.
  29. * @access public
  30. * @return object|bool the model object or false if model file not exists.
  31. */
  32. public function loadModel($moduleName, $appName = '')
  33. {
  34. return parent::loadModel($moduleName);
  35. }
  36. /**
  37. * 企业版部分功能是从然之合并过来的。ZDOO代码中调用loadTao方法时传递了一个非空的appName,在禅道中会导致错误。
  38. * 调用父类的loadTao方法来避免这个错误。
  39. * Some codes merged from ZDOO called the function loadTao with a non-empty appName which causes an error in zentao.
  40. * Call the parent function with empty appName to avoid this error.
  41. *
  42. * @param string $moduleName 模块名,如果为空,使用当前模块。The module name, if empty, use current module's name.
  43. * @param string $appName 应用名,如果为空,使用当前应用。The app name, if empty, use current app's name.
  44. * @access public
  45. * @return object|bool the model object or false if model file not exists.
  46. */
  47. public function loadTao($moduleName, $appName = '')
  48. {
  49. return parent::loadTao($moduleName);
  50. }
  51. /**
  52. * Load dao of bi.
  53. *
  54. * @access public
  55. * @return void
  56. */
  57. public function loadBIDAO()
  58. {
  59. global $config, $biDAO;
  60. if(is_object($biDAO)) return $this->dao = $biDAO;
  61. if(!isset($config->biDB)) return;
  62. $driver = $config->db->driver;
  63. $biDAO = new $driver();
  64. $biDAO->slaveDBH = $this->app->connectByPDO($config->biDB, 'BI');
  65. $this->dao = $biDAO;
  66. }
  67. /**
  68. * 通过对象ID获取对象信息。
  69. * Get object information by ID.
  70. *
  71. * @param int $objectID
  72. * @param string $moduleName
  73. * @access public
  74. * @return object|bool
  75. */
  76. public function fetchByID($objectID, $moduleName = '')
  77. {
  78. if(empty($objectID)) return false;
  79. if(empty($moduleName)) $moduleName = $this->getModuleName();
  80. $table = zget($this->config->objectTables, $moduleName, '');
  81. if(empty($table)) return false;
  82. return $this->mao->findById($objectID)->from($table)->fetch();
  83. }
  84. /**
  85. * 删除记录
  86. * Delete one record.
  87. *
  88. * @param string $table the table name
  89. * @param int $id the id value of the record to be deleted
  90. * @access public
  91. * @return bool
  92. */
  93. public function delete($table, $id)
  94. {
  95. if(empty($id)) return false;
  96. $this->dao->update($table)->set('deleted')->eq(1)->where('id')->eq($id)->exec();
  97. $objectType = preg_replace('/^' . preg_quote((string) $this->config->db->prefix) . '/', '', trim($table, '`'));
  98. $this->loadModel('action')->create($objectType, $id, 'deleted', '', ACTIONMODEL::CAN_UNDELETED);
  99. return true;
  100. }
  101. /**
  102. * 回滚数据库操作。
  103. * Rollback database operation.
  104. *
  105. * @access public
  106. * @return false
  107. */
  108. public function rollback()
  109. {
  110. $this->dao->rollback();
  111. return false;
  112. }
  113. /**
  114. * Build menu of a module.
  115. *
  116. * @param string $moduleName
  117. * @param string $methodName
  118. * @param string $params
  119. * @param object $data
  120. * @param string $type
  121. * @param string $icon
  122. * @param string $target
  123. * @param string $class
  124. * @param bool $onlyBody
  125. * @param string $misc
  126. * @param string $title
  127. * @param bool $returnHtml
  128. * @access public
  129. * @return string
  130. */
  131. public function buildMenu($moduleName, $methodName, $params, $data, $type = 'view', $icon = '', $target = '', $class = '', $onlyBody = false, $misc = '' , $title = '', $returnHtml = true)
  132. {
  133. if(strpos($moduleName, '.') !== false) list($appName, $moduleName) = explode('.', $moduleName);
  134. if(strpos($methodName, '_') !== false && strpos($methodName, '_') > 0) list($module, $method) = explode('_', $methodName);
  135. if(empty($module)) $module = $moduleName;
  136. if(empty($method)) $method = $methodName;
  137. static $actions = array();
  138. if(isset($this->config->bizVersion))
  139. {
  140. if(empty($actions[$moduleName]))
  141. {
  142. $actions[$moduleName] = $this->dao->select('*')->from(TABLE_WORKFLOWACTION)
  143. ->where('module')->eq($moduleName)
  144. ->andWhere('buildin')->eq('1')
  145. ->andWhere('status')->eq('enable')
  146. ->beginIF(!empty($this->config->vision))->andWhere('vision')->eq($this->config->vision)->fi()
  147. ->fetchAll('action', false);
  148. }
  149. }
  150. $enabled = true;
  151. if(!empty($actions) and isset($actions[$moduleName][$methodName]))
  152. {
  153. $action = $actions[$moduleName][$methodName];
  154. if($action->extensionType == 'override') return $this->loadModel('flow')->buildActionMenu($moduleName, $action, $data, $type);
  155. $conditions = json_decode($action->conditions);
  156. if($conditions and $action->extensionType == 'extend')
  157. {
  158. if($icon != 'copy' and $methodName != 'create') $title = $action->name;
  159. if($conditions) $enabled = $this->loadModel('flow')->checkConditions($conditions, $data);
  160. }
  161. else
  162. {
  163. if(method_exists($this, 'isClickable')) $enabled = $this->isClickable($data, $method, $module);
  164. }
  165. }
  166. else
  167. {
  168. if(method_exists($this, 'isClickable')) $enabled = $this->isClickable($data, $method, $module);
  169. }
  170. if(!$returnHtml) return $enabled;
  171. $html = '';
  172. $type = $type == 'browse' ? 'list' : 'button';
  173. $html = common::buildIconButton($module, $method, $params, $data, $type, $icon, $target, $class, $onlyBody, $misc, $title, '', $enabled);
  174. return $html;
  175. }
  176. /**
  177. * Build menu of actions created by workflow action.
  178. *
  179. * @param string $module
  180. * @param int $data
  181. * @param string $type browse | view
  182. * @param string $show direct | dropdownlist
  183. * @access public
  184. * @return string
  185. */
  186. public function buildFlowMenu($module, $data, $type = 'browse', $show = '')
  187. {
  188. if($this->config->edition == 'open') return '';
  189. if(!empty($this->app->installing) || !empty($this->app->upgrading)) return '';
  190. $moduleName = $module;
  191. if(strpos($module, '.') !== false) [$appName, $moduleName] = explode('.', $module);
  192. static $actions;
  193. static $relations;
  194. if(empty($actions))
  195. {
  196. $actions = $this->dao->select('*')->from(TABLE_WORKFLOWACTION)
  197. ->where('module')->eq($moduleName)
  198. ->andWhere('buildin')->eq('0')
  199. ->andWhere('status')->eq('enable')
  200. ->beginIF(!empty($this->config->vision))->andWhere('vision')->eq($this->config->vision)->fi()
  201. ->orderBy('order_asc')
  202. ->fetchAll('', false);
  203. }
  204. if(empty($relations)) $relations = $this->dao->select('next, actions')->from(TABLE_WORKFLOWRELATION)->where('prev')->eq($moduleName)->fetchPairs();
  205. $this->loadModel('flow');
  206. $approvalProgressMenu = '';
  207. if($type == 'view' && !empty($this->config->openedApproval) && commonModel::hasPriv('approval', 'progress'))
  208. {
  209. $flow = $this->loadModel('workflow', 'flow')->getByModule($moduleName);
  210. if(!$flow) return '';
  211. if($flow->approval == 'enabled' && !empty($data->approval))
  212. {
  213. $extraClass = strpos(',testsuite,build,release,productplan,', ",{$moduleName},") !== false ? 'btn-link' : '';
  214. $approvalProgressMenu .= "<div class='divider'></div>";
  215. $approvalProgressMenu .= baseHTML::a(helper::createLink('approval', 'progress', "approvalID={$data->approval}", '', true), $this->lang->flow->approvalProgress, "class='btn {$extraClass} iframe'");
  216. }
  217. }
  218. $menu = '';
  219. if($show)
  220. {
  221. foreach($actions as $action)
  222. {
  223. if(strpos((string) $action->position, $type) === false || $action->show != $show) continue;
  224. $menu .= $this->flow->buildActionMenu($moduleName, $action, $data, $type, $relations);
  225. }
  226. if($approvalProgressMenu) $menu .= $approvalProgressMenu;
  227. }
  228. else
  229. {
  230. $dropdownMenu = '';
  231. foreach($actions as $action)
  232. {
  233. if(strpos((string) $action->position, $type) === false) continue;
  234. if($type == 'view' || $action->show == 'direct') $menu .= $this->flow->buildActionMenu($moduleName, $action, $data, $type, $relations);
  235. if($type == 'browse' && $action->show == 'dropdownlist') $dropdownMenu .= $this->flow->buildActionMenu($moduleName, $action, $data, $type, $relations);
  236. }
  237. if($approvalProgressMenu) $menu .= $approvalProgressMenu;
  238. if($type == 'browse' && $dropdownMenu)
  239. {
  240. $menu .= "<div class='dropdown'><a href='javascript:;' data-toggle='dropdown'>{$this->lang->more}<span class='caret'> </span></a>";
  241. $menu .= "<ul class='dropdown-menu pull-right'>{$dropdownMenu}</ul></div>";
  242. }
  243. }
  244. return $menu;
  245. }
  246. /**
  247. * 缓存用于构造搜索参数的方法和参数。
  248. * Cache the method and arguments used to build search parameters.
  249. *
  250. * @param string $module
  251. * @param string $classMethod 构造搜索参数的类名和方法名,以::分隔。The class name and method name which builds the search params, separated by ::.
  252. * @param array $methodArgs 调用构造搜索参数的方法时传入的参数列表(实参)。 The arguments passed to the method which builds the search params (actual parameters).
  253. * @access public
  254. * @return void
  255. */
  256. public function cacheSearchFunc($module, $classMethod, $methodArgs)
  257. {
  258. list($className, $methodName) = explode('::', $classMethod);
  259. $funcModel = str_replace(['ext', 'Model'], '', $className);
  260. $key = 0;
  261. $funcArgs = [];
  262. $method = new ReflectionMethod($className, $methodName);
  263. $params = $method->getParameters(); // 构造搜索参数的方法的参数列表(形参)。The parameters of the method which builds the search params (formal parameters).
  264. foreach($params as $param)
  265. {
  266. if(isset($methodArgs[$key]))
  267. {
  268. $funcArgs[$param->getName()] = $methodArgs[$key];
  269. }
  270. elseif($param->isDefaultValueAvailable())
  271. {
  272. $funcArgs[$param->getName()] = $param->getDefaultValue();
  273. }
  274. $key++;
  275. }
  276. $this->session->set($module . 'SearchFunc', ['funcModel' => $funcModel, 'funcName' => $methodName, 'funcArgs' => $funcArgs]);
  277. }
  278. /**
  279. * Process status of an object according to its subStatus.
  280. *
  281. * @param string $module product | release | story | project | task | bug | testcase | testtask | feedback
  282. * @param object $record a record of above modules.
  283. * @access public
  284. * @return string
  285. */
  286. public function processStatus($module, $record)
  287. {
  288. if($module == 'story') $module = $record->type;
  289. if($this->config->edition == 'open' or empty($record->subStatus)) return zget($this->lang->$module->statusList, $record->status);
  290. return $this->loadModel('workflowfield')->processSubStatus($module, $record);
  291. }
  292. /**
  293. * Process workflow export data.
  294. *
  295. * @param object $data
  296. * @access public
  297. * @return object
  298. */
  299. public function processExportData($data)
  300. {
  301. if($this->config->edition == 'open') return $data;
  302. if(!empty($this->app->installing) || !empty($this->app->upgrading)) return $data;
  303. return $this->loadModel('workflowfield')->processExportData($data);
  304. }
  305. /**
  306. * Process workflow export options.
  307. *
  308. * @param object $data
  309. * @access public
  310. * @return object
  311. */
  312. public function processExportOptions($data)
  313. {
  314. if($this->config->edition == 'open') return $data;
  315. if(!empty($this->app->installing) || !empty($this->app->upgrading)) return $data;
  316. return $this->loadModel('workflowfield')->processExportOptions($data);
  317. }
  318. /**
  319. * Process workflow import data.
  320. *
  321. * @param object $data
  322. * @access public
  323. * @return object
  324. */
  325. public function processImportData($data)
  326. {
  327. if($this->config->edition == 'open') return $data;
  328. if(!empty($this->app->installing) || !empty($this->app->upgrading)) return $data;
  329. return $this->loadModel('workflowfield')->processImportData($data);
  330. }
  331. /**
  332. * Get flow extend fields.
  333. *
  334. * @param int $objectID
  335. * @access public
  336. * @return array
  337. */
  338. public function getFlowExtendFields($objectID = 0)
  339. {
  340. if($this->config->edition == 'open') return array();
  341. if(!empty($this->app->installing) || !empty($this->app->upgrading)) return array();
  342. return $this->loadModel('flow')->getExtendFields($this->app->rawModule, $this->app->rawMethod, $objectID);
  343. }
  344. /**
  345. * Set workflow export fields.
  346. *
  347. * @access public
  348. * @return string
  349. */
  350. public function getFlowExportFields()
  351. {
  352. if($this->config->edition == 'open') return array();
  353. if(!empty($this->app->installing) || !empty($this->app->upgrading)) return array();
  354. return $this->loadModel('workflowfield')->getExportFields($this->app->rawModule);
  355. }
  356. /**
  357. * Execute Hooks
  358. *
  359. * @param int $objectID
  360. * @access public
  361. * @return string
  362. */
  363. public function executeHooks($objectID)
  364. {
  365. if($this->config->edition == 'open') return '';
  366. if(!empty($this->app->installing) || !empty($this->app->upgrading)) return '';
  367. $moduleName = $this->app->rawModule;
  368. $methodName = $this->app->rawMethod;
  369. $groupID = $this->loadModel('workflowgroup')->getGroupIDByDataID($moduleName, $objectID);
  370. $action = $this->loadModel('workflowaction')->getByModuleAndAction($moduleName, $methodName, $groupID);
  371. if(empty($action) or $action->extensionType == 'none') return '';
  372. $this->loadModel('file');
  373. if($this->post->uid) $this->file->updateObjectID($this->post->uid, $objectID, $moduleName);
  374. $uiID = $this->loadModel('workflowlayout')->getUIByDataID($moduleName, $methodName, $objectID);
  375. $fields = $this->workflowaction->getPageFields($moduleName, $action->action, '', null, $uiID, $groupID);
  376. foreach($fields as $field)
  377. {
  378. if($field->control == 'file' && $field->show && !$field->readonly)
  379. {
  380. $this->file->saveUpload($moduleName, $objectID, $field->field, $field->field, $field->field);
  381. }
  382. }
  383. $flow = $this->loadModel('workflow')->getByModule($moduleName, false, $groupID);
  384. if($flow && $action) return $this->loadModel('workflowhook')->execute($flow, $action, $objectID);
  385. }
  386. /**
  387. * Call the functions declared in the tao files.
  388. *
  389. * @param string $method
  390. * @param array $arguments
  391. * @access public
  392. * @return mixed
  393. */
  394. public function __call($method, $arguments)
  395. {
  396. $moduleName = $this->getModuleName();
  397. $taoClass = $moduleName . 'Tao';
  398. if(isset($this->{$taoClass}) && is_callable(array($this->{$taoClass}, $method))) return call_user_func_array(array($this->{$taoClass}, $method), $arguments);
  399. $this->app->triggerError("the module {$moduleName} has no {$method} method", __FILE__, __LINE__, true);
  400. }
  401. /**
  402. * Call the static functions declared in the tao files.
  403. *
  404. * @param string $method
  405. * @param array $arguments
  406. * @access public
  407. * @return mixed
  408. */
  409. public static function __callStatic($method, $arguments)
  410. {
  411. global $app;
  412. $moduleName = strtolower(static::class);
  413. preg_match_all('/^(ext)?(\w+)model/', $moduleName, $matches);
  414. if(isset($matches[2][0]))
  415. {
  416. $moduleName = $matches[2][0];
  417. }
  418. else
  419. {
  420. preg_match_all('/^(ext)?(\w+)tao/', $moduleName, $matches);
  421. if(isset($matches[2][0])) $moduleName = $matches[2][0];
  422. }
  423. $modelClass = 'ext' . $moduleName . 'Model';
  424. if(method_exists($modelClass, $method)) return call_user_func_array("{$modelClass}::{$method}", $arguments);
  425. $taoClass = 'ext' . $moduleName . 'Tao';
  426. if(method_exists($taoClass, $method)) return call_user_func_array("{$taoClass}::{$method}", $arguments);
  427. $taoClass = $moduleName . 'Tao';
  428. if(method_exists($taoClass, $method)) return call_user_func_array("{$taoClass}::{$method}", $arguments);
  429. $app->triggerError("the module {$moduleName} has no {$method} method", __FILE__, __LINE__, true);
  430. }
  431. }