zen.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. <?php
  2. /**
  3. * The zen file of extension module 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 Yuting Wang<wangyuting@easycorp.ltd>
  8. * @package extension
  9. * @link https://www.zentao.net
  10. */
  11. class extensionZen extends extension
  12. {
  13. /**
  14. * 安全性校验。
  15. * Check safe.
  16. *
  17. * @access protected
  18. * @return void
  19. */
  20. protected function checkSafe()
  21. {
  22. /* 判断是否要跳转到安全校验页面。 */
  23. $statusFile = $this->loadModel('common')->checkSafeFile();
  24. if($statusFile) die($this->fetch('extension', 'safe', 'statusFile=' . helper::safe64Encode($statusFile)));
  25. }
  26. /**
  27. * 根据插件代号获取指定的钩子文件地址。
  28. * Get hook file for install or uninstall.
  29. *
  30. * @param string $extension
  31. * @param string $hook preinstall|postinstall|preuninstall|postuninstall
  32. * @access protected
  33. * @return string|false
  34. */
  35. protected function getHookFile($extension, $hook)
  36. {
  37. $hookFile = $this->extension->pkgRoot . "$extension/hook/$hook.php";
  38. if(file_exists($hookFile)) return $hookFile;
  39. return false;
  40. }
  41. /**
  42. * 根据数据库数据获取依赖当前插件的其他插件。
  43. * Get depends extension by database.
  44. *
  45. * @param string $extension
  46. * @access protected
  47. * @return array
  48. */
  49. protected function getDependsByDB($extension)
  50. {
  51. $extensionInfo = $this->extension->getInfoFromDB($extension);
  52. $dependsList = $this->extension->getDependsExtension($extension);
  53. $result = array();
  54. if($dependsList)
  55. {
  56. foreach($dependsList as $dependsExtension)
  57. {
  58. $depends = json_decode($dependsExtension->depends, true);
  59. if(empty($depends[$extension])) continue;
  60. if($this->compareForLimit($extensionInfo->version, $depends[$extension])) $result[] = $dependsExtension->name;
  61. }
  62. }
  63. return $result;
  64. }
  65. /**
  66. * 插件安装前的合规校验。
  67. * Check before installation.
  68. *
  69. * @param string $extension
  70. * @param string $ignoreCompatible
  71. * @param string $ignoreLink
  72. * @param string $overrideFile
  73. * @param string $overrideLink
  74. * @param string $installType
  75. * @access protected
  76. * @return bool
  77. */
  78. protected function checkExtension($extension, $ignoreCompatible, $ignoreLink, $overrideFile, $overrideLink, $installType)
  79. {
  80. /* 安全性校验。 */
  81. $statusFile = $this->loadModel('common')->checkSafeFile();
  82. if($statusFile)
  83. {
  84. $okFile = str_replace($this->app->getBasePath(), '', $statusFile);
  85. $this->view->error = sprintf($this->lang->noticeOkFile, $okFile, $statusFile);
  86. return false;
  87. }
  88. /* Check the package file exists or not. */
  89. $packageFile = $this->extension->getPackageFile($extension);
  90. if(!file_exists($packageFile))
  91. {
  92. $this->view->error = sprintf($this->lang->extension->errorPackageNotFound, $packageFile);
  93. return false;
  94. }
  95. /* Checking the extension paths. */
  96. $return = $this->checkExtensionPaths($extension);
  97. if($this->session->dirs2Created == false) $this->session->set('dirs2Created', $return->dirs2Created, 'admin'); // Save the dirs to be created.
  98. if($return->result != 'ok')
  99. {
  100. $this->view->error = $return->errors;
  101. return false;
  102. }
  103. /* Extract the package. */
  104. $return = $this->extractPackage($extension);
  105. if($return->result != 'ok')
  106. {
  107. $this->view->error = sprintf($this->lang->extension->errorExtracted, $packageFile, $return->error);
  108. return false;
  109. }
  110. /* Get condition. e.g. zentao|depends|conflicts. */
  111. $condition = $this->extension->getCondition($extension);
  112. $installedExts = $this->extension->getLocalExtensions('installed');
  113. if(!$this->checkCompatible($extension, $condition, $ignoreCompatible, $ignoreLink, $installType)) return false;
  114. if(!$this->checkConflicts($condition, $installedExts)) return false;
  115. if(!$this->checkDepends($condition, $installedExts)) return false;
  116. if(!$this->checkFile($extension, $overrideFile, $overrideLink)) return false;
  117. return true;
  118. }
  119. /**
  120. * 检查插件包的目录结构是否禅道目录结构冲突。
  121. * Check files in the package conflicts with exists files or not.
  122. *
  123. * @param string $extension
  124. * @access protected
  125. * @return object
  126. */
  127. protected function checkFileConflict($extension)
  128. {
  129. $return = new stdclass();
  130. $return->result = 'ok';
  131. $return->error = '';
  132. $appRoot = $this->app->getAppRoot();
  133. $extensionFiles = $this->extension->getFilesFromPackage($extension);
  134. foreach($extensionFiles as $extensionFile)
  135. {
  136. $compareFile = $appRoot . str_replace($this->extension->pkgRoot . $extension . DS, '', $extensionFile);
  137. if(!file_exists($compareFile)) continue;
  138. if(md5_file($extensionFile) != md5_file($compareFile)) $return->error .= $compareFile . '<br />';
  139. }
  140. if($return->error != '') $return->result = 'fail';
  141. return $return;
  142. }
  143. /**
  144. * 插件插件的hook文件到禅道目录。
  145. * Copy hookFiles to zentao.
  146. *
  147. * @param string $extension
  148. * @access public
  149. * @return void
  150. */
  151. public function copyHookFiles($extension)
  152. {
  153. $extHookPath = $this->extension->pkgRoot . $extension . DS . 'hook' . DS;
  154. $hookPath = $this->app->getBasePath() . DS . 'hook' . DS;
  155. if(!is_dir($extHookPath)) return;
  156. if(!is_dir($hookPath)) return;
  157. foreach(glob($hookPath . '*') as $hookFile) $this->extension->classFile->removeFile($hookFile);
  158. $this->extension->classFile->copyDir($extHookPath, $hookPath);
  159. }
  160. /**
  161. * 执行插件安装程序。
  162. * Install extension.
  163. *
  164. * @param string $extension
  165. * @param string $type
  166. * @param string $upgrade
  167. * @access protected
  168. * @return void
  169. */
  170. protected function installExtension($extension, $type, $upgrade)
  171. {
  172. /* The preInstall hook file. */
  173. $hook = $upgrade == 'yes' ? 'preupgrade' : 'preinstall';
  174. if($preHookFile = $this->getHookFile($extension, $hook)) include $preHookFile;
  175. /* Save to database. */
  176. $this->extension->saveExtension($extension, $type);
  177. /* Copy files to target directory. */
  178. $this->view->files = $this->copyPackageFiles($extension);
  179. /* Execute the install.sql. */
  180. $needExecuteDB = file_exists($this->extension->getDBFile($extension, 'install'));
  181. if($upgrade == 'no' && $needExecuteDB)
  182. {
  183. $return = $this->extension->executeDB($extension, 'install');
  184. if($return->result != 'ok')
  185. {
  186. $this->view->error = sprintf($this->lang->extension->errorInstallDB, $return->error);
  187. return false;
  188. }
  189. }
  190. /* Update status, dirs, files and installed time. */
  191. $data = array();
  192. $data['code'] = $extension;
  193. $data['status'] = 'installed';
  194. $data['dirs'] = $this->session->dirs2Created;
  195. $data['files'] = $this->view->files;
  196. $data['installedTime'] = helper::now();
  197. $this->extension->updateExtension($data);
  198. $this->session->set('dirs2Created', array(), 'admin'); // clean the session.
  199. $this->view->downloadedPackage = false;
  200. /* The postInstall hook file. */
  201. $hook = $upgrade == 'yes' ? 'postupgrade' : 'postinstall';
  202. if($postHookFile = $this->getHookFile($extension, $hook)) include $postHookFile;
  203. return true;
  204. }
  205. /**
  206. * 解压插件包到pkg目录。
  207. * Extract an extension.
  208. *
  209. * @param string $extension
  210. * @access protected
  211. * @return object
  212. */
  213. protected function extractPackage($extension)
  214. {
  215. $return = new stdclass();
  216. $return->result = 'ok';
  217. $return->error = '';
  218. /* 验证extension目录是否允许写入。 */
  219. $extensionRoot = $this->app->getExtensionRoot();
  220. if(is_dir($extensionRoot) && !is_writable($extensionRoot))
  221. {
  222. return (object)array('result' => 'fail', 'error' => strip_tags(sprintf($this->lang->extension->errorDownloadPathNotWritable, $extensionRoot, $extensionRoot)));
  223. }
  224. /* try remove pre extracted files. */
  225. $extensionPath = $this->extension->pkgRoot . $extension;
  226. if(is_dir($extensionPath)) $this->extension->classFile->removeDir($extensionPath);
  227. /* 获取插件包所在目录。 */
  228. $packageFile = $this->extension->getPackageFile($extension);
  229. /* 解压插件包到extensionPath目录。 */
  230. $this->app->loadClass('pclzip', true);
  231. $zip = new pclzip($packageFile);
  232. $files = $zip->listContent();
  233. $pathinfo = pathinfo($files[0]['filename']);
  234. $removePath = isset($pathinfo['dirname']) && $pathinfo['dirname'] != '.' ? $pathinfo['dirname'] : $pathinfo['basename'];
  235. /* 修复漏洞:过滤不安全的文件。 Fix vulnerability: filter unsafe files. */
  236. $unsafePatterns = '/\.\.[\/\\\]|^[^\w]|\w:[\/\\\]/'; // 非法路径包括: ../ | ..\ | /opt/ | c:/ | c:\
  237. $filteredFiles = array();
  238. $lastIndex = 0;
  239. foreach($files as $file)
  240. {
  241. $filename = $file['filename'];
  242. $lastIndex = $file['index'];
  243. if(preg_match($unsafePatterns, $filename)) $filteredFiles[] = $lastIndex;
  244. }
  245. $indexes = array();
  246. $startIndex = 0;
  247. foreach($filteredFiles as $index)
  248. {
  249. $preIndex = $index - 1;
  250. if($preIndex < 0) return $return;
  251. $indexes[] = $preIndex == $startIndex ? $startIndex : $startIndex . '-' . $preIndex;
  252. $startIndex = $index + 1;
  253. }
  254. if($startIndex <= $lastIndex) $indexes[] = $startIndex == $lastIndex ? $startIndex : $startIndex . '-' . $lastIndex;
  255. if($indexes && $zip->extract(PCLZIP_OPT_PATH, $extensionPath, PCLZIP_OPT_BY_INDEX, $indexes, PCLZIP_OPT_REMOVE_PATH, $removePath) == 0)
  256. {
  257. $return->result = 'fail';
  258. $return->error = $zip->errorInfo(true);
  259. }
  260. return $return;
  261. }
  262. /**
  263. * 复制插件包文件到禅道目录。
  264. * Copy package files.
  265. *
  266. * @param string $extension
  267. * @access protected
  268. * @return array
  269. */
  270. protected function copyPackageFiles($extension)
  271. {
  272. $appRoot = $this->app->getAppRoot();
  273. $extensionDir = $this->extension->pkgRoot . $extension . DS;
  274. $paths = scandir($extensionDir);
  275. $copiedFiles = array();
  276. foreach($paths as $path)
  277. {
  278. if($path == 'db' || $path == 'doc' || $path == 'hook' || $path == '..' || $path == '.') continue;
  279. $result = $this->extension->classFile->copyDir($extensionDir . $path, $appRoot . $path, true);
  280. $copiedFiles = zget($result, 'copiedFiles', array());
  281. }
  282. foreach($copiedFiles as $key => $copiedFile)
  283. {
  284. $copiedFiles[$copiedFile] = md5_file($copiedFile);
  285. unset($copiedFiles[$key]);
  286. }
  287. return $copiedFiles;
  288. }
  289. /**
  290. * 卸载插件前备份即将删除的表。
  291. * Backup db when uninstall extension.
  292. *
  293. * @param string $extension
  294. * @access protected
  295. * @return string|false
  296. */
  297. protected function backupDB($extension)
  298. {
  299. $sqls = file_get_contents($this->extension->getDBFile($extension, 'uninstall'));
  300. $sqls = explode(';', $sqls);
  301. /* Get tables for backup. */
  302. $backupTables = array();
  303. foreach($sqls as $sql)
  304. {
  305. $sql = str_replace('zt_', $this->config->db->prefix, $sql);
  306. $sql = preg_replace('/IF EXISTS /i', '', trim($sql));
  307. if(preg_match('/TABLE +`?([^` ]*)`?/i', $sql, $out))
  308. {
  309. if(!empty($out[1])) $backupTables[$out[1]] = $out[1];
  310. }
  311. }
  312. /* Back up database. */
  313. $zdb = $this->app->loadClass('zdb');
  314. if($backupTables)
  315. {
  316. $backupFile = $this->app->getTmpRoot() . $extension . '.' . date('Ymd') . '.sql';
  317. $result = $zdb->dump($backupFile, $backupTables);
  318. if($result->result) return $backupFile;
  319. }
  320. return false;
  321. }
  322. /**
  323. * 标记此插件是否被禁用。
  324. * Mark package active or disabled
  325. *
  326. * @param string $extension
  327. * @param string $action disabled|active
  328. * @access protected
  329. * @return bool
  330. */
  331. protected function togglePackageDisable($extension, $action = 'disabled')
  332. {
  333. if(!is_dir($this->extension->pkgRoot . $extension)) return true;
  334. $disabledFile = $this->extension->pkgRoot . $extension . DS . 'disabled';
  335. if($action == 'disabled') touch($disabledFile);
  336. if($action == 'active' && file_exists($disabledFile)) unlink($disabledFile);
  337. return true;
  338. }
  339. /**
  340. * 检查安装前的文件夹权限。
  341. * Check extension files.
  342. *
  343. * @param string $extension
  344. * @access private
  345. * @return object
  346. */
  347. private function checkExtensionPaths($extension)
  348. {
  349. $checkResult = new stdclass();
  350. $checkResult->result = 'ok';
  351. $checkResult->errors = '';
  352. $checkResult->mkdirCommands = '';
  353. $checkResult->chmodCommands = '';
  354. $checkResult->dirs2Created = array();
  355. /* 如果extension目录没有创建pkg文件夹并且创建pkg文件夹失败。 */
  356. if(!is_dir($this->extension->pkgRoot) && !mkdir($this->extension->pkgRoot))
  357. {
  358. $checkResult->errors .= sprintf($this->lang->extension->errorTargetPathNotExists, $this->extension->pkgRoot) . '<br />';
  359. $checkResult->mkdirCommands .= "sudo mkdir -p {$this->extension->pkgRoot}<br />";
  360. $checkResult->chmodCommands .= "sudo chmod -R 777 {$this->pkgRoot}<br />";
  361. }
  362. /* 如果extension目录有pkg文件夹但是pkg文件夹不可写。 */
  363. if(is_dir($this->extension->pkgRoot) && !is_writable($this->extension->pkgRoot))
  364. {
  365. $checkResult->errors .= sprintf($this->lang->extension->errorTargetPathNotWritable, $this->extension->pkgRoot) . '<br />';
  366. $checkResult->chmodCommands .= "sudo chmod -R 777 {$this->extension->pkgRoot}<br />";
  367. }
  368. /* 检查插件目录对应的禅道目录权限。 */
  369. $checkResult = $this->checkExtractPath($extension, $checkResult);
  370. if($checkResult->errors) $checkResult->result = 'fail';
  371. $checkResult->mkdirCommands = empty($checkResult->mkdirCommands) ? '' : '<code>' . str_replace('/', DIRECTORY_SEPARATOR, $checkResult->mkdirCommands) . '</code>';
  372. $checkResult->errors .= $this->lang->extension->executeCommands . $checkResult->mkdirCommands;
  373. if(PHP_OS == 'Linux') $checkResult->errors .= empty($checkResult->chmodCommands) ? '' : '<code>' . $checkResult->chmodCommands . '</code>';
  374. return $checkResult;
  375. }
  376. /**
  377. * 检查安装插件时对应的禅道目录权限。
  378. * Check extension path read-write permission.
  379. *
  380. * @param string $extension
  381. * @param object $checkResult
  382. * @access private
  383. * @return object
  384. */
  385. private function checkExtractPath($extension, $checkResult)
  386. {
  387. $appRoot = $this->app->getAppRoot();
  388. $paths = $this->extension->getPathsFromPackage($extension);
  389. foreach($paths as $path)
  390. {
  391. if($path == 'db' || $path == 'doc') continue;
  392. $path = rtrim($appRoot . $path, '/');
  393. if(is_dir($path))
  394. {
  395. /* 检查插件包里的代码文件夹对应禅道目录是否可写。 */
  396. if(!is_writable($path))
  397. {
  398. $checkResult->errors .= sprintf($this->lang->extension->errorTargetPathNotWritable, $path) . '<br />';
  399. $checkResult->chmodCommands .= "sudo chmod -R 777 $path<br />";
  400. }
  401. }
  402. else
  403. {
  404. /* 检查插件包里的代码文件的父目录对应禅道目录是否可写。 */
  405. $parentDir = dirname($path);
  406. while(!file_exists($parentDir)) $parentDir = dirname($parentDir);
  407. if(!is_writable($parentDir))
  408. {
  409. $checkResult->errors .= sprintf($this->lang->extension->errorTargetPathNotWritable, $path) . '<br />';
  410. $checkResult->chmodCommands .= "sudo chmod -R 777 $path<br />";
  411. $checkResult->errors .= sprintf($this->lang->extension->errorTargetPathNotExists, $path) . '<br />';
  412. $checkResult->mkdirCommands .= "sudo mkdir -p $path<br />";
  413. }
  414. elseif(!mkdir($path, 0777, true))
  415. {
  416. /* 如果目录不存在并且创建目录失败。 */
  417. $checkResult->errors .= sprintf($this->lang->extension->errorTargetPathNotExists, $path) . '<br />';
  418. $checkResult->mkdirCommands .= "sudo mkdir -p $path<br />";
  419. }
  420. if(file_exists($path) && realpath($path) != $this->extension->pkgRoot) $checkResult->dirs2Created[] = $path;
  421. }
  422. }
  423. return $checkResult;
  424. }
  425. /**
  426. * 插件兼容性检查。
  427. * Extension compatibility check.
  428. *
  429. * @param string $extension
  430. * @param object $condition
  431. * @param string $ignoreCompatible
  432. * @param string $ignoreLink
  433. * @param string $installType
  434. * @access private
  435. * @return bool
  436. */
  437. private function checkCompatible($extension, $condition, $ignoreCompatible, $ignoreLink, $installType)
  438. {
  439. /* 不兼容版本检查。 */
  440. /* Check version incompatible */
  441. $incompatible = $condition->zentao['incompatible'];
  442. if($this->extension->checkVersion($incompatible))
  443. {
  444. $this->view->error = $this->lang->extension->errorIncompatible;
  445. return false;
  446. }
  447. /* 兼容版本检查。 */
  448. /* Check version compatible. */
  449. $zentaoCompatible = $condition->zentao['compatible'];
  450. if(!$this->extension->checkVersion((string)$zentaoCompatible) && $ignoreCompatible == 'no')
  451. {
  452. $this->view->error = sprintf($this->lang->extension->errorCheckIncompatible, $installType, $ignoreLink, $installType, inlink('obtain'));
  453. return false;
  454. }
  455. return true;
  456. }
  457. /**
  458. * 插件与插件之间的冲突检查。
  459. * Check conflicts.
  460. *
  461. * @param object $condition
  462. * @param array $installedExts
  463. * @access private
  464. * @return bool
  465. */
  466. private function checkConflicts($condition, $installedExts)
  467. {
  468. $conflicts = $condition->conflicts;
  469. if($conflicts)
  470. {
  471. $conflictsExt = '';
  472. foreach($conflicts as $code => $limit)
  473. {
  474. if(isset($installedExts[$code]))
  475. {
  476. if($this->compareForLimit($installedExts[$code]->version, $limit)) $conflictsExt .= $installedExts[$code]->name . " ";
  477. }
  478. }
  479. if($conflictsExt)
  480. {
  481. $this->view->error = sprintf($this->lang->extension->errorConflicts, $conflictsExt);
  482. return false;
  483. }
  484. }
  485. return true;
  486. }
  487. /**
  488. * 相关依赖插件检查。
  489. * Check depends.
  490. *
  491. * @param object $condition
  492. * @param array $installedExts
  493. * @access private
  494. * @return bool
  495. */
  496. private function checkDepends($condition, $installedExts)
  497. {
  498. $depends = $condition->depends;
  499. if($depends)
  500. {
  501. $dependsExt = '';
  502. foreach($depends as $code => $limit)
  503. {
  504. $noDepends = false;
  505. if(isset($installedExts[$code]))
  506. {
  507. if($this->compareForLimit($installedExts[$code]->version, $limit, 'noBetween')) $noDepends = true;
  508. }
  509. else
  510. {
  511. $noDepends = true;
  512. }
  513. $extVersion = '';
  514. if($limit != 'all')
  515. {
  516. $extVersion .= '(';
  517. if(!empty($limit['min'])) $extVersion .= '>=v' . $limit['min'];
  518. if(!empty($limit['max'])) $extVersion .= ' <=v' . $limit['max'];
  519. $extVersion .=')';
  520. }
  521. if($noDepends) $dependsExt .= $code . $extVersion . ' ' . html::a(inlink('obtain', 'type=bycode&param=' . helper::safe64Encode($code)), $this->lang->extension->installExt, '_blank') . '<br />';
  522. }
  523. if($noDepends)
  524. {
  525. $this->view->error = sprintf($this->lang->extension->errorDepends, $dependsExt);
  526. return false;
  527. }
  528. }
  529. return true;
  530. }
  531. /**
  532. * 插件文件和禅道已有文件的冲突检查。
  533. * Check files in the package conflicts with exists files or not.
  534. *
  535. * @param string $extension
  536. * @param string $overrideFile
  537. * @param string $overrideLink
  538. * @access private
  539. * @return bool
  540. */
  541. private function checkFile($extension, $overrideFile, $overrideLink)
  542. {
  543. if($overrideFile == 'no')
  544. {
  545. $return = $this->checkFileConflict($extension);
  546. if($return->result != 'ok')
  547. {
  548. $this->view->error = sprintf($this->lang->extension->errorFileConflicted, $return->error, $overrideLink, inlink('obtain'));
  549. return false;
  550. }
  551. }
  552. return true;
  553. }
  554. /**
  555. * 检查当前版本是否包含在指定版本内。
  556. * Compare for limit data.
  557. *
  558. * @param string $version
  559. * @param array|string $limit
  560. * @param string $type
  561. * @access private
  562. * @return bool
  563. */
  564. private function compareForLimit($version, $limit, $type = 'between')
  565. {
  566. $result = false;
  567. if(empty($limit)) return true;
  568. if($limit == 'all') return true;
  569. if(!empty($limit['min']) && $version >= $limit['min']) $result = true;
  570. if(!empty($limit['max']) && $version <= $limit['max']) $result = true;
  571. if(!empty($limit['max']) && $version > $limit['max'] && $result) $result = false;
  572. /* 如果取的不是被包含则返回取反的布尔值。 */
  573. if($type != 'between') return !$result;
  574. return $result;
  575. }
  576. }