model.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. <?php
  2. /**
  3. * The model file of editor module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
  6. * @license ZPL (http://zpl.pub/page/zplv12.html)
  7. * @author Yidong Wang <yidong@cnezsoft.com>
  8. * @package editor
  9. * @version $Id$
  10. * @link https://www.zentao.net
  11. */
  12. class editorModel extends model
  13. {
  14. /**
  15. * Get module files, contain control's methods and model's method but except ext.
  16. *
  17. * @param string $moduleName
  18. * @access public
  19. * @return array
  20. */
  21. public function getModuleFiles($moduleName)
  22. {
  23. $allModules = array();
  24. $modulePath = $this->app->getModulePath('', $moduleName);
  25. foreach($this->config->editor->sort as $name)
  26. {
  27. $moduleFullFile = $modulePath . $name;
  28. if(!file_exists($moduleFullFile)) continue;
  29. if($name == 'control.php' or $name == 'model.php')
  30. {
  31. $allModules[$modulePath][$moduleFullFile] = $this->analysis($moduleFullFile);
  32. }
  33. elseif(is_dir($moduleFullFile))
  34. {
  35. $ext = ($name == 'js' or $name == 'css') ? $name : 'php';
  36. foreach(glob($moduleFullFile . DS . "*.$ext") as $fileName) $allModules[$modulePath][$moduleFullFile][$fileName] = basename($fileName);
  37. }
  38. else
  39. {
  40. $allModules[$modulePath][$moduleFullFile] = $name;
  41. }
  42. }
  43. $allModules += $this->getExtensionFiles($moduleName);
  44. return $allModules;
  45. }
  46. /**
  47. * Get extension files.
  48. *
  49. * @param string $moduleName
  50. * @access public
  51. * @return array
  52. */
  53. public function getExtensionFiles($moduleName)
  54. {
  55. $extensionList = array();
  56. foreach($this->config->editor->extSort as $ext)
  57. {
  58. $extModulePaths = $this->app->getModuleExtPath($moduleName, $ext);
  59. foreach($extModulePaths as $extType => $extensionFullDir)
  60. {
  61. if(empty($extensionFullDir) or !is_dir($extensionFullDir)) continue;
  62. if($extType == 'common') $extType = $this->config->edition;
  63. if($ext == 'lang' or $ext == 'js' or $ext == 'css')
  64. {
  65. $extensionList[$extType][$extensionFullDir] = $this->getTwoGradeFiles($extensionFullDir);
  66. }
  67. else
  68. {
  69. foreach(glob($extensionFullDir . '*') as $extensionFullFile)
  70. {
  71. if($ext == 'model' and is_dir($extensionFullFile))
  72. {
  73. $extModelDir = $extensionFullFile;
  74. foreach(glob($extModelDir . '/*') as $extModelFile)
  75. {
  76. $fileName = basename($extModelFile);
  77. if($fileName == 'index.html') continue;
  78. $extensionList[$extType][$extensionFullDir][$extensionFullFile][$extModelFile] = $fileName;
  79. }
  80. }
  81. else
  82. {
  83. $fileName = basename($extensionFullFile);
  84. if($fileName == 'index.html') continue;
  85. $extensionList[$extType][$extensionFullDir][$extensionFullFile] = $fileName;
  86. }
  87. }
  88. }
  89. }
  90. }
  91. return $extensionList;
  92. }
  93. /**
  94. * if a directory has two grage, this method will get files
  95. *
  96. * @param string $extensionFullDir
  97. * @access public
  98. * @return array
  99. */
  100. public function getTwoGradeFiles($extensionFullDir)
  101. {
  102. $fileList = array();
  103. $langDirs = scandir($extensionFullDir);
  104. foreach($langDirs as $langDir)
  105. {
  106. if($langDir == '.' or $langDir == '..' or $langDir == '.svn' or $langDir == 'index.html') continue;
  107. $langFullDir = $extensionFullDir . $langDir;
  108. $fileList[$langFullDir] = array();
  109. if(is_dir($langFullDir))
  110. {
  111. $langFiles = scandir($langFullDir);
  112. foreach($langFiles as $langFile)
  113. {
  114. if($langFile == '.' or $langFile == '..' or $langFile == '.svn' or $langFile == 'index.html') continue;
  115. $langFullFile = $langFullDir . DS . $langFile;
  116. $fileList[$langFullDir][$langFullFile] = $langFile;
  117. }
  118. }
  119. }
  120. return $fileList;
  121. }
  122. /**
  123. * Analysis methods of control and model.
  124. *
  125. * @param string $fileName
  126. * @access public
  127. * @return array
  128. */
  129. public function analysis($fileName)
  130. {
  131. $classMethod = array();
  132. $class = $this->getClassNameByPath($fileName);
  133. if(strpos($fileName, 'model.php') !== false) $class .= 'Model';
  134. if(!class_exists($class)) include $fileName;
  135. $reflection = new ReflectionClass($class);
  136. foreach($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method)
  137. {
  138. $methodName = $method->name;
  139. if($method->getFileName() != $fileName) continue;
  140. if($methodName == '__construct') continue;
  141. $classMethod[$fileName . DS . $methodName] = $methodName;
  142. }
  143. return $classMethod;
  144. }
  145. /**
  146. * Print tree from module files.
  147. *
  148. * @param array $files
  149. * @param bool $isRoot
  150. * @access public
  151. * @return string
  152. */
  153. public function printTree($files, $isRoot = true)
  154. {
  155. if(empty($files) or !is_array($files)) return false;
  156. $tree = array();
  157. if($isRoot)
  158. {
  159. $module = basename(dirname(key($files)));
  160. $langFile = dirname(key($files)) . DS . 'lang' . DS . $this->app->getClientLang() . '.php';
  161. if(file_exists($langFile))
  162. {
  163. if(!isset($lang)) $lang = new stdclass();
  164. if(!isset($lang->$module)) $lang->$module = new stdclass();
  165. include $langFile;
  166. }
  167. $this->module = '';
  168. if(isset($lang->$module)) $this->module = $lang->$module;
  169. if(empty($this->module) and isset($this->lang->$module)) $this->module = $this->lang->$module;
  170. }
  171. foreach($files as $key => $file)
  172. {
  173. if(is_array($file))
  174. {
  175. $dirTree = $this->addLink4Dir($key);
  176. $dirTree->items = $this->printTree($file, false);
  177. $tree[] = $dirTree;
  178. }
  179. else
  180. {
  181. $tree[] = $this->addLink4File($key, $file);
  182. }
  183. }
  184. return $tree;
  185. }
  186. /**
  187. * Add link for directory or has children grade
  188. *
  189. * @param string $filePath
  190. * @access public
  191. * @return object
  192. */
  193. public function addLink4Dir($filePath)
  194. {
  195. $tree = new stdclass();
  196. $fileName = basename($filePath);
  197. $tree->id = md5($filePath);
  198. $tree->actions['items'] = array();
  199. if(isset($this->lang->editor->translate[$fileName]))
  200. {
  201. $tree->text = $this->lang->editor->translate[$fileName];
  202. }
  203. else
  204. {
  205. $moduleName = zget($this->lang->editor->modules, $fileName, isset($this->lang->{$fileName}->common) ? $this->lang->{$fileName}->common : $fileName);
  206. $tree->text = $moduleName;
  207. }
  208. if(strpos($filePath, DS . 'ext' . DS) !== false && $fileName != 'lang' && $fileName != 'js' && $fileName != 'css')
  209. {
  210. $parentName = basename(dirname($filePath));
  211. if($parentName == 'js')
  212. {
  213. $tree->actions['items'][] = array('key' => 'edit', 'text' => $this->lang->editor->newExtend, 'id' => $tree->id, 'data-url' => $this->getExtendLink($filePath, "newJS"), 'data-on' => 'click', 'data-call' => 'openInEditWin', 'data-params' => 'event');
  214. }
  215. elseif($parentName == 'css')
  216. {
  217. $tree->actions['items'][] = array('key' => 'edit', 'text' => $this->lang->editor->newExtend, 'id' => $tree->id, 'data-url' => $this->getExtendLink($filePath, "newCSS"), 'data-on' => 'click', 'data-call' => 'openInEditWin', 'data-params' => 'event');
  218. }
  219. else
  220. {
  221. $tree->actions['items'][] = array('key' => 'edit', 'text' => $this->lang->editor->newExtend, 'id' => $tree->id, 'data-url' => $this->getExtendLink($filePath, "newExtend"), 'data-on' => 'click', 'data-call' => 'openInEditWin', 'data-params' => 'event');
  222. }
  223. }
  224. elseif($fileName == 'model.php')
  225. {
  226. $tree->actions['items'][] = array('key' => 'edit', 'text' => $this->lang->editor->newMethod, 'id' => $tree->id, 'data-url' => $this->getExtendLink($filePath, "newMethod"), 'data-on' => 'click', 'data-call' => 'openInEditWin', 'data-params' => 'event');
  227. }
  228. elseif($fileName == 'control.php')
  229. {
  230. $tree->actions['items'][] = array('key' => 'edit', 'text' => $this->lang->editor->newPage, 'id' => $tree->id, 'data-url' => inlink('newPage', "filePath=" . helper::safe64Encode($filePath)), 'data-on' => 'click', 'data-call' => 'openInEditWin', 'data-params' => 'event');
  231. }
  232. return $tree;
  233. }
  234. /**
  235. * Add link for file
  236. *
  237. * @param string $filePath
  238. * @param string $file
  239. * @access public
  240. * @return object
  241. */
  242. public function addLink4File($filePath, $file)
  243. {
  244. $tree = new stdClass();
  245. $tree->id = md5($file);
  246. $tree->name = $file;
  247. $tree->text = $file;
  248. $tree->actions['items'] = array();
  249. if(strpos($filePath, DS . 'ext' . DS) !== false)
  250. {
  251. $tree->actions['items'][] = array('key' => 'edit', 'text' => $this->lang->editor->edit, 'id' => $tree->id, 'data-url' => $this->getExtendLink($filePath, "edit"), 'data-on' => 'click', 'data-call' => 'openInEditWin', 'data-params' => 'event');
  252. $tree->actions['items'][] = array('key' => 'delete', 'text' => $this->lang->delete, 'id' => $tree->id, 'className' => 'ajax-submit', 'url' => inlink('delete', 'path=' . helper::safe64Encode($filePath)), 'data-confirm' => $this->lang->editor->deleteConfirm);
  253. }
  254. elseif(basename(dirname($filePath))== 'view')
  255. {
  256. $tree->actions['items'][] = array('key' => 'edit', 'text' => $this->lang->editor->override, 'id' => $tree->id, 'data-url' => $this->getExtendLink($filePath, "override"), 'data-on' => 'click', 'data-call' => 'openInEditWin', 'data-params' => 'event');
  257. $tree->actions['items'][] = array('key' => 'create', 'text' => $this->lang->editor->newHook, 'id' => $tree->id, 'data-url' => $this->getExtendLink($filePath, "newHook"), 'data-on' => 'click', 'data-call' => 'openInEditWin', 'data-params' => 'event');
  258. }
  259. else
  260. {
  261. $parentDir = basename(dirname($filePath));
  262. $action = 'extendOther';
  263. if($parentDir == 'control.php') $action = 'extendControl';
  264. if($parentDir == 'model.php') $action = 'extendModel';
  265. $tree->actions['items'][] = array('key' => 'edit', 'text' => $this->lang->editor->extend, 'id' => $tree->id, 'data-url' => $this->getExtendLink($filePath, $action), 'data-on' => 'click', 'data-call' => 'openInEditWin', 'data-params' => 'event');
  266. if($action != 'extendOther') $tree->actions['items'][] = array('key' => 'api', 'text' => $this->lang->editor->api, 'id' => $tree->id, 'data-url' => $this->getAPILink($filePath, $action), 'data-on' => 'click', 'data-call' => 'openInEditWin', 'data-params' => 'event');
  267. if($parentDir == 'lang') $tree->actions['items'][] = array('key' => 'newLang', 'text' => $this->lang->editor->newLang, 'id' => $tree->id, 'data-url' => $this->getExtendLink($filePath, "new" . str_replace('-', '_', basename($filePath, '.php'))), 'data-on' => 'click', 'data-call' => 'openInEditWin', 'data-params' => 'event');
  268. if(basename($filePath) == 'config.php') $tree->actions['items'][] = array('key' => 'newConfig', 'text' => $this->lang->editor->newConfig, 'id' => $tree->id, 'data-url' => $this->getExtendLink($filePath, "newConfig"), 'data-on' => 'click', 'data-call' => 'openInEditWin', 'data-params' => 'event');
  269. }
  270. return $tree;
  271. }
  272. /**
  273. * Get extend link
  274. *
  275. * @param string $filePath
  276. * @param string $action
  277. * @param string $isExtends
  278. * @access public
  279. * @return string
  280. */
  281. public function getExtendLink($filePath, $action, $isExtends = '')
  282. {
  283. return inlink('edit', "filePath=" . helper::safe64Encode($filePath) . "&action=$action&isExtends=$isExtends");
  284. }
  285. /**
  286. * Get api link.
  287. *
  288. * @param string $filePath
  289. * @param string $action
  290. * @access public
  291. * @return string
  292. */
  293. public function getAPILink($filePath, $action)
  294. {
  295. return helper::createLink('api', 'debug', "filePath=" . helper::safe64Encode($filePath) . "&action=$action");
  296. }
  297. /**
  298. * Save file to extension.
  299. *
  300. * @param string $filePath
  301. * @access public
  302. * @return string|bool
  303. */
  304. public function save($filePath)
  305. {
  306. /* Reduce expiration time for check safe file. */
  307. $this->config->safeFileTimeout = 15 * 60;
  308. $statusFile = $this->loadModel('common')->checkSafeFile();
  309. if($statusFile) return dao::$errors[] = sprintf($this->lang->editor->noticeOkFile, str_replace('\\', '/', $statusFile));
  310. $dirPath = dirname($filePath);
  311. $extFilePath = substr($filePath, 0, strpos($filePath, DS . 'ext' . DS) + 4);
  312. if(!is_dir($dirPath) and is_writable($extFilePath)) mkdir($dirPath, 0777, true);
  313. if(!is_dir($dirPath))
  314. {
  315. if(is_dir($extFilePath)) return dao::$errors[] = sprintf($this->lang->editor->notWritable, $extFilePath);
  316. return dao::$errors[] = sprintf($this->lang->editor->notExists, $extFilePath);
  317. }
  318. if(!is_writable($dirPath)) return dao::$errors[] = sprintf($this->lang->editor->notWritable, $extFilePath);
  319. if(strpos(strtolower(realpath($dirPath)), strtolower($this->app->getBasePath())) !== 0) return dao::$errors[] = $this->lang->editor->editFileError;
  320. $fileContent = $this->post->fileContent;
  321. $evils = array('eval', 'exec', 'passthru', 'proc_open', 'shell_exec', 'system', '$$', 'include', 'require', 'assert', 'javascript', 'onclick');
  322. $gibbedEvils = array('e v a l', 'e x e c', ' p a s s t h r u', ' p r o c _ o p e n', 's h e l l _ e x e c', 's y s t e m', '$ $', 'i n c l u d e', 'r e q u i r e', 'a s s e r t', 'j a v a s c r i p t', 'o n c l i c k');
  323. $fileContent = str_ireplace($gibbedEvils, $evils, $fileContent);
  324. if(function_exists('get_magic_quotes_gpc') and get_magic_quotes_gpc()) $fileContent = stripslashes($fileContent);
  325. file_put_contents($filePath, $fileContent);
  326. return true;
  327. }
  328. /**
  329. * Extend model.php and get file content.
  330. *
  331. * @param string $filePath
  332. * @access public
  333. * @return string
  334. */
  335. public function extendModel($filePath)
  336. {
  337. $className = basename(dirname(dirname($filePath)));
  338. if(!class_exists($className)) helper::import(dirname($filePath));
  339. $methodName = basename($filePath);
  340. $methodParam = $this->getParam($className, $methodName, 'Model');
  341. return <<<EOD
  342. <?php
  343. public function $methodName($methodParam)
  344. {
  345. return parent::$methodName($methodParam);
  346. }
  347. EOD;
  348. }
  349. /**
  350. * Extend control.php and get file content.
  351. *
  352. * @param string $filePath
  353. * @param string $isExtends
  354. * @access public
  355. * @return string
  356. */
  357. public function extendControl($filePath, $isExtends)
  358. {
  359. $className = basename(dirname(dirname($filePath)));
  360. if(!class_exists($className)) helper::import(dirname($filePath));
  361. $methodName = basename($filePath);
  362. if($isExtends == 'yes')
  363. {
  364. $methodParam = $this->getParam($className, $methodName);
  365. return <<<EOD
  366. <?php
  367. helper::importControl('$className');
  368. class my$className extends $className
  369. {
  370. public function $methodName($methodParam)
  371. {
  372. return parent::$methodName($methodParam);
  373. }
  374. }
  375. EOD;
  376. }
  377. else
  378. {
  379. $methodCode = $this->getMethodCode($className, $methodName);
  380. return <<<EOD
  381. <?php
  382. class $className extends control
  383. {
  384. $methodCode
  385. }
  386. EOD;
  387. }
  388. }
  389. /**
  390. * Add a control method.
  391. *
  392. * @param string $filePath
  393. * @access public
  394. * @return string
  395. */
  396. public function newControl($filePath)
  397. {
  398. $className = $this->getClassNameByPath($filePath);
  399. $methodName = basename($filePath, '.php');
  400. return <<<EOD
  401. <?php
  402. class $className extends control
  403. {
  404. public function $methodName()
  405. {
  406. }
  407. }
  408. EOD;
  409. }
  410. /**
  411. * Get method's parameters.
  412. *
  413. * @param string $className
  414. * @param string $methodName
  415. * @param string $ext
  416. * @access public
  417. * @return string
  418. */
  419. public function getParam($className, $methodName, $ext = '')
  420. {
  421. $method = new ReflectionMethod($className . $ext, $methodName);
  422. $methodParams = array();
  423. foreach($method->getParameters() as $param)
  424. {
  425. $methodParam = '$' . $param->getName();
  426. if($param->isOptional())
  427. {
  428. $defaultParam = $param->getDefaultValue();
  429. if(is_string($defaultParam)) $defaultParam = "'$defaultParam'";
  430. if(is_array($defaultParam) and empty($defaultParam)) $defaultParam = 'array()';
  431. if(is_null($defaultParam)) $defaultParam = 'null';
  432. $methodParam .= "=$defaultParam";
  433. }
  434. $methodParams[] = $methodParam;
  435. }
  436. return implode(', ', $methodParams);
  437. }
  438. /**
  439. * Get method code.
  440. *
  441. * @param string $className
  442. * @param string $methodName
  443. * @param string $ext value may be Model
  444. * @access public
  445. * @return string
  446. */
  447. public function getMethodCode($className, $methodName, $ext = '')
  448. {
  449. $method = new ReflectionMethod($className . $ext, $methodName);
  450. $fileName = $method->getFileName();
  451. $startLine = $method->getStartLine();
  452. $endLine = $method->getEndLine();
  453. $file = file($fileName);
  454. $code = '';
  455. for($i = $startLine - 1; $i <= $endLine; $i++) $code .= $file[$i];
  456. return $code;
  457. }
  458. /**
  459. * Get save path.
  460. *
  461. * @param string $filePath
  462. * @param string $action
  463. * @access public
  464. * @return string
  465. */
  466. public function getSavePath($filePath, $action)
  467. {
  468. $sourceFileName = basename($filePath);
  469. $fileExtension = 'php';
  470. if(strrpos($sourceFileName, '.') !== false) $fileExtension = substr($sourceFileName, strrpos($sourceFileName, '.') + 1);
  471. if(strtolower($action) == 'newjs') $fileExtension = 'js';
  472. if(strtolower($action) == 'newcss') $fileExtension = 'css';
  473. $fileName = empty($_POST['fileName']) ? '' : trim($this->post->fileName);
  474. $moduleName = $this->getClassNameByPath($filePath);
  475. $methodName = '';
  476. if(strpos('|newjs|newcss|', '|' . strtolower($action) . '|') !== false) $methodName = basename($filePath);
  477. if($fileName and (strpos($fileName, '.' . $fileExtension) !== (strlen($fileName) - strlen($fileExtension) - 1))) $fileName .= '.' . $fileExtension;
  478. $extPath = $this->app->getExtensionRoot() . 'custom' . DS . $moduleName . DS . 'ext' . DS;
  479. switch($action)
  480. {
  481. case 'extendModel':
  482. $fileName = empty($fileName) ? strtolower(basename($filePath)) . ".{$fileExtension}" : $fileName;
  483. return $extPath . 'model' . DS . $fileName;
  484. case 'extendControl':
  485. $fileName = strtolower(basename($filePath)) . ".{$fileExtension}";
  486. return $extPath . 'control' . DS . $fileName;
  487. case 'override':
  488. $fileName = basename($filePath);
  489. return $extPath . 'view' . DS . $fileName;
  490. case 'extendOther':
  491. $editName = basename($filePath);
  492. $fileName = empty($fileName) ? $editName: $fileName;
  493. if($editName == 'config.php') return $extPath . 'config' .DS . $fileName;
  494. if(strpos($editName, '.php') !== false) return $extPath . 'lang' . DS . basename($editName, ".{$fileExtension}") . DS . $fileName;
  495. return $extPath . $fileExtension . DS . basename($editName, ".{$fileExtension}") . DS . $fileName;
  496. default:
  497. if(empty($fileName)) return dao::$errors[] = $this->lang->editor->emptyFileName;
  498. $action = strtolower(str_replace('new', '', $action));
  499. if($action == 'method') return $extPath . basename($filePath, ".{$fileExtension}") . DS . $fileName;
  500. if($action == 'extend') return $filePath . DS . $fileName;
  501. if($action == 'hook') return $extPath . 'view' . DS . $fileName;
  502. if($action == 'config') return $extPath . 'config' . DS . $fileName;
  503. if($action == 'js') return $extPath . 'js' . DS . $methodName . DS . $fileName;
  504. if($action == 'css') return $extPath . 'css' . DS . $methodName . DS . $fileName;
  505. return $extPath . 'lang' . DS . str_replace('_', '-', $action) . DS . $fileName;
  506. }
  507. }
  508. /**
  509. * Get class name by path.
  510. *
  511. * @param string $filePath
  512. * @access public
  513. * @return string
  514. */
  515. public function getClassNameByPath($filePath)
  516. {
  517. $className = '';
  518. if(strpos($filePath, DS . 'module' . DS) !== false)
  519. {
  520. $className = strstr($filePath, DS . 'module' . DS);
  521. $className = substr($className, 0, strpos($className, DS, 9));
  522. }
  523. elseif(strpos($filePath, DS . 'ext' . DS) !== false)
  524. {
  525. $className = substr($filePath, 0, strpos($filePath, DS . 'ext' . DS));
  526. }
  527. elseif(strpos($filePath, DS . 'extension' . DS) !== false)
  528. {
  529. $className = strstr($filePath, DS . 'extension' . DS);
  530. $className = str_replace(DS . 'extension' . DS, '', $className);
  531. $className = substr($className, 0, strpos($className, DS, strpos($className, DS) + 1));
  532. }
  533. $className = basename($className);
  534. return $className;
  535. }
  536. }