model.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763
  1. <?php
  2. /**
  3. * The model file of extension 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) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Chunsheng Wang <chunsheng@cnezsoft.com>
  8. * @package extension
  9. * @version $Id$
  10. * @link https://www.zentao.net
  11. */
  12. class extensionModel extends model
  13. {
  14. /**
  15. * The extension manager version. Don't change it.
  16. */
  17. const EXT_MANAGER_VERSION = '1.3';
  18. /**
  19. * The api root.
  20. *
  21. * @var string
  22. * @access public
  23. */
  24. public $apiRoot;
  25. /**
  26. * The package root.
  27. *
  28. * @var string
  29. * @access public
  30. */
  31. public $pkgRoot;
  32. /**
  33. * 构造函数。
  34. * The construct function.
  35. *
  36. * @access public
  37. * @return void
  38. */
  39. public function __construct()
  40. {
  41. parent::__construct();
  42. $this->apiRoot = $this->config->extension->apiRoot;
  43. $this->classFile = $this->app->loadClass('zfile');
  44. $this->pkgRoot = $this->app->getExtensionRoot() . 'pkg' . DS;
  45. }
  46. /**
  47. * 根据状态获取本地安装的插件。
  48. * Get extensions by status.
  49. *
  50. * @param string $status
  51. * @access public
  52. * @return array
  53. */
  54. public function getLocalExtensions($status)
  55. {
  56. $extensions = $this->dao->select('*')->from(TABLE_EXTENSION)->where('status')->in($status)->fi()->fetchAll('code', false);
  57. foreach($extensions as $extension)
  58. {
  59. if($extension->site && stripos(strtolower($extension->site), 'http') === false) $extension->site = 'http://' . $extension->site;
  60. }
  61. return $extensions;
  62. }
  63. /**
  64. * 根据插件代号从数据库获取插件信息。
  65. * Get extension info from database.
  66. *
  67. * @param string $extension
  68. * @access public
  69. * @return object
  70. */
  71. public function getInfoFromDB($extension)
  72. {
  73. return $this->dao->select('*')->from(TABLE_EXTENSION)->where('code')->eq($extension)->fetch();
  74. }
  75. /**
  76. * 获取可能依赖此插件的其他插件。
  77. * Get other extensions that may depend on this extension.
  78. *
  79. * @param string $extension
  80. * @access public
  81. * @return array
  82. */
  83. public function getDependsExtension($extension)
  84. {
  85. return $this->dao->select('*')->from(TABLE_EXTENSION)->where('depends')->like("%$extension%")->andWhere('status')->ne('available')->fetchAll();
  86. }
  87. /**
  88. * 根据插件代号从插件包获取插件信息。
  89. * Get info of an extension from the package file.
  90. *
  91. * @param string $extension
  92. * @access public
  93. * @return object
  94. */
  95. public function getInfoFromPackage($extension)
  96. {
  97. /* Init the data. */
  98. $data = new stdclass();
  99. $data->name = $extension;
  100. $data->code = $extension;
  101. $data->version = 'unknown';
  102. $data->author = 'unknown';
  103. $data->desc = $extension;
  104. $data->site = 'unknown';
  105. $data->license = 'unknown';
  106. $data->zentaoCompatible = '';
  107. $data->type = '';
  108. $data->depends = '';
  109. $info = $this->parseExtensionCFG($extension);
  110. foreach($info as $key => $value)
  111. {
  112. if(isset($data->$key)) $data->$key = is_null($value) ? '' : $value;
  113. }
  114. if(isset($info->zentaoversion)) $data->zentaoCompatible = $info->zentaoversion;
  115. if(isset($info->zentao['compatible'])) $data->zentaoCompatible = $info->zentao['compatible'];
  116. if(isset($info->depends)) $data->depends = json_encode($info->depends);
  117. return $data;
  118. }
  119. /**
  120. * 根据插件代号获取插件包的兼容性信息。
  121. * Get the extension's condition.
  122. *
  123. * @param string $extenstion
  124. * @access public
  125. * @return object
  126. * @param string $extension
  127. */
  128. public function getCondition($extension)
  129. {
  130. $info = $this->parseExtensionCFG($extension);
  131. $condition = new stdclass();
  132. $condition->zentao = array('compatible' => '', 'incompatible' => '');
  133. $condition->depends = '';
  134. $condition->conflicts = '';
  135. if(isset($info->zentao)) $condition->zentao = $info->zentao;
  136. if(isset($info->depends)) $condition->depends = $info->depends;
  137. if(isset($info->conflicts)) $condition->conflicts = $info->conflicts;
  138. /* zentaoversion和zentaoVersion哪个有值取那个。 */
  139. if(isset($info->zentaoVersion)) $condition->zentao['compatible'] = $info->zentaoVersion;
  140. if(isset($info->zentaoversion)) $condition->zentao['compatible'] = $info->zentaoversion;
  141. return $condition;
  142. }
  143. /**
  144. * 调用禅道官网接口获取插件的分类。
  145. * Get extension modules from the api.
  146. *
  147. * @access public
  148. * @return array|bool
  149. */
  150. public function getModulesByAPI()
  151. {
  152. $requestType = helper::safe64Encode($this->config->requestType);
  153. $webRoot = helper::safe64Encode($this->config->webRoot, '', false, true);
  154. $apiURL = "{$this->apiRoot}apiGetmodules-{$requestType}-{$webRoot}.json";
  155. $data = $this->fetchAPI($apiURL);
  156. if(isset($data->newmodules)) return $data->newmodules;
  157. return false;
  158. }
  159. /**
  160. * 调用禅道官网接口获取插件的版本。
  161. * Get versions for some extensions.
  162. *
  163. * @param string $extensions
  164. * @access public
  165. * @return array|bool
  166. */
  167. public function getVersionsByAPI($extensions)
  168. {
  169. $extensions = helper::safe64Encode($extensions);
  170. $apiURL = "{$this->apiRoot}apiGetVersions-{$extensions}.json";
  171. $data = $this->fetchAPI($apiURL);
  172. if(isset($data->versions)) return (array)$data->versions;
  173. return false;
  174. }
  175. /**
  176. * 调用禅道官网接口获取插件的列表。
  177. * Get extensions by some condition.
  178. *
  179. * @param string $type
  180. * @param string $param
  181. * @param int $recTotal
  182. * @param int $recPerPage
  183. * @param int $pageID
  184. * @access public
  185. * @return object|bool
  186. */
  187. public function getExtensionsByAPI($type, $param, $recTotal = 0, $recPerPage = 20, $pageID = 1)
  188. {
  189. $apiURL = $this->apiRoot . "apiGetExtensions-$type-$param-$recTotal-$recPerPage-$pageID.json";
  190. $data = $this->fetchAPI($apiURL);
  191. if(isset($data->extensions))
  192. {
  193. foreach($data->extensions as $extension)
  194. {
  195. $extension->currentRelease = isset($extension->compatibleRelease) ? $extension->compatibleRelease : $extension->latestRelease;
  196. $extension->currentRelease->compatible = isset($extension->compatibleRelease);
  197. }
  198. return $data;
  199. }
  200. return false;
  201. }
  202. /**
  203. * 根据插件代号获取插件包里的文件夹列表。
  204. * Get paths from an extension package.
  205. *
  206. * @param string $extension
  207. * @access public
  208. * @return array
  209. */
  210. public function getPathsFromPackage($extension)
  211. {
  212. $paths = array();
  213. $packageFile = $this->getPackageFile($extension);
  214. /* Get files from the package file. */
  215. $this->app->loadClass('pclzip', true);
  216. $zip = new pclzip($packageFile);
  217. $files = $zip->listContent();
  218. if($files)
  219. {
  220. foreach($files as $file)
  221. {
  222. $file = (object)$file;
  223. if($file->folder) continue;
  224. $file->filename = substr($file->filename, strpos($file->filename, '/') + 1);
  225. $paths[] = dirname($file->filename);
  226. }
  227. }
  228. return array_unique($paths);
  229. }
  230. /**
  231. * 根据插件代号获取插件包里除db和doc目录外的文件列表。
  232. * Get all files from a package.
  233. *
  234. * @param string $extension
  235. * @access public
  236. * @return array
  237. */
  238. public function getFilesFromPackage($extension)
  239. {
  240. $extensionDir = $this->pkgRoot . $extension;
  241. return $this->classFile->readDir($extensionDir, array('db', 'doc'));
  242. }
  243. /**
  244. * 根据插件代号获取上传的插件包路径。
  245. * Get the full path of the zip file of a extension.
  246. *
  247. * @param string $extension
  248. * @access public
  249. * @return string
  250. */
  251. public function getPackageFile($extension)
  252. {
  253. return $this->app->getTmpRoot() . 'extension/' . $extension . '.zip';
  254. }
  255. /**
  256. * 根据插件代号获取数据库执行文件。
  257. * Get the install db file.
  258. *
  259. * @param string $extension
  260. * @param string $method install|upgrade
  261. * @access public
  262. * @return string
  263. */
  264. public function getDBFile($extension, $method = 'install')
  265. {
  266. return $this->pkgRoot . "$extension/db/$method.sql";
  267. }
  268. /**
  269. * 获取插件到期时间。
  270. * Get extension expire date.
  271. *
  272. * @param object $extension
  273. * @access public
  274. * @return string
  275. */
  276. public function getExpireDate($extension)
  277. {
  278. $licencePath = $this->app->getConfigRoot() . 'license/';
  279. $today = date('Y-m-d');
  280. $expiredDate = '';
  281. $licenceOrderFiles = glob($licencePath . 'order*.txt');
  282. foreach($licenceOrderFiles as $licenceOrderFile)
  283. {
  284. /* 找到和当前插件代号匹配的授权文件。 */
  285. if(stripos($licenceOrderFile, "{$extension->code}{$extension->version}.txt") === false) continue;
  286. $order = file_get_contents($licenceOrderFile);
  287. $order = unserialize($order);
  288. /* 如果是终生版授权,直接返回life。 */
  289. if($order->type == 'life') return 'life';
  290. /* 获取授权总时长。 */
  291. $days = 0;
  292. if($order->type == 'demo') $days = 31; // 试用授权时长31天。
  293. if($order->type == 'year') $days = 365; // 一年版授权时长365天。
  294. if(!$days && isset($order->days)) $days = $order->days; // 自定义授权时长根据days字段设置。
  295. /* 根据授权时长和购买插件时间获取到期时间。 */
  296. $startDate = !helper::isZeroDate($order->paidDate) ? $order->paidDate : $order->createdDate;
  297. if($days) $expiredDate = date('Y-m-d', strtotime($startDate) + $days * 24 * 3600); // 如果授权时长是0则代表是不限时长的授权。
  298. }
  299. return $expiredDate;
  300. }
  301. /**
  302. * 获取即将到期和已过期的插件列表。
  303. * Get plugins that are about to expire or have expired.
  304. *
  305. * @param bool $isGroup 是否分组
  306. * @access public
  307. * @return array
  308. */
  309. public function getExpiringPlugins($isGroup = false)
  310. {
  311. $extensions = $this->getLocalExtensions('installed');
  312. $plugins = $isGroup ? array('expiring' => array(), 'expired' => array()) : array();
  313. $today = helper::today();
  314. foreach($extensions as $extension)
  315. {
  316. $expiredDate = $this->getExpireDate($extension);
  317. if(!empty($expiredDate) && $expiredDate != 'life')
  318. {
  319. $dateDiff = helper::diffDate($expiredDate, $today);
  320. if($isGroup)
  321. {
  322. /* 分组获取即将过期和已过期的插件列表。 */
  323. if($dateDiff == 30 || $dateDiff == 14 || ($dateDiff <= 7 && $dateDiff >= 0)) $plugins['expiring'][] = $extension->name;
  324. if($dateDiff <= -1) $plugins['expired'][] = $extension->name;
  325. }
  326. else
  327. {
  328. /* 获取即将过期和已过期的插件列表。 */
  329. if($dateDiff == 30 || $dateDiff == 14 || $dateDiff <= 7) $plugins[] = $extension->name;
  330. }
  331. }
  332. }
  333. return $plugins;
  334. }
  335. /**
  336. * 根据插件的当前版本号返回插件的当前兼容版本。
  337. * Check incompatible extension
  338. *
  339. * @param array $versions
  340. * @access public
  341. * @return array
  342. */
  343. public function checkIncompatible($versions)
  344. {
  345. $apiURL = $this->apiRoot . 'apiCheckIncompatible' . '.json?versions=' . helper::safe64Encode(json_encode($versions));
  346. $data = $this->fetchAPI($apiURL);
  347. if(isset($data->incompatibleExts)) return (array)$data->incompatibleExts;
  348. return array();
  349. }
  350. /**
  351. * 检查当前禅道版本是否包含在指定版本号中。
  352. * Check the extension's version is compatibility for zentao version
  353. *
  354. * @param string $version
  355. * @access public
  356. * @return bool
  357. */
  358. public function checkVersion($version)
  359. {
  360. if($version == 'all') return true;
  361. $version = explode(',', $version);
  362. if(in_array($this->config->version, $version)) return true;
  363. return false;
  364. }
  365. /**
  366. * 根据插件代号从插件包获取配置信息。
  367. * Parse extension's config file.
  368. *
  369. * @param string $extension
  370. * @access public
  371. * @return object
  372. */
  373. public function parseExtensionCFG($extension)
  374. {
  375. /* First, try ini file. before 2.5 version. */
  376. $infoFile = $this->pkgRoot . "$extension/doc/copyright.txt";
  377. if(file_exists($infoFile)) return (object)parse_ini_file($infoFile);
  378. /**
  379. * Then try parse yaml file. since 2.5 version.
  380. */
  381. /* Try the yaml of current lang, then try en. */
  382. $info = new stdclass();
  383. $lang = $this->app->getClientLang();
  384. $infoFile = $this->pkgRoot . "$extension/doc/$lang.yaml";
  385. if(!file_exists($infoFile)) $infoFile = $this->pkgRoot . "$extension/doc/en.yaml";
  386. if(!file_exists($infoFile)) return $info;
  387. /* Load the yaml file and parse it into object. */
  388. $spyc = $this->app->loadClass('spyc');
  389. $info = (object)$spyc->loadFile($infoFile);
  390. if(isset($info->releases))
  391. {
  392. $info->version = key($info->releases);
  393. foreach(array_keys($info->releases) as $version)
  394. {
  395. if(version_compare($info->version, $version, '<')) $info->version = $version;
  396. }
  397. foreach($info->releases[$info->version] as $key => $value) $info->$key = $value;
  398. }
  399. return $info;
  400. }
  401. /**
  402. * 生成授权协议内容。
  403. * Process license. If is opensource return the full text of it.
  404. *
  405. * @param string $license apache|bsd|gpl|lgpl|mit|自定义内容
  406. * @access public
  407. * @return string
  408. */
  409. public function processLicense($license)
  410. {
  411. if(strlen($license) > 10) return $license; // 大于10个字符的就代表是自定义协议内容,直接返回即可.
  412. $licenseFile = dirname(__FILE__) . '/license/' . strtolower($license) . '.txt';
  413. if(file_exists($licenseFile)) return file_get_contents($licenseFile);
  414. return $license;
  415. }
  416. /**
  417. * 删除安装的插件文件并返回错误提示。
  418. * Remove an extension.
  419. *
  420. * @param string $extension
  421. * @access public
  422. * @return array
  423. */
  424. public function removePackage($extension)
  425. {
  426. $extension = $this->getInfoFromDB($extension);
  427. if(empty($extension)) return array();
  428. if($extension->type == 'patch') return array(); // 无法删除补丁类型的插件包。
  429. $removeFilesTips = $this->removeExtensionFiles($extension->files);
  430. $removeDirsTips = $this->removeExtensionDirs($extension->dirs);
  431. /* Clean model cache files. */
  432. $this->cleanModelCache();
  433. return array_merge($removeFilesTips, $removeDirsTips);
  434. }
  435. /**
  436. * 清除插件并删除文件。
  437. * Erase an extension's package file.
  438. *
  439. * @param string $extension
  440. * @access public
  441. * @return array
  442. */
  443. public function erasePackage($extension)
  444. {
  445. $this->dao->delete()->from(TABLE_EXTENSION)->where('code')->eq($extension)->exec();
  446. $packageFile = $this->getPackageFile($extension);
  447. if(!file_exists($packageFile)) return array();
  448. /* Remove the zip file. */
  449. $removeCommands = array();
  450. if(file_exists($packageFile) && !@unlink($packageFile))
  451. {
  452. $removeCommands[] = PHP_OS == 'Linux' ? "rm -fr $packageFile" : "del $packageFile";
  453. }
  454. /* Remove the extracted files. */
  455. $extractedDir = $this->pkgRoot . $extension;
  456. if($extractedDir && $extractedDir != '/' && !$this->classFile->removeDir($extractedDir))
  457. {
  458. $removeCommands[] = PHP_OS == 'Linux' ? "rm -fr $extractedDir" : "rmdir $extractedDir /s";
  459. }
  460. return $removeCommands;
  461. }
  462. /**
  463. * 执行插件包中安装或者卸载时的SQL语句。
  464. * Execute the extension db.
  465. *
  466. * @param string $extension
  467. * @param string $method install|uninstall
  468. * @access public
  469. * @return object
  470. */
  471. public function executeDB($extension, $method = 'install')
  472. {
  473. $result = new stdclass();
  474. $result->result = 'ok';
  475. $result->error = '';
  476. /* 获取安装或者卸载对应的SQL文件。 */
  477. $dbFile = $this->getDBFile($extension, $method);
  478. if(!file_exists($dbFile)) return $result;
  479. /* 获取文件中的SQL语句。 */
  480. $sqls = file_get_contents($this->getDBFile($extension, $method));
  481. $sqls = explode(';', $sqls);
  482. $this->loadModel('install');
  483. $ignoreCode = '|1050|1060|1062|1091|1169|';
  484. foreach($sqls as $sql)
  485. {
  486. $sql = trim($sql);
  487. if(empty($sql)) continue;
  488. $sql = $this->install->replaceContantsInSQL($sql);
  489. $sql = $this->install->appendMySQLTableOptions($sql);
  490. try
  491. {
  492. $this->dbh->query($sql);
  493. $this->dao->setTableCache($sql);
  494. }
  495. catch(PDOException $e)
  496. {
  497. $errorInfo = $e->errorInfo;
  498. $errorCode = $errorInfo ? $errorInfo[1] : '';
  499. if(strpos($ignoreCode, "|$errorCode|") === false) $result->error .= '<p>' . $e->getMessage() . "<br />THE SQL IS: $sql</p>";
  500. }
  501. }
  502. if($result->error) $result->result = 'fail';
  503. return $result;
  504. }
  505. /**
  506. * 保存插件信息到数据库。
  507. * Save the extension to database.
  508. *
  509. * @param string $code
  510. * @param string $type
  511. * @access public
  512. * @return bool
  513. */
  514. public function saveExtension($code, $type)
  515. {
  516. //从插件包中获取配置信息。
  517. $extension = $this->getInfoFromPackage($code);
  518. $extension->status = 'available'; // 安装时的默认状态是已下载,后续安装完成后会变更。
  519. $extension->code = $code;
  520. $extension->type = empty($type) ? $extension->type : $type;
  521. $extension->installedTime = helper::now();
  522. $this->dao->replace(TABLE_EXTENSION)->data($extension)->exec();
  523. return !dao::isError();
  524. }
  525. /**
  526. * 更新插件信息到数据库。
  527. * Update an extension.
  528. *
  529. * @param array $extension
  530. * @access public
  531. * @return bool
  532. */
  533. public function updateExtension($extension)
  534. {
  535. if(empty($extension['code'])) return false;
  536. /* 安装插件时新增的文件夹目录。 */
  537. $appRoot = $this->app->getAppRoot();
  538. if(isset($extension['dirs']))
  539. {
  540. if($extension['dirs'])
  541. {
  542. foreach($extension['dirs'] as $key => $dir) $extension['dirs'][$key] = str_replace($appRoot, '', $dir);
  543. }
  544. $extension['dirs'] = json_encode($extension['dirs']);
  545. }
  546. /* 安装插件时复制的文件目录。 */
  547. if(isset($extension['files']))
  548. {
  549. foreach($extension['files'] as $fullFilePath => $md5)
  550. {
  551. $relativeFilePath = str_replace($appRoot, '', $fullFilePath);
  552. $extension['files'][$relativeFilePath] = $md5;
  553. unset($extension['files'][$fullFilePath]);
  554. }
  555. $extension['files'] = json_encode($extension['files']);
  556. }
  557. $this->dao->update(TABLE_EXTENSION)->data($extension)->where('code')->eq($extension['code'])->exec();
  558. return !dao::isError();
  559. }
  560. /**
  561. * 调用接口并返回结果中的data。
  562. * Fetch data from an api.
  563. *
  564. * @param string $url
  565. * @access private
  566. * @return mixed
  567. */
  568. private function fetchAPI($url)
  569. {
  570. /* 拼接URL并调用接口。 */
  571. $version = $this->loadModel('upgrade')->getOpenVersion(str_replace('.', '_', $this->config->version));
  572. $version = str_replace('_', '.', $version);
  573. $url .= (strpos($url, '?') === false ? '?' : '&') . 'lang=' . str_replace('-', '_', $this->app->getClientLang()) . '&managerVersion=' . self::EXT_MANAGER_VERSION;
  574. $url .= '&zentaoVersion=' . $version . '&edition=' . $this->config->edition;
  575. $result = json_decode(common::http($url));
  576. /* 返回接口结果。 */
  577. if(!isset($result->status)) return false;
  578. if($result->status != 'success') return false;
  579. if(isset($result->data) && md5($result->data) != $result->md5) return false;
  580. if(isset($result->data)) return json_decode($result->data);
  581. return false;
  582. }
  583. /**
  584. * 删除安装插件时拷贝到禅道目录的插件文件。
  585. * Remove extension files.
  586. *
  587. * @param string $files
  588. * @access private
  589. * @return array
  590. */
  591. private function removeExtensionFiles($files)
  592. {
  593. $commandTips = array();
  594. $appRoot = $this->app->getAppRoot();
  595. $files = json_decode($files);
  596. if($files)
  597. {
  598. foreach($files as $file => $savedMD5)
  599. {
  600. $file = $appRoot . $file;
  601. if(!file_exists($file)) continue;
  602. /* 如果没有权限或者删除失败则返回提示信息。 */
  603. $parentDir = dirname($file);
  604. if(!is_writable($file) || !is_writable($parentDir))
  605. {
  606. $commandTips[] = PHP_OS == 'Linux' ? "sudo rm -fr $file" : "del $file";
  607. }
  608. elseif(@md5_file($file) != $savedMD5)
  609. {
  610. $commandTips[] = PHP_OS == 'Linux' ? "sudo rm -fr $file" : "del $file";
  611. }
  612. elseif(!@unlink($file))
  613. {
  614. $commandTips[] = PHP_OS == 'Linux' ? "sudo rm -fr $file" : "del $file";
  615. }
  616. }
  617. }
  618. return $commandTips;
  619. }
  620. /**
  621. * 删除安装插件时在禅道目录新增的文件夹。
  622. * Remove extension dirs.
  623. *
  624. * @param string $dirs
  625. * @access private
  626. * @return array
  627. */
  628. private function removeExtensionDirs($dirs)
  629. {
  630. $commandTips = array();
  631. $appRoot = $this->app->getAppRoot();
  632. $dirs = json_decode($dirs);
  633. if($dirs)
  634. {
  635. rsort($dirs); // remove from the lower level directory.
  636. foreach($dirs as $dir)
  637. {
  638. $path = rtrim($appRoot . $dir, '/');
  639. if(!is_dir($path)) continue;
  640. /* 如果没有权限或者删除失败则返回提示信息。*/
  641. $parentDir = dirname($path);
  642. if(!is_writable($path) || !is_writable($parentDir))
  643. {
  644. $commandTips[] = PHP_OS == 'Linux' ? "sudo rm -fr $appRoot$dir" : "rmdir $appRoot$dir /s /q";
  645. }
  646. elseif(!$this->classFile->removeDir($path))
  647. {
  648. $commandTips[] = PHP_OS == 'Linux' ? "sudo rm -fr $appRoot$dir" : "rmdir $appRoot$dir /s /q";
  649. }
  650. }
  651. }
  652. return $commandTips;
  653. }
  654. /**
  655. * 删除禅道model的缓存文件。
  656. * Clean model cache files.
  657. *
  658. * @access private
  659. * @return true
  660. */
  661. private function cleanModelCache()
  662. {
  663. $zfile = $this->app->loadClass('zfile');
  664. $modelCacheFiles = glob($this->app->getTmpRoot() . 'model/*');
  665. foreach($modelCacheFiles as $cacheFile)
  666. {
  667. if(is_dir($cacheFile))
  668. {
  669. $zfile->removeDir($cacheFile);
  670. }
  671. elseif(is_writable($cacheFile) && !is_dir($cacheFile))
  672. {
  673. @unlink($cacheFile);
  674. }
  675. }
  676. return true;
  677. }
  678. }