model.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. <?php
  2. /**
  3. * The model file of client module of XXB.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd., www.zentao.net)
  6. * @license ZOSL (https://zpl.pub/page/zoslv1.html)
  7. * @author Gang Liu <liugang@cnezsoft.com>
  8. * @package client
  9. * @version $Id$
  10. * @link https://xuanim.com
  11. */
  12. class clientModel extends model
  13. {
  14. /**
  15. * Get a client by id.
  16. *
  17. * @param int $clientID
  18. * @access public
  19. * @return object | bool
  20. */
  21. public function getByID($clientID)
  22. {
  23. $client = $this->dao->select('*')->from(TABLE_IM_CLIENT)->where('id')->eq($clientID)->fetch();
  24. if(empty($client)) return false;
  25. $client->downloads = json_decode($client->downloads, true);
  26. return $client;
  27. }
  28. /**
  29. * Get a client by version.
  30. *
  31. * @param string $version
  32. * @access public
  33. * @return object | bool
  34. */
  35. public function getByVersion($version)
  36. {
  37. $client = $this->dao->select('*')->from(TABLE_IM_CLIENT)->where('version')->eq($version)->fetch();
  38. if(empty($client)) return false;
  39. $client->downloads = json_decode($client->downloads, true);
  40. return $client;
  41. }
  42. /**
  43. * Get client list.
  44. *
  45. * @access public
  46. * @return array
  47. */
  48. public function getList()
  49. {
  50. return $this->dao->select('*')->from(TABLE_IM_CLIENT)->orderBy('id_desc')->fetchAll();
  51. }
  52. /**
  53. * Get xxc current version from xuan.im.
  54. *
  55. * @access public
  56. * @return array
  57. */
  58. public function getXxcVersionFromWebsite()
  59. {
  60. $this->loadModel('setting');
  61. $now = helper::now();
  62. $xxcUpdateInfo = $this->setting->getItem('owner=system&module=common&section=client&key=xxcUpdateInfo');
  63. if(!empty($xxcUpdateInfo))
  64. {
  65. $xxcInfo = json_decode($xxcUpdateInfo, false);
  66. $prevGetDate = $xxcInfo->updateDate;
  67. if(helper::diffDate($now, $prevGetDate) <= $this->config->client->expirationDays) return $xxcInfo->version;
  68. }
  69. $currentVersion = $this->getCurrentVersion();
  70. $apiUrl = sprintf($this->config->client->upgradeApi, $currentVersion->version);
  71. $jsonData = file_get_contents($apiUrl);
  72. $serverVersions = json_decode($jsonData);
  73. if(empty($serverVersions)) return false;
  74. $xxcVersion = $serverVersions[0]->xxcVersion;
  75. if(!empty($xxcVersion))
  76. {
  77. $xxcUpdateInfo = new stdclass();
  78. $xxcUpdateInfo->version = $xxcVersion;
  79. $xxcUpdateInfo->updateDate = $now;
  80. $this->setting->setItem('system.common.client.xxcUpdateInfo', json_encode($xxcUpdateInfo));
  81. }
  82. return $xxcVersion;
  83. }
  84. /**
  85. * Create a client.
  86. *
  87. * @access public
  88. * @return bool
  89. */
  90. public function create()
  91. {
  92. $client = fixer::input('post')
  93. ->add('createdBy', $this->app->user->account)
  94. ->add('createdDate', helper::now())
  95. ->get();
  96. if(empty($client->version)) dao::$errors['version'][] = sprintf($this->lang->error->notempty, $this->lang->client->version);
  97. if($client->version && !preg_match("/([0-9]+)((?:\.[0-9]+)?)((?:\.[0-9]+)?)(?:[\s\-\+]?)((?:[a-z]+)?)((?:\.?[0-9]+)?)/i", $client->version)) dao::$errors['version'][] = $this->lang->client->wrongVersion;
  98. foreach($client->downloads as $os => $url)
  99. {
  100. if(!empty($url) && (!validater::checkURL($url) || !preg_match("/\.zip$/", $url))) dao::$errors[$os][] = sprintf($this->lang->client->urlError, zget($this->lang->client->zipList, $os) . $this->lang->client->downloadLink);
  101. }
  102. if(dao::isError()) return false;
  103. $client->downloads = helper::jsonEncode($client->downloads);
  104. $this->dao->insert(TABLE_IM_CLIENT)->data($client)->autoCheck()->exec();
  105. return !dao::isError();
  106. }
  107. /**
  108. * Create or edit a client by account.
  109. * @param $version
  110. * @param $link
  111. * @param $os
  112. * @return bool
  113. */
  114. public function edit($version, $link, $os)
  115. {
  116. $client = $this->getByVersion($version);
  117. if($client)
  118. {
  119. $client->downloads[$os] = $link;
  120. $client->editedBy = $this->app->user->account;
  121. $client->editedDate = helper::now();
  122. $client->downloads = helper::jsonEncode( $client->downloads);
  123. $this->dao->update(TABLE_IM_CLIENT)->data($client)->where('id')->eq($client->id)->exec();
  124. }
  125. else
  126. {
  127. $client = new stdClass();
  128. $client->status = 'wait';
  129. $client->version = $version;
  130. $client->strategy = 'optional';
  131. $client->downloads = helper::jsonEncode(array($os => $link));
  132. $client->createdBy = $this->app->user->account;
  133. $client->createdDate = helper::now();
  134. $this->dao->insert(TABLE_IM_CLIENT)->data($client)->autoCheck()->exec();
  135. $client->id = $this->dao->lastInsertID();
  136. }
  137. if(dao::isError()) return false;
  138. return $client;
  139. }
  140. /**
  141. * Update a client.
  142. *
  143. * @param int $clientID
  144. * @access public
  145. * @return bool
  146. */
  147. public function update($clientID)
  148. {
  149. $client = fixer::input('post')
  150. ->add('editedBy', $this->app->user->account)
  151. ->add('editedDate', helper::now())
  152. ->get();
  153. if(empty($client->version)) dao::$errors['version'][] = sprintf($this->lang->error->notempty, $this->lang->client->version);
  154. if($client->version && !preg_match("/([0-9]+)((?:\.[0-9]+)?)((?:\.[0-9]+)?)(?:[\s\-\+]?)((?:[a-z]+)?)((?:\.?[0-9]+)?)/i", $client->version)) dao::$errors['version'][] = $this->lang->client->wrongVersion;
  155. foreach($client->downloads as $os => $url)
  156. {
  157. if(!empty($url) && (!validater::checkURL($url) || !preg_match("/\.zip$/", $url))) dao::$errors[$os][] = sprintf($this->lang->client->urlError, zget($this->lang->client->zipList, $os) . $this->lang->client->downloadLink);
  158. }
  159. if(dao::isError()) return false;
  160. $client->downloads = helper::jsonEncode($client->downloads);
  161. $this->dao->update(TABLE_IM_CLIENT)->data($client)->autoCheck()->where('id')->eq($clientID)->exec();
  162. return !dao::isError();
  163. }
  164. /**
  165. * Get current version
  166. * @access public
  167. * @return object | bool
  168. */
  169. public function getCurrentVersion()
  170. {
  171. $currentVersion = $this->dao->select('*')->from(TABLE_IM_CLIENT)->where('status')->eq('released')->orderBy('id_desc')->limit(1)->fetch();
  172. if(dao::isError()) return false;
  173. return $currentVersion ?: json_decode('{"version": "'.$this->config->xuanxuan->version.'"}');
  174. }
  175. /**
  176. * Check if a client need upgrade.
  177. *
  178. * @param string $version
  179. * @access public
  180. * @return object | bool
  181. */
  182. public function getUpgrade($version)
  183. {
  184. $latestUpgradesIDQuery = $this->dao->select('MAX(id)')->from(TABLE_IM_CLIENT)
  185. ->where('status')->eq('released')->groupBy('strategy')
  186. ->get();
  187. $latestUpgradesObjectQuery = $this->dao->select('t1.downloads, t1.strategy, t1.version, t1.changeLog')->from(TABLE_IM_CLIENT)->alias('t1')
  188. ->leftJoin("($latestUpgradesIDQuery)")->alias('t2')->on('t1.id = t2.`MAX(id)`')
  189. ->get();
  190. $latestUpgradesObjectQuery = str_replace(' LEFT JOIN ', ' INNER JOIN ', $latestUpgradesObjectQuery);
  191. $latestUpgrades = $this->dao->query($latestUpgradesObjectQuery)->fetchAll();
  192. if(empty($latestUpgrades)) return false;
  193. foreach($latestUpgrades as $upgrade)
  194. {
  195. $upgrade->readme = $upgrade->changeLog;
  196. unset($upgrade->changeLog);
  197. $downloads = json_decode($upgrade->downloads);
  198. if(isset($downloads->linux32zip)) $downloads->linux32 = $downloads->linux32zip;
  199. if(isset($downloads->linux64zip)) $downloads->linux64 = $downloads->linux64zip;
  200. if(isset($downloads->macOSzip)) $downloads->mac64 = $downloads->macOSzip;
  201. if(isset($downloads->win32zip)) $downloads->win32 = $downloads->win32zip;
  202. if(isset($downloads->win64zip)) $downloads->win64 = $downloads->win64zip;
  203. unset($downloads->linux32zip);
  204. unset($downloads->linux64zip);
  205. unset($downloads->macOSzip);
  206. unset($downloads->win32zip);
  207. unset($downloads->win64zip);
  208. $upgrade->downloads = $downloads;
  209. }
  210. $optionalUpgrade = current(array_filter($latestUpgrades, function($upgrade) {return $upgrade->strategy == 'optional';}));
  211. $forcedUpgrade = current(array_filter($latestUpgrades, function($upgrade) {return $upgrade->strategy == 'force';}));
  212. if($forcedUpgrade && version_compare(helper::formatVersion($version), helper::formatVersion($forcedUpgrade->version)) == -1)
  213. {
  214. if(version_compare(helper::formatVersion($version), '3.0.0-alpha.1') == -1)
  215. {
  216. $semver = helper::formatVersion($forcedUpgrade->version);
  217. if(strpos($semver, '-') !== false)
  218. {
  219. $versions = explode('-', $semver);
  220. $forcedUpgrade->version = $versions[0];
  221. }
  222. else
  223. {
  224. $forcedUpgrade->version = $semver;
  225. }
  226. }
  227. return $forcedUpgrade;
  228. }
  229. elseif($optionalUpgrade && version_compare($version, $optionalUpgrade->version) == -1) return $optionalUpgrade;
  230. return false;
  231. }
  232. /**
  233. * Download zip package.
  234. * @param $version
  235. * @param $link
  236. * @return bool | string
  237. */
  238. public function downloadZipPackage($version, $link)
  239. {
  240. ignore_user_abort(true);
  241. set_time_limit(0);
  242. if(empty($version) || empty($link)) return false;
  243. $dir = "data/client/" . $version . '/';
  244. $link = helper::safe64Decode($link);
  245. $file = basename($link);
  246. if(!is_dir($this->app->wwwRoot . $dir))
  247. {
  248. mkdir($this->app->wwwRoot . $dir, 0755, true);
  249. }
  250. if(!is_dir($this->app->wwwRoot . $dir)) return false;
  251. if(file_exists($this->app->wwwRoot . $dir . $file))
  252. {
  253. return commonModel::getSysURL() . $this->config->webRoot . $dir . $file;
  254. }
  255. ob_clean();
  256. ob_end_flush();
  257. $local = fopen($this->app->wwwRoot . $dir . $file, 'w');
  258. $remote = fopen($link, 'rb');
  259. if($remote === false) return false;
  260. while(!feof($remote))
  261. {
  262. $buffer = fread($remote, 4096);
  263. fwrite($local, $buffer);
  264. }
  265. fclose($local);
  266. fclose($remote);
  267. return commonModel::getSysURL() . $this->config->webRoot . $dir . $file;
  268. }
  269. /**
  270. * Fetch client zip packages for current version.
  271. *
  272. * @param string $osList comma separated os list
  273. * @access public
  274. * @return void
  275. */
  276. public function fetchCurrentClient($osList = 'win64')
  277. {
  278. ignore_user_abort(true);
  279. set_time_limit(0);
  280. $osList = explode(',', $osList);
  281. $version = $this->config->xuanxuan->version;
  282. $downloadDir = "{$this->app->wwwRoot}data/client/$version/";
  283. if(!is_dir($downloadDir)) mkdir($downloadDir, 0755, true);
  284. /* Lock for single download process. */
  285. if(file_exists($downloadDir . 'downloading')) return 'downloading';
  286. touch($downloadDir . 'downloading');
  287. $errors = array();
  288. foreach($osList as $os)
  289. {
  290. $fileName = "xuanxuan.$version.$os.zip";
  291. $localPath = $downloadDir . $fileName;
  292. if(file_exists($localPath)) continue;
  293. $downloadLink = "{$this->config->client->downloadLinkPrefix}$version/$fileName";
  294. $downloadHandle = fopen($downloadLink, 'rb');
  295. $localFileHandle = fopen($localPath, 'w');
  296. if($downloadHandle === false)
  297. {
  298. $errors[] = $downloadLink;
  299. continue;
  300. }
  301. while(!feof($downloadHandle))
  302. {
  303. $buffer = fread($downloadHandle, 4096);
  304. fwrite($localFileHandle, $buffer);
  305. }
  306. fclose($localFileHandle);
  307. fclose($downloadHandle);
  308. }
  309. /* Unlock. */
  310. unlink($downloadDir . 'downloading');
  311. return empty($errors) ? 'error' : 'done';
  312. }
  313. /**
  314. * Pack current server config into packages.
  315. *
  316. * @param string $osList comma separated os list
  317. * @param bool $repack
  318. * @access public
  319. * @return void
  320. */
  321. public function packServerConfig($osList = 'win64', $repack = false)
  322. {
  323. ignore_user_abort(true);
  324. set_time_limit(0);
  325. $osList = explode(',', $osList);
  326. $version = $this->config->xuanxuan->version;
  327. $downloadDir = "{$this->app->wwwRoot}data/client/$version/";
  328. $packageDir = "{$this->app->wwwRoot}data/client/$version/_common/";
  329. if(!is_dir($packageDir)) mkdir($packageDir, 0755, true);
  330. /* Lock for single packing process. */
  331. if(file_exists($packageDir . 'packing')) return 'packing';
  332. touch($packageDir . 'packing');
  333. /* Write server address into config file. */
  334. $loginInfo = new stdclass();
  335. $loginInfo->ui = new stdclass();
  336. $loginInfo->ui->defaultUser = new stdclass();
  337. $loginInfo->ui->defaultUser->server = $this->config->xuanxuan->server;
  338. $loginInfo = json_encode($loginInfo);
  339. $loginFile = $packageDir . 'config.json';
  340. file_put_contents($loginFile, $loginInfo);
  341. define('PCLZIP_TEMPORARY_DIR', $packageDir);
  342. $this->app->loadClass('pclzip', true);
  343. $errors = array();
  344. foreach($osList as $os)
  345. {
  346. $fileName = "xuanxuan.$version.$os.zip";
  347. $packagePath = $packageDir . $fileName;
  348. if(file_exists($packagePath) && !$repack) continue;
  349. $localPath = $downloadDir . $fileName;
  350. if(!file_exists($localPath))
  351. {
  352. $errors[] = "$localPath does not exist.";
  353. continue;
  354. }
  355. $copyResult = copy($localPath, $packagePath);
  356. if($copyResult === false)
  357. {
  358. $errors[] = "cannot copy $localPath to $packagePath.";
  359. continue;
  360. }
  361. $archive = new pclzip($packagePath);
  362. if($os == 'mac') $result = $archive->add($loginFile, PCLZIP_OPT_REMOVE_ALL_PATH, PCLZIP_OPT_ADD_PATH, 'xuanxuan.app/Contents/Resources/build-in');
  363. if($os != 'mac') $result = $archive->add($loginFile, PCLZIP_OPT_REMOVE_ALL_PATH, PCLZIP_OPT_ADD_PATH, 'xuanxuan/resources/build-in');
  364. if($result != 0) $errors[] = $archive->errorInfo(true);
  365. }
  366. unlink($loginFile);
  367. /* Unlock. */
  368. unlink($packageDir . 'packing');
  369. return empty($errors) ? 'error' : 'done';
  370. }
  371. /**
  372. * Get download link texts.
  373. *
  374. * @param string $osList
  375. * @access public
  376. * @return void
  377. */
  378. public function getLinks($osList)
  379. {
  380. $osList = explode(',', $osList);
  381. $version = $this->config->xuanxuan->version;
  382. $linksText = '';
  383. foreach($osList as $os) $linksText .= $this->lang->client->osList[$os] . ': ' . commonModel::getSysURL() . $this->config->webRoot . "data/client/$version/_common/xuanxuan.$version.$os.zip\n";
  384. return $linksText;
  385. }
  386. }