control.class.php 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000
  1. <?php /**
  2. * ZenTaoPHP的control类。
  3. * The control 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. * control基类继承与baseControl,所有模块的control类都派生于它。
  14. * The base class of control extends baseControl.
  15. *
  16. * @package framework
  17. */
  18. include dirname(__FILE__, 2) . '/base/control.class.php';
  19. class control extends baseControl
  20. {
  21. /**
  22. * 是否收集FormData,用于补全Form表单
  23. * Collect form data.
  24. *
  25. * @var bool
  26. * @access public
  27. */
  28. public $getFormData = false;
  29. /**
  30. * Form表单数据
  31. * FormData.
  32. *
  33. * @var array
  34. * @access public
  35. */
  36. public $formData = array();
  37. /**
  38. * Check requiredFields and set exportFields for workflow.
  39. *
  40. * @param string $moduleName
  41. * @param string $methodName
  42. * @param string $appName
  43. * @access public
  44. * @return void
  45. */
  46. public function __construct($moduleName = '', $methodName = '', $appName = '')
  47. {
  48. parent::__construct($moduleName, $methodName, $appName);
  49. $this->app->setOpenApp();
  50. if($this->config->edition == 'open') return false;
  51. /* Code for task #9224. Set requiredFields for workflow. */
  52. if($this->dbh && ($this->app->isServing() || (defined('RUN_MODE') and RUN_MODE == 'api')))
  53. {
  54. $this->extendExportFields();
  55. $this->extendEditorFields();
  56. /* If workflow is created by a normal user, set priv. */
  57. if(isset($this->app->user) and !$this->app->user->admin) $this->setDefaultPrivByWorkflow();
  58. }
  59. }
  60. /**
  61. * 渲染json格式。
  62. * Parse json format.
  63. *
  64. * @param string $moduleName module name
  65. * @param string $methodName method name
  66. * @access public
  67. * @return void
  68. */
  69. public function parseJSON($moduleName, $methodName)
  70. {
  71. header('Content-Type: application/json');
  72. $this->view->status = is_object($this->view) ? 'success' : 'fail';
  73. unset($this->view->app);
  74. unset($this->view->config);
  75. unset($this->view->lang);
  76. unset($this->view->header);
  77. unset($this->view->position);
  78. unset($this->view->moduleTree);
  79. unset($this->view->common);
  80. unset($this->view->pager->app);
  81. unset($this->view->pager->lang);
  82. $output = $this->view ? json_encode($this->view) : null;
  83. if($this->app->apiVersion == 'v1')
  84. {
  85. $output = array();
  86. $output['status'] = is_object($this->view) ? 'success' : 'fail';
  87. $output['data'] = json_encode($this->view) ? json_encode($this->view) : '';
  88. $output['md5'] = md5($output['data']);
  89. $this->output = json_encode($output);
  90. }
  91. elseif($this->app->responseExtractor != '*')
  92. {
  93. $this->app->loadClass('jsonextractor');
  94. $extractor = new jsonextractor();
  95. $responseExtractor = 'status,'. $this->app->responseExtractor;
  96. $this->output = $extractor->extract($output, $responseExtractor);
  97. }
  98. else
  99. {
  100. $this->output = $output;
  101. }
  102. }
  103. /**
  104. * 向浏览器输出内容。
  105. * Print the content of the view.
  106. *
  107. * @param string $moduleName module name
  108. * @param string $methodName method name
  109. * @access public
  110. * @return void
  111. */
  112. public function display($moduleName = '', $methodName = '')
  113. {
  114. if($this->viewType == 'html') return parent::display($moduleName, $methodName);
  115. $this->render($moduleName, $methodName);
  116. }
  117. /**
  118. * 向浏览器输出内容。
  119. * Print the content of the view.
  120. *
  121. * @param string $moduleName module name
  122. * @param string $methodName method name
  123. * @access public
  124. * @return void
  125. */
  126. public function render($moduleName = '', $methodName = '')
  127. {
  128. if($this->app->apiVersion != 'v2')
  129. {
  130. $this->parseJSON($moduleName, $methodName);
  131. $output = array();
  132. $output['status'] = is_object($this->view) ? 'success' : 'fail';
  133. $output['data'] = json_encode($this->view) ? json_encode($this->view) : '';
  134. $output['md5'] = md5($output['data']);
  135. ob_start();
  136. echo json_encode($output);
  137. return;
  138. }
  139. elseif(!$this->getFormData)
  140. {
  141. if($this->viewType == 'html') return parent::render($moduleName, $methodName);
  142. $this->parseJSON($moduleName, $methodName);
  143. ob_start();
  144. echo $this->output;
  145. return;
  146. }
  147. if(empty($moduleName)) $moduleName = $this->moduleName;
  148. if(empty($methodName)) $methodName = $this->methodName;
  149. /* Load zin lib */
  150. $this->app->loadClass('zin', true);
  151. \zin\loadConfig();
  152. /**
  153. * 设置视图文件。(PHP7有一个bug,不能直接$viewFile = $this->setViewFile())。
  154. * Set viewFile. (Can't assign $viewFile = $this->setViewFile() directly because one php7's bug.)
  155. */
  156. $results = $this->setViewFile($moduleName, $methodName, 'ui');
  157. $viewFile = $results;
  158. if(is_array($results)) extract($results);
  159. /**
  160. * 切换到视图文件所在的目录,以保证视图文件里面的include语句能够正常运行。
  161. * Change the dir to the view file to keep the relative paths work.
  162. */
  163. $currentPWD = getcwd();
  164. chdir(dirname($viewFile));
  165. /**
  166. * Init zin context data.
  167. * 设置 zin 渲染上下文数据。
  168. */
  169. $context = \zin\context();
  170. $context->control = $this;
  171. $context->data = (array)$this->view;
  172. /**
  173. * 使用extract和ob方法渲染$viewFile里面的代码。
  174. * Use extract and ob functions to eval the codes in $viewFile.
  175. */
  176. extract($context->data);
  177. /* 将 hooks 文件添加到当前 context 中。 */
  178. if(!empty($hookFiles)) $context->addHookFiles($hookFiles);
  179. /* 加载 common.field.php 和 method.field.php。 */
  180. $commonFieldFile = dirname($viewFile) . DS . 'common.field.php';
  181. $methodFieldFile = dirname($viewFile) . DS . $methodName . '.field.php';
  182. helper::import($commonFieldFile);
  183. helper::import($methodFieldFile);
  184. ob_start();
  185. include $viewFile;
  186. $this->setResponseHeader();
  187. \zin\renderPage(array('type' => 'html'));
  188. $content = ob_get_clean();
  189. $this->app->loadClass('formdom');
  190. $parser = new formdom(array('include_disabled' => true));
  191. $this->formData = $parser->parse($content);
  192. /**
  193. * 解析完毕后,再切换回之前的路径。
  194. * At the end, chang the dir to the previous.
  195. */
  196. chdir($currentPWD);
  197. }
  198. /**
  199. * Send error data
  200. *
  201. * @param mixed $error
  202. * @param string|bool $locate
  203. * @return void
  204. */
  205. public function sendError($error, $locate = false)
  206. {
  207. $result = array('status' => 'fail', 'message' => $error);
  208. return $this->send($result);
  209. }
  210. /**
  211. * Send success data
  212. *
  213. * @param array $data
  214. * @return void
  215. */
  216. public function sendSuccess($data = array())
  217. {
  218. $data['status'] = 'success';
  219. if(!isset($data['message'])) $data['message'] = $this->lang->saveSuccess;
  220. return $this->send($data);
  221. }
  222. /**
  223. * 直接输出data数据。
  224. * Send data directly.
  225. *
  226. * @param mixed $data
  227. * @param string $type
  228. * @access public
  229. * @return void
  230. */
  231. public function send($data, $type = 'json')
  232. {
  233. header('Content-Type: application/json');
  234. if($type != 'json') return helper::end();
  235. /* APIv1 send json in entries, print data for getData method. */
  236. if($this->app->apiVersion == 'v1')
  237. {
  238. $response = helper::removeUTF8Bom(json_encode($data, JSON_UNESCAPED_UNICODE));
  239. return print($response);
  240. }
  241. $data = (array)$data;
  242. if(isset($data['result']))
  243. {
  244. $data['status'] = $data['result'];
  245. unset($data['result']);
  246. }
  247. elseif(!isset($data['status']))
  248. {
  249. $data['status'] = 'success';
  250. }
  251. $response = helper::removeUTF8Bom(json_encode($data, JSON_UNESCAPED_UNICODE));
  252. $this->app->outputXhprof();
  253. return helper::end($response);
  254. }
  255. /**
  256. * Append export fields to the config of this module from workflow.
  257. *
  258. * @access public
  259. * @return void
  260. */
  261. public function extendExportFields()
  262. {
  263. if(isset($this->config->{$this->moduleName}) and strpos((string) $this->methodName, 'export') !== false)
  264. {
  265. if(isset($this->config->{$this->moduleName}->exportFields) or isset($this->config->{$this->moduleName}->list->exportFields))
  266. {
  267. $exportFields = $this->dao->select('field, name')->from(TABLE_WORKFLOWFIELD)->where('module')->eq($this->moduleName)->andWhere('canExport')->eq('1')->andWhere('buildin')->eq('0')->fetchAll('field');
  268. if(isset($this->config->{$this->moduleName}->exportFields))
  269. {
  270. foreach($exportFields as $field) $this->config->{$this->moduleName}->exportFields .= ",{$field->field}";
  271. }
  272. if(isset($this->config->{$this->moduleName}->list->exportFields))
  273. {
  274. foreach($exportFields as $field) $this->config->{$this->moduleName}->list->exportFields .= ",{$field->field}";
  275. }
  276. if(isset($this->config->excel->editor[$this->moduleName]))
  277. {
  278. foreach($exportFields as $field)
  279. {
  280. if(isset($field->control) && $field->control == 'richtext') $this->config->excel->editor[$this->moduleName][] = $field->field;
  281. }
  282. }
  283. foreach($exportFields as $flowField => $exportField)
  284. {
  285. if(!isset($this->lang->{$this->moduleName}->$flowField)) $this->lang->{$this->moduleName}->$flowField = $exportField->name;
  286. }
  287. }
  288. }
  289. }
  290. /**
  291. * Append editor fields to the config of this module from workflow.
  292. *
  293. * @access public
  294. * @return void
  295. */
  296. public function extendEditorFields()
  297. {
  298. $moduleName = $this->moduleName;
  299. $methodName = $this->methodName;
  300. $fields = $this->dao->select('field')->from(TABLE_WORKFLOWFIELD)->where('module')->eq($this->moduleName)->andWhere('control')->eq('richtext')->andWhere('buildin')->eq('0')->fetchPairs();
  301. if($fields)
  302. {
  303. $editorIdList = array();
  304. foreach($fields as $field) $editorIdList[] = $field;
  305. if(!isset($this->config->{$moduleName})) $this->config->{$moduleName} = new stdclass();
  306. if(!isset($this->config->{$moduleName}->editor)) $this->config->{$moduleName}->editor = new stdclass();
  307. if(!isset($this->config->{$moduleName}->editor->{$methodName})) $this->config->{$moduleName}->editor->{$methodName} = array('id' => '', 'tools' => 'simpleTools');
  308. $this->config->{$moduleName}->editor->{$methodName}['id'] .= ',' . join(',', $editorIdList);
  309. trim($this->config->{$moduleName}->editor->{$methodName}['id'], ',');
  310. }
  311. }
  312. /**
  313. * Det default priv by workflow.
  314. *
  315. * @access public
  316. * @return bool
  317. */
  318. public function setDefaultPrivByWorkflow()
  319. {
  320. $actionList = $this->dao->select('module, `action`')->from(TABLE_WORKFLOWACTION)
  321. ->where('createdBy')->eq($this->app->user->account)
  322. ->andWhere('buildin')->eq('0')
  323. ->fetchGroup('module');
  324. if($actionList)
  325. {
  326. foreach($actionList as $module => $actions)
  327. {
  328. foreach($actions as $action) $this->app->user->rights['rights'][$module][$action->action] = 1;
  329. }
  330. }
  331. $labelList = $this->dao->select('module, code')->from(TABLE_WORKFLOWLABEL)
  332. ->where('createdBy')->eq($this->app->user->account)
  333. ->andWhere('buildin')->eq('0')
  334. ->fetchGroup('module');
  335. if($labelList)
  336. {
  337. foreach($labelList as $module => $labels)
  338. {
  339. foreach($labels as $label)
  340. {
  341. $code = str_replace('browse', '', (string) $label->code);
  342. $this->app->user->rights['rights'][$module][$code] = 1;
  343. }
  344. }
  345. }
  346. return true;
  347. }
  348. /**
  349. * 企业版部分功能是从然之合并过来的。ZDOO代码中调用loadModel方法时传递了一个非空的appName,在禅道中会导致错误。
  350. * 调用父类的loadModel方法来避免这个错误。
  351. * Some codes merged from ZDOO called the function loadModel with a non-empty appName which causes an error in zentao.
  352. * Call the parent function with empty appName to avoid this error.
  353. *
  354. * @param string $moduleName 模块名,如果为空,使用当前模块。The module name, if empty, use current module's name.
  355. * @param string $appName 应用名,如果为空,使用当前应用。The app name, if empty, use current app's name.
  356. * @access public
  357. * @return object|bool 如果没有model文件,返回false,否则返回model对象。If no model file, return false, else return the model object.
  358. */
  359. public function loadModel($moduleName = '', $appName = '')
  360. {
  361. return parent::loadModel($moduleName);
  362. }
  363. /**
  364. * 企业版部分功能是从然之合并过来的。ZDOO代码中调用loadZen方法时传递了一个非空的appName,在禅道中会导致错误。
  365. * 调用父类的loadZen方法来避免这个错误。
  366. * Some codes merged from ZDOO called the function loadZen with a non-empty appName which causes an error in zentao.
  367. * Call the parent function with empty appName to avoid this error.
  368. *
  369. * @param string $moduleName 模块名,如果为空,使用当前模块。The module name, if empty, use current module's name.
  370. * @param string $appName 应用名,如果为空,使用当前应用。The app name, if empty, use current app's name.
  371. * @access public
  372. * @return object|bool 如果没有model文件,返回false,否则返回model对象。If no model file, return false, else return the model object.
  373. */
  374. public function loadZen($moduleName = '', $appName = '')
  375. {
  376. return parent::loadZen($moduleName);
  377. }
  378. /**
  379. * 加载model的class扩展,主要是为了开发加密代码使用。
  380. * 可以将主要的逻辑存放到$moduleName/ext/model/class/$extensionName.class.php中。
  381. * 然后在ext/model/$extension.php的扩展里面使用$this->loadExtension()来调用相应的方法。
  382. * ext/model/class/*.class.php代码可以加密。而ext/model/*.php可以不用加密。
  383. * 因为框架对model的扩展是采取合并文件的方式,ext/model/*.php文件不能加密。
  384. *
  385. * Load extension class of a model thus user can encrypt the code.
  386. * You can put the main extension logic codes in $moduleName/ext/model/class/$extensionName.class.php.
  387. * And call them by the ext/model/$extension.php like this: $this->loadExtension('myextension')->method().
  388. * You can encrypt the code in ext/model/class/*.class.php.
  389. * Because the framework will merge the extension files in ext/model/*.php to the module/model.php.
  390. *
  391. * @param string $extensionName
  392. * @param string $moduleName
  393. * @access public
  394. * @return void
  395. */
  396. public function loadExtension($extensionName, $moduleName = '')
  397. {
  398. if(empty($extensionName)) return false;
  399. if(empty($moduleName)) $moduleName = $this->moduleName;
  400. $moduleName = strtolower((string) $moduleName);
  401. $extensionName = strtolower($extensionName);
  402. $type = 'model';
  403. $className = strtolower(static::class);
  404. if($className == $moduleName . 'zen' || $className == 'ext' . $moduleName . 'zen') $type = 'zen';
  405. /* 设置扩展类的名字。Set the extension class name. */
  406. $extensionClass = $extensionName . ucfirst($moduleName);
  407. if($type != 'model') $extensionClass .= ucfirst($type);
  408. if(isset($this->$extensionClass)) return $this->$extensionClass;
  409. /* 设置扩展的名字和相应的文件。Set extenson name and extension file. */
  410. $moduleExtPath = $this->app->getModuleExtPath($moduleName, $type);
  411. if(!empty($moduleExtPath['site'])) $extensionFile = $moduleExtPath['site'] . 'class/' . $extensionName . '.class.php';
  412. if(!isset($extensionFile) or !file_exists($extensionFile)) $extensionFile = $moduleExtPath['custom'] . 'class/' . $extensionName . '.class.php';
  413. if(!isset($extensionFile) or !file_exists($extensionFile)) $extensionFile = $moduleExtPath['saas'] . 'class/' . $extensionName . '.class.php';
  414. if(!isset($extensionFile) or !file_exists($extensionFile)) $extensionFile = $moduleExtPath['vision'] . 'class/' . $extensionName . '.class.php';
  415. if(!isset($extensionFile) or !file_exists($extensionFile)) $extensionFile = $moduleExtPath['xuan'] . 'class/' . $extensionName . '.class.php';
  416. if(!isset($extensionFile) or !file_exists($extensionFile)) $extensionFile = $moduleExtPath['common'] . 'class/' . $extensionName . '.class.php';
  417. /* 载入父类。Try to import parent model file auto and then import the extension file. */
  418. if(!class_exists($moduleName . ucfirst($type))) helper::import($this->app->getModulePath($this->appName, $moduleName) . $type . '.php');
  419. if(!helper::import($extensionFile)) return false;
  420. if(!class_exists($extensionClass)) return false;
  421. /* 实例化扩展类。Create an instance of the extension class and return it. */
  422. $extensionObject = new $extensionClass();
  423. if($type == 'model') $extensionClass = str_replace(ucfirst($type), '', $extensionClass);
  424. $this->$extensionClass = $extensionObject;
  425. $this->$extensionClass->view = $this->view;
  426. return $extensionObject;
  427. }
  428. /**
  429. * 设置视图文件:主视图文件,扩展视图文件, 站点扩展视图文件,以及钩子脚本。
  430. * Set view files: the main file, extension view file, site extension view file and hook files.
  431. *
  432. * @param string $moduleName module name
  433. * @param string $methodName method name
  434. * @param string $viewDir
  435. * @access public
  436. * @return string the view file
  437. */
  438. public function setViewFile($moduleName, $methodName, $viewDir = 'view')
  439. {
  440. $moduleName = strtolower(trim($moduleName));
  441. $methodName = strtolower(trim($methodName));
  442. $modulePath = $this->app->getModulePath($this->appName, $moduleName);
  443. $viewExtPath = $this->app->getModuleExtPath($moduleName, $viewDir);
  444. $viewType = 'html';
  445. $mainViewFile = $modulePath . $viewDir . DS . $this->devicePrefix . $methodName . '.' . $viewType . '.php';
  446. /* If the main view file doesn't exist, set the device prefix to empty and reset the main view file. */
  447. if(!file_exists($mainViewFile) and $this->app->clientDevice != 'mobile')
  448. {
  449. $originalPrefix = $this->devicePrefix;
  450. $this->devicePrefix = '';
  451. $mainViewFile = $modulePath . 'view' . DS . $this->devicePrefix . $methodName . '.' . $viewType . '.php';
  452. $this->devicePrefix = $originalPrefix;
  453. }
  454. $viewFile = $mainViewFile;
  455. if(!empty($viewExtPath))
  456. {
  457. $commonExtViewFile = $viewExtPath['common'] . $this->devicePrefix . $methodName . ".{$viewType}.php";
  458. $xuanExtViewFile = $viewExtPath['xuan'] . $this->devicePrefix . $methodName . ".{$viewType}.php";
  459. $visionExtViewFile = $viewExtPath['vision'] . $this->devicePrefix . $methodName . ".{$viewType}.php";
  460. $saasExtViewFile = $viewExtPath['saas'] . $this->devicePrefix . $methodName . ".{$viewType}.php";
  461. $customExtViewFile = $viewExtPath['custom'] . $this->devicePrefix . $methodName . ".{$viewType}.php";
  462. $siteExtViewFile = empty($viewExtPath['site']) ? '' : $viewExtPath['site'] . $this->devicePrefix . $methodName . ".{$viewType}.php";
  463. /* Get ext files, site > custom > vision > common. */
  464. if(!empty($siteExtViewFile) and file_exists($siteExtViewFile))
  465. {
  466. $viewFile = $siteExtViewFile;
  467. }
  468. else if(file_exists($customExtViewFile))
  469. {
  470. $viewFile = $customExtViewFile;
  471. }
  472. else if(!empty($viewExtPath['vision']) and file_exists($visionExtViewFile))
  473. {
  474. $viewFile = $visionExtViewFile;
  475. }
  476. else if(file_exists($xuanExtViewFile))
  477. {
  478. $viewFile = $xuanExtViewFile;
  479. }
  480. else if(file_exists($saasExtViewFile))
  481. {
  482. $viewFile = $saasExtViewFile;
  483. }
  484. else if(file_exists($commonExtViewFile))
  485. {
  486. $viewFile = $commonExtViewFile;
  487. }
  488. if(!is_file($viewFile)) $viewFile = dirname((string) $viewExtPath['common'], 2) . DS . 'view' . DS . $this->devicePrefix . $methodName . ".{$viewType}.php";
  489. if(!is_file($viewFile)) helper::end(js::error($this->lang->notPage) . js::locate('back'));
  490. /* Get ext hook files. */
  491. $commonExtHookFiles = glob($viewExtPath['common'] . $this->devicePrefix . $methodName . ".*.{$viewType}.hook.php");
  492. if(!empty($viewExtPath['vision']))
  493. {
  494. $visionExtHookFiles = glob($viewExtPath['vision'] . $this->devicePrefix . $methodName . ".*.{$viewType}.hook.php");
  495. $commonExtHookFiles = array_merge((array)$commonExtHookFiles, (array)$visionExtHookFiles);
  496. }
  497. $xuanExtHookFiles = glob($viewExtPath['xuan'] . $this->devicePrefix . $methodName . ".*.{$viewType}.hook.php");
  498. $saasExtHookFiles = glob($viewExtPath['saas'] . $this->devicePrefix . $methodName . ".*.{$viewType}.hook.php");
  499. $customExtHookFiles = glob($viewExtPath['custom'] . $this->devicePrefix . $methodName . ".*.{$viewType}.hook.php");
  500. $siteExtHookFiles = empty($viewExtPath['site']) ? '' : glob($viewExtPath['site'] . $this->devicePrefix . $methodName . ".*.{$viewType}.hook.php");
  501. $extHookFiles = array_merge((array)$commonExtHookFiles, (array)$xuanExtHookFiles, (array)$saasExtHookFiles, (array)$customExtHookFiles, (array)$siteExtHookFiles);
  502. }
  503. if(!empty($extHookFiles)) return array('viewFile' => $viewFile, 'hookFiles' => $extHookFiles);
  504. return $viewFile;
  505. }
  506. /**
  507. * 默认渲染方法,适用于viewType = html的时候。
  508. * Default parse method when viewType != json, like html.
  509. *
  510. * @param string $moduleName module name
  511. * @param string $methodName method name
  512. * @access public
  513. * @return void
  514. */
  515. public function parseDefault($moduleName, $methodName)
  516. {
  517. /**
  518. * 设置视图文件。(PHP7有一个bug,不能直接$viewFile = $this->setViewFile())。
  519. * Set viewFile. (Can't assign $viewFile = $this->setViewFile() directly because one php7's bug.)
  520. */
  521. $results = $this->setViewFile($moduleName, $methodName);
  522. $viewFile = $results;
  523. if(is_array($results)) extract($results);
  524. /**
  525. * 获得当前页面的CSS和JS。
  526. * Get css and js codes for current method.
  527. */
  528. $css = $this->getCSS($moduleName, $methodName);
  529. $js = $this->getJS($moduleName, $methodName);
  530. /* If the js or css file doesn't exist, set the device prefix to empty and reset the js or css file. */
  531. if($this->viewType == 'xhtml')
  532. {
  533. $originalPrefix = $this->devicePrefix;
  534. $this->devicePrefix = '';
  535. $css .= $this->getCSS($moduleName, $methodName);
  536. $js .= $this->getJS($moduleName, $methodName);
  537. $this->devicePrefix = $originalPrefix;
  538. }
  539. if($css) $this->view->pageCSS = $css;
  540. if($js) $this->view->pageJS = $js;
  541. /**
  542. * 切换到视图文件所在的目录,以保证视图文件里面的include语句能够正常运行。
  543. * Change the dir to the view file to keep the relative paths work.
  544. */
  545. $currentPWD = getcwd();
  546. chdir(dirname($viewFile));
  547. /**
  548. * 使用extract和ob方法渲染$viewFile里面的代码。
  549. * Use extract and ob functions to eval the codes in $viewFile.
  550. */
  551. extract((array)$this->view);
  552. ob_start();
  553. include $viewFile;
  554. if(isset($hookFiles)) foreach($hookFiles as $hookFile) if(file_exists($hookFile)) include $hookFile;
  555. $this->output .= ob_get_contents();
  556. ob_end_clean();
  557. /**
  558. * 渲染完毕后,再切换回之前的路径。
  559. * At the end, chang the dir to the previous.
  560. */
  561. chdir($currentPWD);
  562. }
  563. /**
  564. * 获取一个方法的输出内容,这样我们可以在一个方法里获取其他模块方法的内容。
  565. * 如果模块名为空,则调用该模块、该方法;如果设置了模块名,调用指定模块指定方法。
  566. *
  567. * Get the output of one module's one method as a string, thus in one module's method, can fetch other module's content.
  568. * If the module name is empty, then use the current module and method. If set, use the user defined module and method.
  569. *
  570. * @param string $moduleName module name.
  571. * @param string $methodName method name.
  572. * @param array $params params.
  573. * @access public
  574. * @return string the parsed html.
  575. * @param string $appName
  576. */
  577. public function fetch($moduleName = '', $methodName = '', $params = array(), $appName = '')
  578. {
  579. if($moduleName != $this->moduleName) $this->app->fetchModule = $moduleName;
  580. $content = parent::fetch($moduleName, $methodName, $params, $appName);
  581. if($this->getFormData)
  582. {
  583. $this->app->loadClass('formdom');
  584. $parser = new formdom(array('include_disabled' => true));
  585. $this->formData = $parser->parse($content);
  586. }
  587. return $content;
  588. }
  589. /**
  590. * Build operate menu of a method.
  591. *
  592. * @param object $object product|project|productplan|release|build|story|task|bug|testtask|testcase|testsuite
  593. * @param string $type view|browse
  594. * @access public
  595. * @return string
  596. */
  597. public function buildOperateMenu($object, $type = 'view')
  598. {
  599. if($this->config->edition == 'open') return false;
  600. $moduleName = $this->moduleName;
  601. return $this->$moduleName->buildOperateMenu($object, $type);
  602. }
  603. /**
  604. * Execute hooks of a method.
  605. *
  606. * @param int $objectID The id of an object. The object maybe a bug | build | feedback | product | productplan | project | release | story | task | testcase | testsuite | testtask.
  607. * @access public
  608. * @return string
  609. */
  610. public function executeHooks($objectID)
  611. {
  612. if($this->config->edition == 'open') return '';
  613. $moduleName = $this->moduleName;
  614. return $this->$moduleName->executeHooks($objectID);
  615. }
  616. /**
  617. * Set workflow export fields
  618. *
  619. * @access public
  620. * @return array
  621. */
  622. public function getFlowExportFields()
  623. {
  624. if($this->config->edition == 'open') return array();
  625. if(!empty($this->app->installing) || !empty($this->app->upgrading)) return array();
  626. $moduleName = $this->moduleName;
  627. return $this->$moduleName->getFlowExportFields();
  628. }
  629. /**
  630. * Print extend fields.
  631. *
  632. * @param object $object bug | build | feedback | product | productplan | project | release | story | task | testcase | testsuite | testtask
  633. * @param string $type The parent component which fileds displayed in. It should be table or div.
  634. * @param string $extras The extra params.
  635. * columns=2|3|5 Number of the columns merged to display the fields. The default is 1.
  636. * position=left|right The position which the fields displayed in a page.
  637. * inForm=0|1 The fields displayed in a form or not. The default is 1.
  638. * inCell=0|1 The fields displayed in a div with class cell or not. The default is 0.
  639. * @param bool $print
  640. * @param string $moduleName
  641. * @param string $methodName
  642. * @access public
  643. * @return void
  644. */
  645. public function printExtendFields($object, $type, $extras = '', $print = true, $moduleName = '', $methodName = '')
  646. {
  647. if($this->config->edition == 'open') return false;
  648. if(!empty($this->app->installing) || !empty($this->app->upgrading)) return false;
  649. $moduleName = $moduleName ?: $this->app->getModuleName();
  650. $methodName = $methodName ?: $this->app->getMethodName();
  651. $this->loadModel('flow');
  652. if($type == 'html')
  653. {
  654. parse_str($extras, $result);
  655. $html = $this->flow->buildExtendHtmlValue($object, zget($result, 'position', 'info'));
  656. $html .= $this->appendExtendCssAndJS($moduleName, $methodName, $object);
  657. }
  658. else
  659. {
  660. ob_start();
  661. $this->flow->printFields($moduleName, $methodName, $object, $type, $extras);
  662. $html = ob_get_contents();
  663. ob_end_clean();
  664. }
  665. if(!$print) return $html;
  666. echo $html;
  667. }
  668. /**
  669. * 追加工作流新增的字段,给formGridPanel组件使用。
  670. * append workflow fields to form.
  671. *
  672. * @param zin\fieldList $fields
  673. * @param string $moduleName
  674. * @param string $methodName
  675. * @param object $object
  676. * @access public
  677. * @return zin\fieldList
  678. */
  679. public function appendExtendFields($fields, $moduleName = '', $methodName = '', $object = null)
  680. {
  681. if($this->config->edition == 'open') return $fields;
  682. if(!empty($this->app->installing) || !empty($this->app->upgrading)) return $fields;
  683. $moduleName = $moduleName ? $moduleName : $this->app->rawModule;
  684. $methodName = $methodName ? $methodName : $this->app->rawMethod;
  685. $groupID = $this->loadModel('workflowgroup')->getGroupIDByData($moduleName, $object);
  686. $flow = $this->loadModel('workflow')->getByModule($moduleName, false, $groupID);
  687. if(!$flow) return $fields;
  688. $action = $this->loadModel('workflowaction')->getByModuleAndAction($flow->module, $methodName, $groupID);
  689. if(!$action || $action->extensionType != 'extend') return $fields;
  690. $uiID = $this->loadModel('workflowlayout')->getUIByData($flow->module, $action->action, $object);
  691. $fieldList = $this->workflowaction->getPageFields($flow->module, $action->action, true, null, $uiID, $groupID);
  692. /* 复制项目时显示被复制项目的工作流字段值。*/
  693. if($moduleName == 'project' && $methodName == 'create')
  694. {
  695. foreach($fieldList as $field)
  696. {
  697. $objectValue = zget($object, $field->field, '');
  698. if($objectValue) $field->default = $objectValue;
  699. }
  700. }
  701. return $this->loadModel('flow')->buildFormFields($fields, $fieldList, array(), $object);
  702. }
  703. /**
  704. * 追加工作流配置的js和css到页面上。
  705. * append workflow js and css to form.
  706. *
  707. * @param string $moduleName
  708. * @param string $methodName
  709. * @param object $object
  710. * @access public
  711. * @return string
  712. */
  713. public function appendExtendCssAndJS($moduleName = '', $methodName = '', $object = null)
  714. {
  715. if($this->config->edition == 'open') return '';
  716. if(!empty($this->app->installing) || !empty($this->app->upgrading)) return '';
  717. $moduleName = $moduleName ? $moduleName : $this->app->rawModule;
  718. $methodName = $methodName ? $methodName : $this->app->rawMethod;
  719. $groupID = $this->loadModel('workflowgroup')->getGroupIDByData($moduleName, $object);
  720. $flow = $this->loadModel('workflow')->getByModule($moduleName, false, $groupID);
  721. if(!$flow) return '';
  722. $action = $this->loadModel('workflowaction')->getByModuleAndAction($flow->module, $methodName, $groupID);
  723. if(!$action || $action->extensionType == 'none') return '';
  724. $uiID = $this->loadModel('workflowlayout')->getUIByData($flow->module, !empty($action->action) ? $action->action: '', $object);
  725. $fieldList = $this->loadModel('workflowaction')->getPageFields($flow->module, !empty($action->action) ? $action->action: '', true, null, $uiID, $groupID);
  726. $html = '';
  727. if(!empty($flow->css)) $html .= "<style>$flow->css</style>";
  728. if(!empty($action->css)) $html .= "<style>$action->css</style>";
  729. $html .= $this->loadModel('flow')->getFormulaScript($moduleName, $action, $fieldList);
  730. if(!empty($action->linkages)) $html .= $this->flow->getLinkageScript($action, $fieldList, $uiID);
  731. if(!empty($flow->js)) $html .= "<script>$flow->js</script>";
  732. if(!empty($action->js)) $html .= "<script>$action->js</script>";
  733. return $html;
  734. }
  735. /**
  736. * 追加工作流新增的字段,给非formGridPanel组件使用。
  737. * append workflow field to form.
  738. *
  739. * @param string $position info|basic
  740. * @param object $object
  741. * @param string $moduleName
  742. * @param string $methodName
  743. * @access public
  744. * @return array
  745. */
  746. public function appendExtendForm($position = 'info', $object = null, $moduleName = '', $methodName = '')
  747. {
  748. if($this->config->edition == 'open') return array();
  749. if(!empty($this->app->installing) || !empty($this->app->upgrading)) return array();
  750. $moduleName = $moduleName ? $moduleName : $this->app->rawModule;
  751. $methodName = $methodName ? $methodName : $this->app->rawMethod;
  752. $this->loadModel('flow');
  753. $this->loadModel('workflowfield');
  754. $groupID = $this->loadModel('workflowgroup')->getGroupIDByData($moduleName, $object);
  755. $flow = $this->loadModel('workflow')->getByModule($moduleName, false, $groupID);
  756. if(!$flow) return array();
  757. $action = $this->loadModel('workflowaction')->getByModuleAndAction($flow->module, $methodName, $groupID);
  758. if(!$action || $action->extensionType != 'extend') return array();
  759. $uiID = is_object($object) ? $this->loadModel('workflowlayout')->getUIByData($flow->module, $action->action, $object) : 0;
  760. $fieldList = $this->workflowaction->getPageFields($flow->module, $action->action, true, $object, $uiID, $groupID);
  761. $layouts = $this->loadModel('workflowlayout')->getFields($moduleName, $methodName, $uiID, $groupID);
  762. $notEmptyRule = $this->loadModel('workflowrule')->getByTypeAndRule('system', 'notempty');
  763. if($layouts)
  764. {
  765. $allFields = $this->dao->select('field, `default`')->from(TABLE_WORKFLOWFIELD)->where('module')->eq($moduleName)->fetchPairs();
  766. foreach($fieldList as $fieldName => $field)
  767. {
  768. if(isset($allFields[$fieldName])) $field->default = $allFields[$fieldName];
  769. }
  770. foreach($fieldList as $key => $field)
  771. {
  772. if($field->buildin || !$field->show || !isset($layouts[$field->field]) || (!empty($field->position) && $position != $field->position)) unset($fieldList[$key]);
  773. if((is_numeric($field->default) || $field->default) && empty($field->defaultValue)) $field->defaultValue = $field->default;
  774. if($object && empty($object->{$field->field})) $object->{$field->field} = $field->defaultValue;
  775. if(in_array($field->type, $this->config->workflowfield->numberTypes)) $field = $this->workflowfield->processNumberField($field);
  776. $field->required = $field->readonly || ($notEmptyRule && strpos(",$field->rules,", ",{$notEmptyRule->id},") !== false);
  777. $field->control = $this->flow->buildFormControl($field);
  778. $field->items = $field->options ? array_filter($field->options) : null;
  779. $field->value = !empty($object->{$field->field}) ? zget($object, $field->field, '') : '';
  780. $field->width = $field->width != 'auto' ? $field->width : 'full';
  781. if(!$field->value && $field->defaultValue) $field->value = $field->defaultValue;
  782. }
  783. return $fieldList;
  784. }
  785. return array();
  786. }
  787. /**
  788. * Process status of an object according to its subStatus.
  789. *
  790. * @param string $module product | release | story | project | task | bug | testcase | testtask | feedback
  791. * @param object $record a record of above modules.
  792. * @access public
  793. * @return string
  794. */
  795. public function processStatus($module, $record)
  796. {
  797. $moduleName = $this->moduleName;
  798. return $this->$moduleName->processStatus($module, $record);
  799. }
  800. /**
  801. * Print view file.
  802. *
  803. * @param string $viewFile
  804. * @access public
  805. * @return bool|string
  806. */
  807. public function printViewFile($viewFile)
  808. {
  809. if(!file_exists($viewFile)) return false;
  810. if(substr($viewFile, -4) != '.php') return false;
  811. if(strpos($viewFile, '..') !== false) return false;
  812. if(strpos($viewFile, '/view/') === false) return false;
  813. if(strpos($viewFile, $this->app->getModuleRoot()) !== 0 && strpos($viewFile, $this->app->getExtensionRoot()) !== 0) return false;
  814. $currentPWD = getcwd();
  815. chdir(dirname($viewFile));
  816. extract((array)$this->view);
  817. ob_start();
  818. include $viewFile;
  819. $output = ob_get_contents();
  820. ob_end_clean();
  821. chdir($currentPWD);
  822. return $output;
  823. }
  824. /**
  825. * 将工作流扩展的必填字段加到requiredFields里,类似批量操作的会在checkFlow之前手动校验。
  826. * append workflow extension fields to requiredFields.
  827. *
  828. * @param int $objectID
  829. * @access public
  830. * @return void
  831. */
  832. public function extendRequireFields($objectID = 0)
  833. {
  834. if($this->config->edition == 'open') return;
  835. $groupID = $this->loadModel('workflowgroup')->getGroupIDByDataID($this->moduleName, $objectID);
  836. $uiID = $this->loadModel('workflowlayout')->getUIByDataID($this->moduleName, $this->methodName, $objectID);
  837. $fields = $this->loadModel('workflowaction')->getPageFields($this->moduleName, $this->methodName, true, null, $uiID, $groupID);
  838. $layouts = $this->loadModel('workflowlayout')->getFields($this->moduleName, $this->methodName, $uiID, $groupID);
  839. $notEmptyRule = $this->loadModel('workflowrule')->getByTypeAndRule('system', 'notempty');
  840. foreach($fields as $field)
  841. {
  842. if($field->buildin || !$field->show || !isset($layouts[$field->field])) continue;
  843. if($notEmptyRule && strpos(",$field->rules,", ",$notEmptyRule->id,") !== false)
  844. {
  845. if(!isset($this->config->{$this->moduleName})) $this->config->{$this->moduleName} = new stdclass();
  846. if(!isset($this->config->{$this->moduleName}->{$this->methodName})) $this->config->{$this->moduleName}->{$this->methodName} = new stdclass();
  847. if(!isset($this->config->{$this->moduleName}->{$this->methodName}->requiredFields)) $this->config->{$this->moduleName}->{$this->methodName}->requiredFields = '';
  848. $this->config->{$this->moduleName}->{$this->methodName}->requiredFields .= ",{$field->field}";
  849. }
  850. }
  851. }
  852. /**
  853. * Call the functions declared in the zen files.
  854. *
  855. * @param string $method
  856. * @param array $arguments
  857. * @access public
  858. * @return mixed
  859. */
  860. public function __call($method, $arguments)
  861. {
  862. $moduleName = $this->app->getModuleName();
  863. $zenClass = $moduleName . 'Zen';
  864. if(isset($this->{$zenClass}) && is_callable(array($this->{$zenClass}, $method))) return call_user_func_array(array($this->{$zenClass}, $method), $arguments);
  865. $this->app->triggerError("the module {$moduleName} has no {$method} method", __FILE__, __LINE__, true);
  866. }
  867. /**
  868. * Call the static functions declared in the zen files.
  869. *
  870. * @param string $method
  871. * @param array $arguments
  872. * @access public
  873. * @return mixed
  874. */
  875. public static function __callStatic($method, $arguments)
  876. {
  877. global $app;
  878. $moduleName = $app->getModuleName();
  879. $zenClass = 'ext' . $moduleName . 'Zen';
  880. if(is_callable("{$zenClass}::{$method}")) return call_user_func_array("{$zenClass}::{$method}", $arguments);
  881. $zenClass = $moduleName . 'Zen';
  882. if(is_callable("{$zenClass}::{$method}")) return call_user_func_array("{$zenClass}::{$method}", $arguments);
  883. $app->triggerError("the module {$moduleName} has no {$method} method", __FILE__, __LINE__, true);
  884. }
  885. }