model.php 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554
  1. <?php
  2. /**
  3. * The model file of file 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 file
  9. * @version $Id: model.php 4976 2013-07-02 08:15:31Z wyd621@gmail.com $
  10. * @link https://www.zentao.net
  11. */
  12. ?>
  13. <?php
  14. class fileModel extends model
  15. {
  16. public $savePath = '';
  17. public $webPath = '';
  18. public $now = 0;
  19. /**
  20. * Construct function.
  21. *
  22. * @access public
  23. * @return void
  24. */
  25. public function __construct()
  26. {
  27. parent::__construct();
  28. $this->now = time();
  29. $this->setSavePath();
  30. $this->setWebPath();
  31. }
  32. /**
  33. * Get files of an object.
  34. *
  35. * @param string $objectType
  36. * @param int|array $objectID
  37. * @param string $extra
  38. * @access public
  39. * @return array
  40. */
  41. public function getByObject($objectType, $objectID, $extra = '')
  42. {
  43. $files = $this->dao->select('*')->from(TABLE_FILE)
  44. ->where('objectType')->eq($objectType)
  45. ->andWhere('objectID')->in($objectID)
  46. ->andWhere('extra')->ne('editor')
  47. ->beginIF($extra)->andWhere('extra')->in($extra)->fi()
  48. ->andWhere('deleted')->eq('0')
  49. ->orderBy('id')
  50. ->fetchAll('id');
  51. foreach($files as $file) $this->setFileWebAndRealPaths($file);
  52. return $files;
  53. }
  54. /**
  55. * 按照objectID分组获取附件列表。
  56. * Get files of an object group by objectID.
  57. *
  58. * @param string $objectType
  59. * @param string|array $objectID
  60. * @access public
  61. * @return array
  62. */
  63. public function groupByObjectID($objectType, $objectID)
  64. {
  65. $fileGroup = $this->dao->select('*')->from(TABLE_FILE)
  66. ->where('objectType')->eq($objectType)
  67. ->andWhere('objectID')->in($objectID)
  68. ->andWhere('deleted')->eq('0')
  69. ->orderBy('id')
  70. ->fetchGroup('objectID', 'id');
  71. foreach($fileGroup as $objectID => $files)
  72. {
  73. foreach($files as $file) $this->setFileWebAndRealPaths($file);
  74. }
  75. return $fileGroup;
  76. }
  77. /**
  78. * Delete files by object.
  79. *
  80. * @param string $objectType
  81. * @param int $objectID
  82. * @access public
  83. * @return bool
  84. */
  85. public function deleteByObject($objectType, $objectID)
  86. {
  87. $files = $this->dao->select('*')->from(TABLE_FILE)
  88. ->where('objectType')->eq($objectType)
  89. ->andWhere('objectID')->in($objectID)
  90. ->andWhere('deleted')->eq('0')
  91. ->fetchAll('id');
  92. foreach($files as $file)
  93. {
  94. $this->dao->update(TABLE_FILE)->set('deleted')->eq(1)->where('id')->eq($file->id)->exec();
  95. }
  96. return true;
  97. }
  98. /**
  99. * Get info of a file.
  100. *
  101. * @param int $fileID
  102. * @access public
  103. * @return object|false
  104. */
  105. public function getById($fileID)
  106. {
  107. $file = $this->dao->findById($fileID)->from(TABLE_FILE)->fetch();
  108. if(empty($file)) return false;
  109. $this->setFileWebAndRealPaths($file);
  110. return $file;
  111. }
  112. /**
  113. * Get file by gid.
  114. *
  115. * @param string $gid
  116. * @access public
  117. * @return string
  118. */
  119. public function getByGid($gid)
  120. {
  121. $file = $this->dao->select('*')->from(TABLE_FILE)
  122. ->where('gid')->eq($gid)
  123. ->orWhere('(gid')->eq('')
  124. ->andWhere('title')->eq($gid)
  125. ->markRight(1)
  126. ->fetch();
  127. if(empty($file)) return false;
  128. $this->setFileWebAndRealPaths($file);
  129. return $file;
  130. }
  131. /**
  132. * Get file by object.
  133. *
  134. * @param string $objectType
  135. * @param int $objectID
  136. * @param string $title
  137. * @param string $extra
  138. * @access public
  139. * @return object|false
  140. */
  141. public function query($objectType, $objectID = 0, $title = '', $extra = '')
  142. {
  143. $file = $this->dao->select('*')->from(TABLE_FILE)
  144. ->where('objectType')->eq($objectType)
  145. ->beginIF($objectID)->andWhere('objectID')->eq($objectID)->fi()
  146. ->beginIF($title)->andWhere('title')->eq($title)->fi()
  147. ->beginIF($extra)->andWhere('extra')->eq($extra)->fi()
  148. ->fetch();
  149. if(empty($file)) return false;
  150. return $file;
  151. }
  152. /**
  153. * Get files by ID list.
  154. *
  155. * @param string|array $fileIdList
  156. * @access public
  157. * @return array
  158. */
  159. public function getByIdList($fileIdList)
  160. {
  161. if(empty($fileIdList)) return array();
  162. $files = $this->dao->select('*')->from(TABLE_FILE)->where('id')->in($fileIdList)->orderBy('id')->fetchAll('id');
  163. foreach($files as $file) $this->setFileWebAndRealPaths($file);
  164. return $files;
  165. }
  166. /**
  167. * Save upload.
  168. *
  169. * @param string $objectType
  170. * @param int $objectID
  171. * @param int|string $extra
  172. * @param string $filesName
  173. * @param string $labelsName
  174. * @access public
  175. * @return array
  176. */
  177. public function saveUpload($objectType = '', $objectID = 0, $extra = '', $filesName = 'files', $labelsName = 'labels')
  178. {
  179. $fileTitles = array();
  180. $now = helper::today();
  181. $files = $this->getUpload($filesName, $labelsName);
  182. foreach($files as $file)
  183. {
  184. if(!isset($file['size']) || $file['size'] == 0) continue;
  185. if(!move_uploaded_file($file['tmpname'], $this->savePath . $this->getSaveName($file['pathname']))) return false;
  186. $file = $this->compressImage($file);
  187. $file['objectType'] = $objectType;
  188. $file['objectID'] = $objectID;
  189. $file['addedBy'] = $this->app->user->account;
  190. $file['addedDate'] = $now;
  191. if($extra) $file['extra'] = $extra;
  192. unset($file['tmpname']);
  193. $this->dao->insert(TABLE_FILE)->data($file)->exec();
  194. $fileTitles[$this->dao->lastInsertId()] = $file['title'];
  195. }
  196. return $fileTitles;
  197. }
  198. /**
  199. * Save a file.
  200. *
  201. * @param array $file
  202. * @param string $objectType
  203. * @param int $objectID
  204. * @param string $extra
  205. * @param string $filesName
  206. * @param string $labelsName
  207. * @access public
  208. * @return object
  209. */
  210. public function saveAFile($file, $objectType = '', $objectID = 0, $extra = '', $filesName = 'files', $labelsName = 'labels')
  211. {
  212. $now = helper::today();
  213. if(!move_uploaded_file($file['tmpname'], $this->savePath . $this->getSaveName($file['pathname']))) return false;
  214. $file = $this->compressImage($file);
  215. $file['objectType'] = $objectType;
  216. $file['objectID'] = $objectID;
  217. $file['addedBy'] = $this->app->user->account;
  218. $file['addedDate'] = $now;
  219. $file['extra'] = $extra;
  220. unset($file['tmpname']);
  221. $this->dao->insert(TABLE_FILE)->data($file)->exec();
  222. $fileTitle = new stdclass();
  223. $fileTitle->id = $this->dao->lastInsertId();
  224. $fileTitle->title = $file['title'];
  225. return $fileTitle;
  226. }
  227. /**
  228. * Get counts of uploaded files.
  229. *
  230. * @access public
  231. * @return int
  232. */
  233. public function getCount()
  234. {
  235. return count($this->getUpload());
  236. }
  237. /**
  238. * Get info of uploaded files.
  239. *
  240. * @param string $htmlTagName
  241. * @param string $labelsName
  242. * @access public
  243. * @return array
  244. */
  245. public function getUpload($htmlTagName = 'files', $labelsName = 'labels')
  246. {
  247. $files = array();
  248. if(!isset($_FILES[$htmlTagName])) return $files;
  249. if(!is_array($_FILES[$htmlTagName]['error']) && $_FILES[$htmlTagName]['error'] != 0) return $_FILES[$htmlTagName];
  250. if(is_array($_FILES[$htmlTagName]['error']) && reset($_FILES[$htmlTagName]['error']) != 0)
  251. {
  252. $_FILES[$htmlTagName]['error'] = reset($_FILES[$htmlTagName]['error']);
  253. return $_FILES[$htmlTagName];
  254. }
  255. $purifier = $this->app->loadClass('purifier');
  256. /* If the file var name is an array. */
  257. if(is_array($_FILES[$htmlTagName]['name']))
  258. {
  259. extract($_FILES[$htmlTagName]);
  260. foreach($name as $id => $filename)
  261. {
  262. if(empty($filename)) continue;
  263. if(!validater::checkFileName($filename)) continue;
  264. $title = isset($_POST[$labelsName][$id]) ? $_POST[$labelsName][$id] : '';
  265. $file['extension'] = $this->getExtension($filename, $tmp_name[$id]);
  266. $file['pathname'] = $this->setPathName($id, $file['extension']);
  267. $file['title'] = (!empty($title) and $title != $filename) ? htmlSpecialString($title) : $filename;
  268. $file['title'] = $purifier->purify($file['title']);
  269. $file['size'] = $size[$id];
  270. $file['tmpname'] = $tmp_name[$id];
  271. $file['extra'] = !empty($extra[$id]) ? $extra[$id] : '';
  272. $files[] = $file;
  273. }
  274. }
  275. else
  276. {
  277. if(empty($_FILES[$htmlTagName]['name'])) return $files;
  278. extract($_FILES[$htmlTagName]);
  279. if(!validater::checkFileName($name)) return array();
  280. $title = isset($_POST[$labelsName][0]) ? $_POST[$labelsName][0] : '';
  281. $file['extension'] = $this->getExtension($name, $tmp_name);
  282. $file['pathname'] = $this->setPathName(0, $file['extension']);
  283. $file['title'] = (!empty($title) && $title != $name) ? htmlSpecialString($title) : $name;
  284. $file['title'] = $purifier->purify($file['title']);
  285. $file['size'] = $size;
  286. $file['tmpname'] = $tmp_name;
  287. return array($file);
  288. }
  289. return $files;
  290. }
  291. /**
  292. * get uploaded file from zui.uploader.
  293. *
  294. * @access public
  295. * @return array
  296. */
  297. public function getChunkedFile()
  298. {
  299. $name = urldecode(zget($_SERVER, 'HTTP_X_FILENAME', ''));
  300. if(!validater::checkFileName($name) or empty($name)) return array();
  301. $purifier = $this->app->loadClass('purifier');
  302. $file = array();
  303. $file['id'] = 0;
  304. $file['extension'] = $this->getExtension($name);
  305. $file['title'] = $purifier->purify(substr($name, 0, strpos($name, $file['extension']) - 1));
  306. $file['size'] = zget($_SERVER, 'HTTP_X_FILESIZE', 0);
  307. $file['pathname'] = md5($file['title']);
  308. $file['chunks'] = zget($_SERVER, 'HTTP_X_TOTAL_CHUNKS', 0);
  309. $file['chunkIndex'] = zget($_SERVER, 'HTTP_X_CHUNK_INDEX', 0);
  310. if(stripos($this->config->file->allowed, ",{$file['extension']},") === false) $file['pathname'] = $file['pathname'] . '.notAllowed';
  311. return $file;
  312. }
  313. /**
  314. * Save uploaded file by chunk.
  315. *
  316. * @param array $file
  317. * @param string $uid
  318. * @access public
  319. * @return array
  320. */
  321. public function saveChunkedFile($file, $uid)
  322. {
  323. if($file['chunks'] == 0) return array();
  324. $tmpFilePath = $this->app->getTmpRoot() . 'uploadfiles/';
  325. $tmpFileSavePath = $tmpFilePath . $uid . '/';
  326. if(!is_dir($tmpFileSavePath)) mkdir($tmpFileSavePath, 0777, true);
  327. $file['realpath'] = $tmpFileSavePath . basename($this->getSaveName($file['pathname']));
  328. file_put_contents($file['realpath'], file_get_contents('php://input'), $file['chunkIndex'] == 0 ? 0 : FILE_APPEND);
  329. $uploadFile = array();
  330. $uploadFile['extension'] = $file['extension'];
  331. $uploadFile['pathname'] = $file['pathname'];
  332. $uploadFile['title'] = $file['title'];
  333. $uploadFile['realpath'] = $file['realpath'];
  334. $uploadFile['size'] = $file['size'];
  335. return $uploadFile;
  336. }
  337. /**
  338. * Get extension of a file.
  339. *
  340. * @param string $filename
  341. * @param string $filePath
  342. * @access public
  343. * @return string
  344. */
  345. public function getExtension($filename, $filePath = '')
  346. {
  347. $extension = trim(strtolower(pathinfo($filename, PATHINFO_EXTENSION)));
  348. if($extension and strpos($extension, '::') !== false) $extension = substr($extension, 0, strpos($extension, '::'));
  349. if(in_array($extension, $this->config->file->imageExtensions) && !empty($filePath))
  350. {
  351. $imagesize = getimagesize($filePath);
  352. if(!isset($imagesize[2])) return 'txt';
  353. $realExtension = image_type_to_extension($imagesize[2], false);
  354. $extension = $extension == 'jpg' && $realExtension == 'jpeg' ? 'jpg' : $realExtension;
  355. }
  356. if(empty($extension) or stripos(",{$this->config->file->dangers},", ",{$extension},") !== false) return 'txt';
  357. if(empty($extension) or stripos(",{$this->config->file->allowed},", ",{$extension},") === false) return 'txt';
  358. if($extension == 'php') return 'txt';
  359. return $extension;
  360. }
  361. /**
  362. * Get save name.
  363. *
  364. * @param string $pathName
  365. * @access public
  366. * @return string
  367. */
  368. public function getSaveName($pathName)
  369. {
  370. $position = strrpos($pathName, '.');
  371. return $position === false ? $pathName : substr($pathName, 0, $position);
  372. }
  373. /**
  374. * Get real path name.
  375. *
  376. * @param string $pathName
  377. * @access public
  378. * @return string
  379. */
  380. public function getRealPathName($pathName)
  381. {
  382. $realPath = $this->savePath . $pathName;
  383. if(file_exists($realPath)) return $pathName;
  384. return $this->getSaveName($pathName);
  385. }
  386. /**
  387. * Get export tpl.
  388. *
  389. * @param string $module
  390. * @access public
  391. * @return array
  392. */
  393. public function getExportTemplate($module)
  394. {
  395. return $this->dao->select('id,title,content,public')->from(TABLE_USERTPL)
  396. ->where('type')->eq("export$module")
  397. ->andwhere('account', true)->eq($this->app->user->account)
  398. ->orWhere('public')->eq('1')
  399. ->markRight(1)
  400. ->orderBy('id')
  401. ->fetchAll();
  402. }
  403. /**
  404. * Get tmp import path.
  405. *
  406. * @access public
  407. * @return string
  408. */
  409. public function getPathOfImportedFile()
  410. {
  411. $path = $this->app->getTmpRoot() . 'import';
  412. if(!is_dir($path)) mkdir($path, 0755, true);
  413. return $path;
  414. }
  415. /**
  416. * Save export template.
  417. *
  418. * @param string $module
  419. * @access public
  420. * @return int|false
  421. */
  422. public function saveExportTemplate($module)
  423. {
  424. $template = fixer::input('post')
  425. ->add('account', $this->app->user->account)
  426. ->add('type', "export$module")
  427. ->join('content', ',')
  428. ->get();
  429. if($template->title == $this->lang->file->defaultTPL)
  430. {
  431. dao::$errors[] = sprintf($this->lang->error->unique, $this->lang->file->tplTitle, $this->lang->file->defaultTPL);
  432. return false;
  433. }
  434. $condition = "`type`='export$module' and account='{$this->app->user->account}'";
  435. $this->dao->insert(TABLE_USERTPL)->data($template)->batchCheck('title, content', 'notempty')->check('title', 'unique', $condition)->exec();
  436. return $this->dao->lastInsertId();
  437. }
  438. /**
  439. * Set path name of the uploaded file to be saved.
  440. *
  441. * @param string|int $fileID
  442. * @param string $extension
  443. * @access public
  444. * @return string
  445. */
  446. public function setPathName($fileID, $extension)
  447. {
  448. $sessionID = session_id();
  449. $randString = substr($sessionID, mt_rand(0, strlen($sessionID) - 5), 3);
  450. return date('Ym/dHis', $this->now) . $fileID . mt_rand(0, 10000) . $randString . '.' . $extension;
  451. }
  452. /**
  453. * Set save path.
  454. *
  455. * @access public
  456. * @return void
  457. */
  458. public function setSavePath()
  459. {
  460. $companyID = isset($this->app->company->id) ? $this->app->company->id : 1;
  461. $savePath = $this->app->getAppRoot() . "www/data/upload/{$companyID}/" . date('Ym/', $this->now);
  462. $parentDir = dirname($savePath);
  463. while(!isset($parentCanWrite))
  464. {
  465. if(file_exists($parentDir)) $parentCanWrite = is_writable($parentDir);
  466. $parentDir = dirname($parentDir);
  467. }
  468. if(!file_exists($savePath) && $parentCanWrite)
  469. {
  470. mkdir($savePath, 0777, true);
  471. touch($savePath . 'index.html');
  472. }
  473. $this->savePath = dirname($savePath) . '/';
  474. }
  475. /**
  476. * Set the web path of upload files.
  477. *
  478. * @access public
  479. * @return void
  480. */
  481. public function setWebPath()
  482. {
  483. $companyID = isset($this->app->company->id) ? $this->app->company->id : 1;
  484. $this->webPath = $this->app->getWebRoot() . "data/upload/{$companyID}/";
  485. }
  486. /**
  487. * Set paths: realPath and webPath.
  488. *
  489. * @param object $file
  490. * @access public
  491. * @return void
  492. */
  493. public function setFileWebAndRealPaths($file)
  494. {
  495. if($file->objectType == 'traincourse' or $file->objectType == 'traincontents')
  496. {
  497. $file->realPath = $this->app->getWwwRoot() . 'data/course/' . $file->pathname;
  498. $file->webPath = $this->config->webRoot . 'data/course/' . $file->pathname;
  499. }
  500. else
  501. {
  502. $pathName = $this->getRealPathName($file->pathname);
  503. $file->realPath = $this->savePath . $pathName;
  504. $file->webPath = $this->webPath . $pathName;
  505. }
  506. if(!isset($file->name)) $file->name = $file->title;
  507. if(!isset($file->url)) $file->url = helper::createLink('file', 'download', "fileID={$file->id}");
  508. }
  509. /**
  510. * Insert the set image size code.
  511. *
  512. * @param string|null $content
  513. * @param int $maxSize
  514. * @access public
  515. * @return string|null
  516. */
  517. public function setImgSize($content, $maxSize = 0)
  518. {
  519. $content = (string)$content;
  520. if(empty($content)) return $content;
  521. static $fileLink, $imageLink;
  522. if(empty($fileLink)) $fileLink = helper::createLink('file', 'read', 'fileID=(%fileID%)', '\w+');
  523. if(empty($imageLink)) $imageLink = helper::createLink('file', 'read', "fileID=$1", "$3");
  524. $readLinkReg = str_replace(array('%fileID%', '/', '.', '?'), array('[0-9]+', '\/', '\.', '\?'), $fileLink);
  525. $content = preg_replace('/ src="(' . $readLinkReg . ')" /', ' onload="setImageSize(this,' . $maxSize . ')" src="$1" ', $content);
  526. $content = preg_replace('/ src="{([0-9]+)(\.(\w+))?}" /', ' onload="setImageSize(this,' . $maxSize . ')" src="' . $imageLink . '" ', $content);
  527. return str_replace(' src="data/upload', ' onload="setImageSize(this,' . $maxSize . ')" src="data/upload', $content);
  528. }
  529. /**
  530. * Check file exists or not.
  531. *
  532. * @param object $file
  533. * @access public
  534. * @return bool
  535. */
  536. public function fileExists($file)
  537. {
  538. if(empty($file->realPath)) return false;
  539. return file_exists($file->realPath);
  540. }
  541. /**
  542. * Unlink file.
  543. *
  544. * @param object $file
  545. * @access public
  546. * @return bool|null
  547. */
  548. public function unlinkFile($file)
  549. {
  550. if(empty($file->realPath)) return false;
  551. return @unlink($file->realPath);
  552. }
  553. /**
  554. * Replace a file.
  555. *
  556. * @access public
  557. * @return bool
  558. * @param int $fileID
  559. * @param string $postName
  560. */
  561. public function replaceFile($fileID, $postName = 'upFile')
  562. {
  563. $files = $this->getUpload($postName);
  564. if(empty($files)) return false;
  565. $pathName = $this->dao->select('pathname')->from(TABLE_FILE)->where('id')->eq($fileID)->fetch('pathname');
  566. if(empty($pathName)) return false;
  567. $file = $files[0];
  568. $realPathName = $this->savePath . $this->getRealPathName($pathName);
  569. $parentDir = dirname($realPathName);
  570. if(!is_dir($parentDir) and is_writable(dirname($parentDir))) mkdir($parentDir);
  571. move_uploaded_file($file['tmpname'], $realPathName);
  572. $file['pathname'] = $pathName;
  573. $file = $this->compressImage($file);
  574. $fileInfo = new stdclass();
  575. $fileInfo->addedBy = $this->app->user->account;
  576. $fileInfo->addedDate = helper::now();
  577. $fileInfo->size = $file['size'];
  578. $this->dao->update(TABLE_FILE)->data($fileInfo)->where('id')->eq($fileID)->exec();
  579. return true;
  580. }
  581. /**
  582. * Check file priv.
  583. *
  584. * @param object $file
  585. * @access public
  586. * @return bool
  587. */
  588. public function checkPriv($file)
  589. {
  590. if(!$file->objectType || !$file->objectID) return true;
  591. if(!$this->loadModel('user')->isLogon()) return true;
  592. if($file->extra == 'editor') return true;
  593. $objectType = $file->objectType;
  594. $objectID = $file->objectID;
  595. $table = zget($this->config->objectTables, $objectType, '');
  596. if(!$table) return true;
  597. $groupName = zget($this->config->file->objectGroup, $objectType, '');
  598. if(!empty($groupName))
  599. {
  600. $groupID = $this->dao->findByID($objectID)->from($table)->fetch($groupName);
  601. if($objectType == 'testcase' && $groupID == 0) return true;
  602. return $this->loadModel($groupName)->checkPriv((int)$groupID);
  603. }
  604. if($objectType == 'build')
  605. {
  606. $build = $this->dao->select('project,execution')->from(TABLE_BUILD)->where('id')->eq($objectID)->fetch();
  607. if($build->execution) return $this->loadModel('execution')->checkPriv($build->execution);
  608. return $this->loadModel('project')->checkPriv($build->project);
  609. }
  610. if($objectType == 'release')
  611. {
  612. $release = $this->dao->select('project,product')->from(TABLE_RELEASE)->where('id')->eq($objectID)->fetch();
  613. return $this->loadModel('product')->checkPriv($release->product);
  614. }
  615. if($objectType == 'doc')
  616. {
  617. $doc = $this->dao->findById($objectID)->from(TABLE_DOC)->fetch();
  618. return $this->loadModel('doc')->checkPrivDoc($doc);
  619. }
  620. if($objectType == 'feedback')
  621. {
  622. $productID = $this->dao->findById($objectID)->from(TABLE_FEEDBACK)->fetch('product');
  623. $grantProducts = $this->loadModel('feedback')->getGrantProducts();
  624. return in_array($productID, array_keys($grantProducts));
  625. }
  626. return true;
  627. }
  628. /**
  629. * Compress image to config configured size.
  630. *
  631. * @param string $rawImage
  632. * @param string $target
  633. * @param int $x
  634. * @param int $y
  635. * @param int $width
  636. * @param int $height
  637. * @param int $resizeWidth
  638. * @param int $resizeHeight
  639. * @access public
  640. * @return void
  641. */
  642. public function cropImage($rawImage, $target, $x, $y, $width, $height, $resizeWidth = 0, $resizeHeight = 0)
  643. {
  644. $this->app->loadClass('phpthumb', true);
  645. if(!extension_loaded('gd')) return false;
  646. $croper = new phpthumb($rawImage);
  647. if($resizeWidth > 0) $croper->resize($resizeWidth, $resizeHeight);
  648. $croper->crop($x, $y, $width, $height);
  649. $croper->save($target);
  650. }
  651. /**
  652. * Paste image in kindeditor at firefox and chrome.
  653. *
  654. * @param string $data
  655. * @param string $uid
  656. * @param bool $safe
  657. * @access public
  658. * @return string
  659. */
  660. public function pasteImage($data, $uid = '', $safe = false)
  661. {
  662. if(empty($data)) return '';
  663. $dataLength = strlen($data);
  664. if(ini_get('pcre.backtrack_limit') < $dataLength) ini_set('pcre.backtrack_limit', $dataLength);
  665. preg_match_all('/<img src="(data:image\/(\S+);base64,(\S+))".*\/>/U', $data, $out);
  666. if($out[3])
  667. {
  668. foreach($out[3] as $key => $base64Image)
  669. {
  670. $base64Image = str_replace('\"', '"', $base64Image);
  671. $extension = strtolower($out[2][$key]);
  672. if(!in_array($extension, $this->config->file->imageExtensions)) helper::end();
  673. $imageData = base64_decode($base64Image);
  674. $file['extension'] = $extension;
  675. $file['pathname'] = $this->setPathName($key, $file['extension']);
  676. $file['size'] = strlen($imageData);
  677. $file['addedBy'] = $this->app->user->account;
  678. $file['addedDate'] = helper::today();
  679. $file['title'] = str_replace(".$extension", '', basename($file['pathname']));
  680. $imagePath = $this->savePath . $this->getSaveName($file['pathname']);
  681. if(is_writable(dirname($imagePath))) file_put_contents($imagePath, $imageData);
  682. $this->dao->insert(TABLE_FILE)->data($file)->exec();
  683. $fileID = $this->dao->lastInsertID();
  684. if($uid) $_SESSION['album'][$uid][] = $fileID;
  685. $data = str_replace($out[1][$key], helper::createLink('file', 'read', "fileID=$fileID", $file['extension']), $data);
  686. }
  687. }
  688. elseif($safe)
  689. {
  690. $data = fixer::stripDataTags(rawurldecode($data));
  691. }
  692. return $data;
  693. }
  694. /**
  695. * Parse CSV.
  696. *
  697. * @param string $csvFile
  698. * @access public
  699. * @return array
  700. */
  701. public function parseCSV($csvFile)
  702. {
  703. /* Parse file only in zentao. */
  704. if(strpos($csvFile, $this->app->getBasePath()) !== 0) return array();
  705. if(!file_exists($csvFile)) return array();
  706. $fh = fopen($csvFile, "r");
  707. $data = array();
  708. while(($row = fgetcsv($fh, 0, ",", "\"", "\\")) !== false) $data[] = $row;
  709. fclose($fh);
  710. return $data;
  711. }
  712. /**
  713. * Process editor.
  714. *
  715. * @param object $data
  716. * @param string|array $editorList
  717. * @param mixed $uid
  718. * @access public
  719. * @return object
  720. */
  721. public function processImgURL($data, $editorList, $uid = '')
  722. {
  723. if(!is_string($uid)) return $data;
  724. if(is_string($editorList)) $editorList = explode(',', str_replace(' ', '', $editorList));
  725. if(empty($editorList)) return $data;
  726. $readLinkReg = helper::createLink('file', 'read', 'fileID=(%fileID%)', '(%viewType%)');
  727. $readLinkReg = str_replace(array('%fileID%', '%viewType%', '?', '/'), array('[0-9]+', '\w+', '\?', '\/'), $readLinkReg);
  728. $imageIdList = array();
  729. foreach($editorList as $editorID)
  730. {
  731. if(empty($editorID) or empty($data->$editorID)) continue;
  732. $imgURL = $this->config->requestType == 'GET' ? '{$2.$1}' : '{$1.$2}';
  733. $content = $this->pasteImage($data->$editorID, $uid);
  734. if($content) $data->$editorID = $content;
  735. $data->$editorID = preg_replace("/ src=\"$readLinkReg\" /", ' src="' . $imgURL . '" ', $data->$editorID);
  736. $data->$editorID = preg_replace("/ src=\"" . htmlSpecialString($readLinkReg) . "\" /", ' src="' . $imgURL . '" ', $data->$editorID);
  737. preg_match_all('/ src="{([0-9]+)\.\w+}"/', $data->$editorID, $matches);
  738. if($matches[1])
  739. {
  740. foreach($matches[1] as $imageID) $imageIdList[$imageID] = $imageID;
  741. }
  742. }
  743. if(!empty($_SESSION['album'][$uid]))
  744. {
  745. foreach($_SESSION['album'][$uid] as $imageID)
  746. {
  747. if(isset($imageIdList[$imageID])) $_SESSION['album']['used'][$uid][$imageID] = $imageID;
  748. }
  749. }
  750. return $data;
  751. }
  752. /**
  753. * Compress image
  754. *
  755. * @param array $file
  756. * @access public
  757. * @return array
  758. */
  759. public function compressImage($file)
  760. {
  761. if(!extension_loaded('gd') or !function_exists('imagecreatefromjpeg')) return $file;
  762. $pathName = $file['pathname'];
  763. $fileName = $this->savePath . $this->getSaveName($pathName);
  764. $suffix = $file['extension'];
  765. $lowerSuffix = strtolower($suffix);
  766. if(!in_array($lowerSuffix, $this->config->file->image2Compress)) return $file;
  767. $quality = 85;
  768. $newSuffix = '.jpg';
  769. $compressedName = str_replace($suffix, $newSuffix, $pathName);
  770. $res = $lowerSuffix == '.bmp' ? $this->imagecreatefrombmp($fileName) : imagecreatefromjpeg($fileName);
  771. imagejpeg($res, $fileName, $quality);
  772. $file['pathname'] = $compressedName;
  773. $file['extension'] = ltrim($newSuffix, '.');
  774. $file['size'] = filesize($fileName);
  775. return $file;
  776. }
  777. /**
  778. * Read 24bit BMP files
  779. * Author: de77
  780. * Licence: MIT
  781. * Webpage: de77.com
  782. * Version: 07.02.2010
  783. * Source : https://github.com/acustodioo/pic/blob/master/imagecreatefrombmp.function.php
  784. *
  785. * @param string $filename
  786. * @access public
  787. * @return resource
  788. */
  789. public function imagecreatefrombmp($filename)
  790. {
  791. $f = fopen($filename, "rb");
  792. //read header
  793. $header = fread($f, 54);
  794. $header = unpack('c2identifier/Vfile_size/Vreserved/Vbitmap_data/Vheader_size/'.
  795. 'Vwidth/Vheight/vplanes/vbits_per_pixel/Vcompression/Vdata_size/'.
  796. 'Vh_resolution/Vv_resolution/Vcolors/Vimportant_colors', $header);
  797. if ($header['identifier1'] != 66 or $header['identifier2'] != 77) return false;
  798. if ($header['bits_per_pixel'] != 24) return false;
  799. $wid2 = ceil((3 * $header['width']) / 4) * 4;
  800. $wid = $header['width'];
  801. $hei = $header['height'];
  802. $img = imagecreatetruecolor($header['width'], $header['height']);
  803. //read pixels
  804. for($y = $hei - 1; $y >= 0; $y--)
  805. {
  806. $row = fread($f, $wid2);
  807. $pixels = str_split($row, 3);
  808. for ($x = 0; $x < $wid; $x++) imagesetpixel($img, $x, $y, $this->dwordize($pixels[$x]));
  809. }
  810. fclose($f);
  811. return $img;
  812. }
  813. /**
  814. * Dwordize for imagecreatefrombmp
  815. *
  816. * @param string $str
  817. * @access private
  818. * @return int
  819. */
  820. private function dwordize($str)
  821. {
  822. $a = ord($str[0]);
  823. $b = ord($str[1]);
  824. $c = ord($str[2]);
  825. return $c * 256 * 256 + $b * 256 + $a;
  826. }
  827. /**
  828. * Update objectID.
  829. *
  830. * @param array|string|bool $uid
  831. * @param int $objectID
  832. * @param string $objectType
  833. * @access public
  834. * @return bool
  835. */
  836. public function updateObjectID($uid, $objectID, $objectType)
  837. {
  838. if(empty($uid)) return true;
  839. if(is_string($uid)) $uid = array($uid);
  840. if(!is_array($uid)) return true;
  841. foreach($uid as $value)
  842. {
  843. if(empty($_SESSION['album']['used'][$value])) continue;
  844. $data = new stdclass();
  845. $data->objectID = $objectID;
  846. $data->objectType = $objectType;
  847. if(!defined('RUN_MODE') || RUN_MODE != 'api') $data->extra = 'editor';
  848. $this->dao->update(TABLE_FILE)->data($data)->where('id')->in($_SESSION['album']['used'][$value])->exec();
  849. }
  850. return !dao::isError();
  851. }
  852. /**
  853. * Get upload by uid.
  854. *
  855. * @param array|string|bool $uid
  856. * @param string $filesName
  857. * @param string $labelsName
  858. * @access public
  859. * @return array
  860. */
  861. public function getUploadByUID($uid, $filesName = 'files', $labelsName = 'labels')
  862. {
  863. if(empty($uid)) return array();
  864. if(is_string($uid)) $uid = array($uid);
  865. if(!is_array($uid)) return array();
  866. $files = array();
  867. foreach($uid as $value)
  868. {
  869. if(empty($_SESSION['album']['used'][$value])) continue;
  870. $uidFiles = $this->dao->select('id, title')->from(TABLE_FILE)->where('id')->in($_SESSION['album']['used'][$value])->fetchPairs();
  871. foreach($uidFiles as $fileID => $fileName)
  872. {
  873. $files[$fileID] = $fileName;
  874. }
  875. }
  876. return $files;
  877. }
  878. /**
  879. * Revert real src.
  880. *
  881. * @param object $data
  882. * @param string $fields
  883. * @access public
  884. * @return object
  885. */
  886. public function replaceImgURL($data, $fields)
  887. {
  888. $moduleName = $this->app->getModuleName();
  889. $methodName = $this->app->getMethodName();
  890. if($moduleName == 'story' && isset($data->type)) $moduleName = $data->type;
  891. if(is_string($fields)) $fields = explode(',', str_replace(' ', '', $fields));
  892. $textareaFields = $this->dao->select('id,field')->from(TABLE_WORKFLOWFIELD)->where('module')->eq($moduleName)->andWhere('control')->eq('richtext')->andWhere('buildin')->eq('0')->fetchPairs();
  893. if($textareaFields) $fields = array_merge($fields, $textareaFields);
  894. foreach($fields as $field)
  895. {
  896. if(empty($field) or empty($data->$field)) continue;
  897. if(is_numeric($data->$field)) $data->$field = (string)$data->$field;
  898. $data->$field = preg_replace('/ src="{([0-9]+)(\.(\w+))?}" /', ' src="' . helper::createLink('file', 'read', "fileID=$1", "$3") . '" ', $data->$field);
  899. /* Convert plain text URLs into HTML hyperlinks. */
  900. if(isset($this->config->file->convertURL['common'][$methodName]) or isset($this->config->file->convertURL[$moduleName][$methodName]))
  901. {
  902. $fieldData = $data->$field;
  903. preg_match_all('/(<a[^>]*>.*<\/a>)/Ui', $fieldData, $aTags);
  904. preg_match_all('/(<img[^>]*>)/i', $fieldData, $imgTags);
  905. preg_match_all('/(<iframe[^>]*>[^<]*<\/iframe>)/i', $fieldData, $iframeTags);
  906. preg_match_all('/(<embed[^>]*>)/i', $fieldData, $embedTags);
  907. preg_match_all('/(<pre[^>]*>.*<\/pre>)/sUi', $fieldData, $preTags);
  908. foreach($aTags[0] as $i => $aTag) $fieldData = str_replace($aTag, "<A_{$i}>", $fieldData);
  909. foreach($imgTags[0] as $i => $imgTag) $fieldData = str_replace($imgTag, "<IMG_{$i}>", $fieldData);
  910. foreach($iframeTags[0] as $i => $iframeTag) $fieldData = str_replace($iframeTag, "<IFRAME_{$i}>", $fieldData);
  911. foreach($embedTags[0] as $i => $embedTag) $fieldData = str_replace($embedTag, "<EMBED_{$i}>", $fieldData);
  912. foreach($preTags[0] as $i => $preTag) $fieldData = str_replace($preTag, "<PRE_{$i}>", $fieldData);
  913. $fieldData = preg_replace('/(http:\/\/|https:\/\/)((\w|=|\?|\.|\/|\&|-|%|;)+)/i', "<a href=\"\\0\" target=\"_blank\">\\0</a>", $fieldData);
  914. foreach($aTags[0] as $i => $aTag) $fieldData = str_replace("<A_{$i}>", $aTag, $fieldData);
  915. foreach($imgTags[0] as $i => $imgTag) $fieldData = str_replace("<IMG_{$i}>", $imgTag, $fieldData);
  916. foreach($iframeTags[0] as $i => $iframeTag) $fieldData = str_replace("<IFRAME_{$i}>", $iframeTag, $fieldData);
  917. foreach($embedTags[0] as $i => $embedTag) $fieldData = str_replace("<EMBED_{$i}>", $embedTag, $fieldData);
  918. foreach($preTags[0] as $i => $preTag) $fieldData = str_replace("<PRE_{$i}>", $preTag, $fieldData);
  919. $data->$field = $fieldData;
  920. }
  921. }
  922. return $data;
  923. }
  924. /**
  925. * Auto delete useless image.
  926. *
  927. * @param string $uid
  928. * @access public
  929. * @return void
  930. */
  931. public function autoDelete($uid)
  932. {
  933. if(empty($_SESSION['album'][$uid])) return;
  934. foreach($_SESSION['album'][$uid] as $imageID)
  935. {
  936. if(isset($_SESSION['album']['used'][$uid][$imageID])) continue;
  937. $file = $this->getById($imageID);
  938. if(empty($file)) continue;
  939. $this->dao->delete()->from(TABLE_FILE)->where('id')->eq($imageID)->exec();
  940. $this->unlinkFile($file);
  941. }
  942. unset($_SESSION['album'][$uid]);
  943. }
  944. /**
  945. * Send the download header to the client.
  946. *
  947. * @param string $fileName
  948. * @param string $fileType
  949. * @param string $content
  950. * @access public
  951. * @return void
  952. * @param string $type
  953. */
  954. public function sendDownHeader($fileName, $fileType, $content, $type = 'content')
  955. {
  956. /* Clean the ob content to make sure no space or utf-8 bom output. */
  957. $obLevel = ob_get_level();
  958. for($i = 0; $i < $obLevel; $i++) ob_end_clean();
  959. /* Set the downloading cookie, thus the export form page can use it to judge whether to close the window or not. */
  960. helper::setcookie('downloading', 1, 0, $this->config->webRoot, '', $this->config->cookieSecure, false);
  961. /* Append the extension name auto. */
  962. $extension = $fileType ? ('.' . $fileType) : '';
  963. if($extension && strpos(strtolower($fileName), $extension) === false) $fileName .= $extension;
  964. /* Judge the content type. */
  965. $mimes = $this->config->file->mimes;
  966. $contentType = isset($mimes[$fileType]) ? $mimes[$fileType] : $mimes['default'];
  967. /* Safari浏览器下载文件名乱码问题。 */
  968. if(isset($_SERVER['CONTENT_TYPE']) && isset($_SERVER['HTTP_USER_AGENT']) && $_SERVER['CONTENT_TYPE'] == 'application/x-www-form-urlencoded' && preg_match("/Safari/", $_SERVER["HTTP_USER_AGENT"]))
  969. {
  970. $fileName = rawurlencode($fileName);
  971. $attachment = 'attachment; filename*=utf-8\'\'' . $fileName;
  972. }
  973. else
  974. {
  975. $fileName = str_replace("+", "%20", urlencode($fileName));
  976. $attachment = "attachment; filename=\"{$fileName}\";";
  977. }
  978. helper::header('Content-type', $contentType);
  979. helper::header('Content-Disposition', $attachment);
  980. helper::header('Pragma', 'no-cache');
  981. helper::header('Expires', '0');
  982. if($type == 'content') helper::end($content);
  983. if($type == 'file' and file_exists($content))
  984. {
  985. if(stripos($content, $this->app->getBasePath()) !== 0) helper::end();
  986. set_time_limit(0);
  987. $chunkSize = 10 * 1024 * 1024;
  988. $handle = fopen($content, "r");
  989. while(!feof($handle)) echo fread($handle, $chunkSize);
  990. fclose($handle);
  991. helper::end();
  992. }
  993. }
  994. /**
  995. * Get image size.
  996. *
  997. * @param object $file
  998. * @access public
  999. * @return array|false
  1000. */
  1001. public function getImageSize($file)
  1002. {
  1003. if(empty($file->realPath)) return array();
  1004. if($this->config->file->storageType == 'fs')
  1005. {
  1006. return file_exists($file->realPath) ? getimagesize($file->realPath) : array(0, 0, $file->extension);
  1007. }
  1008. elseif($this->config->file->storageType == 's3')
  1009. {
  1010. $this->app->loadClass('ossclient', true);
  1011. $config = $this->config->file;
  1012. $ossClient = new ossclient($config->accessKeyId, $config->accessKeySecret, $config->endpoint, $config->bucket);
  1013. $info = $ossClient->getImageInfo($file->pathname);
  1014. return array($info->ImageWidth->value, $info->ImageHeight->value, 'img');
  1015. }
  1016. }
  1017. /**
  1018. * Get file pairs.
  1019. *
  1020. * @param string $idList
  1021. * @param string $value
  1022. * @access public
  1023. * @return array
  1024. */
  1025. public function getPairs($idList, $value = 'title')
  1026. {
  1027. return $this->dao->select("id,$value")->from(TABLE_FILE)->where('id')->in($idList)->fetchPairs();
  1028. }
  1029. /**
  1030. * Update test case version.
  1031. *
  1032. * @param object $file
  1033. * @access public
  1034. * @return void
  1035. */
  1036. public function updateTestcaseVersion($file)
  1037. {
  1038. $oldCase = $this->dao->select('*')->from(TABLE_CASE)->where('fromCaseID')->eq($file->objectID)->fetch();
  1039. if(empty($oldCase)) return;
  1040. $isLibCase = $oldCase->lib && empty($oldCase->product);
  1041. if($isLibCase)
  1042. {
  1043. $fromcaseVersion = $oldCase->fromCaseVersion + 1;
  1044. $this->dao->update(TABLE_CASE)->set('`fromCaseVersion`')->eq($fromcaseVersion)->where('`fromCaseID`')->eq($file->objectID)->exec();
  1045. }
  1046. }
  1047. /**
  1048. * Update story files.
  1049. *
  1050. * @param int $storyID
  1051. * @access public
  1052. * @return void
  1053. */
  1054. public function updateStoryFiles($storyID)
  1055. {
  1056. $storySpec = $this->dao->select('id')->from(TABLE_STORYSPEC)->where('story')->eq($storyID)->orderBy('id desc')->limit(1)->fetch();
  1057. if(empty($storySpec)) return;
  1058. $files = $this->dao->select('id')->from(TABLE_FILE)
  1059. ->where('objectType')->eq('story')
  1060. ->andWhere('objectID')->eq($storyID)
  1061. ->orderBy('id')
  1062. ->fetchAll();
  1063. $storyFiles = [];
  1064. foreach($files as $file) $storyFiles[] = $file->id;
  1065. $this->dao->update(TABLE_STORYSPEC)->set('files')->eq(implode(',', $storyFiles))->where('id')->eq($storySpec->id)->exec();
  1066. }
  1067. /**
  1068. * Process file info for object.
  1069. *
  1070. * @param string $objectType
  1071. * @param object $oldObject
  1072. * @param object $newObject
  1073. * @param string $extra
  1074. * @param string $filesName
  1075. * @param string $labelsName
  1076. * @access public
  1077. * @return void
  1078. */
  1079. public function processFile4Object($objectType, $oldObject, $newObject, $extra = '', $filesName = 'files', $labelsName = 'labels')
  1080. {
  1081. if(empty($oldObject->id)) return;
  1082. $oldFiles = empty($oldObject->files) ? '' : implode(',', array_keys($oldObject->files));
  1083. $deleteFiles = $newObject->deleteFiles ?? null;
  1084. if(!empty($deleteFiles))
  1085. {
  1086. if(!isset($this->config->file->logicalDeletionType[$objectType]))
  1087. {
  1088. $this->dao->delete()->from(TABLE_FILE)->where('id')->in($deleteFiles)->exec();
  1089. foreach($deleteFiles as $fileID)
  1090. {
  1091. $this->unlinkFile($oldObject->files[$fileID]);
  1092. $oldFiles = empty($oldFiles) ? '' : trim(str_replace(",$fileID,", ',', ",$oldFiles,"), ',');
  1093. }
  1094. }
  1095. else
  1096. {
  1097. foreach($deleteFiles as $fileID)
  1098. {
  1099. $this->dao->update(TABLE_FILE)->set('deleted')->eq(1)->where('id')->eq($fileID)->exec();
  1100. $oldFiles = empty($oldFiles) ? '' : trim(str_replace(",$fileID,", ',', ",$oldFiles,"), ',');
  1101. }
  1102. }
  1103. }
  1104. $this->updateObjectID($this->post->uid, $oldObject->id, $objectType);
  1105. $addedFiles = $this->saveUpload($objectType, $oldObject->id, $extra, $filesName, $labelsName);
  1106. $addedFiles = empty($addedFiles) ? '' : ',' . implode(',', array_keys($addedFiles));
  1107. $newObject->files = trim($oldFiles . $addedFiles, ',');
  1108. $oldObject->files = $oldFiles;
  1109. }
  1110. /**
  1111. * Print file in view/edit page.
  1112. *
  1113. * @param object $file
  1114. * @param string $method
  1115. * @param bool $showDelete
  1116. * @param bool $showEdit
  1117. * @param object|null $object
  1118. * @access public
  1119. * @return string
  1120. */
  1121. public function printFile($file, $method, $showDelete, $showEdit, $object)
  1122. {
  1123. $html = '';
  1124. $sessionString = session_name() . '=' . session_id();
  1125. $uploadDate = $this->lang->file->uploadDate . substr($file->addedDate, 0, 10);
  1126. $fileTitle = "<i class='icon icon-file-text'></i> &nbsp;" . $file->title;
  1127. $fileSize = helper::formatKB((float)$file->size);
  1128. if(strpos($file->title, ".{$file->extension}") === false && $file->extension != 'txt') $fileTitle .= ".{$file->extension}";
  1129. $imageWidth = 0;
  1130. if(stripos('jpg|jpeg|gif|png|bmp', $file->extension) !== false)
  1131. {
  1132. $imageSize = $this->getImageSize($file);
  1133. $imageWidth = $imageSize[0] ?? 0;
  1134. }
  1135. $downloadLink = helper::createLink('file', 'download', "fileID=$file->id");
  1136. $downloadLink .= strpos($downloadLink, '?') === false ? '?' : '&';
  1137. $downloadLink .= $sessionString;
  1138. $fileTitleLink = common::hasPriv('file', 'download') ? html::a($downloadLink, $fileTitle . " <span class='text-gray'>({$fileSize})</span>", '_blank', "id='fileTitle$file->id'") : "<div id='fileTitle$file->id' style='display: inline-block'>" . $fileTitle . " <span class='text-gray'>({$fileSize})</span></div>";
  1139. $objectType = zget($this->config->file->objectType, $file->objectType);
  1140. $viewMethod = $objectType == 'feedback' && $this->config->vision != 'lite' ? 'adminView' : 'view';
  1141. $html .= "<li class='mb-2 file' title='{$uploadDate}'>" . $fileTitleLink;
  1142. if(strpos('view,edit', $method) !== false)
  1143. {
  1144. if(common::hasPriv($objectType, $viewMethod, $object)) $html = $this->buildFileActions($html, $downloadLink, $imageWidth, $showEdit, $showDelete, $file, $object);
  1145. }
  1146. else
  1147. {
  1148. if(common::hasPriv($objectType, 'edit', $object))
  1149. {
  1150. $html .= "<span class='right-icon'>&nbsp; ";
  1151. if(common::hasPriv('file', 'edit')) $html .= html::a(helper::createLink('file', 'edit', "fileID=$file->id"), $this->lang->file->edit, '', "data-width='400' class='fileAction btn btn-link' title='{$this->lang->file->edit}'");
  1152. if(common::hasPriv('file', 'delete')) $html .= html::a('###', $this->lang->delete, '', "class='fileAction btn btn-link' onclick='deleteFile($file->id)' title='{$this->lang->delete}'");
  1153. $html .= '</span>';
  1154. }
  1155. }
  1156. $html .= '</li>';
  1157. return $html;
  1158. }
  1159. /**
  1160. * Build file actions.
  1161. *
  1162. * @param string $html
  1163. * @param string $downloadLink
  1164. * @param bool $showEdit
  1165. * @param bool $showEdit
  1166. * @param bool $showDelete
  1167. * @param object $file
  1168. * @param object|null $object
  1169. * @access public
  1170. * @return string
  1171. * @param int $imageWidth
  1172. */
  1173. public function buildFileActions($html, $downloadLink, $imageWidth, $showEdit, $showDelete, $file, $object)
  1174. {
  1175. $objectType = zget($this->config->file->objectType, $file->objectType);
  1176. $html .= "<span class='right-icon hidden'>&nbsp; ";
  1177. /* Determines whether the file supports preview. */
  1178. if($file->extension == 'txt')
  1179. {
  1180. $extension = 'txt';
  1181. if(($position = strrpos($file->title, '.')) !== false) $extension = substr($file->title, $position + 1);
  1182. $file->extension = $extension;
  1183. }
  1184. /* For the open source version of the file judgment. */
  1185. $canPreview = false;
  1186. $officeTypes = 'doc|docx|xls|xlsx|ppt|pptx|pdf';
  1187. $isOfficeFile = stripos($officeTypes, $file->extension) !== false;
  1188. if(stripos('txt|jpg|jpeg|gif|png|bmp|mp4', $file->extension) !== false) $canPreview = true;
  1189. if(isset($this->config->file->libreOfficeTurnon) and $this->config->file->libreOfficeTurnon == 1 && $isOfficeFile) $canPreview = true;
  1190. if($canPreview && common::hasPriv('file', 'preview'))
  1191. {
  1192. $dataToggle = $isOfficeFile ? '' : " data-toggle='modal' data-size='lg'";
  1193. $html .= html::a(helper::createLink('file', 'preview', "fileID=$file->id&mouse=left"), "<i class='icon icon-eye'></i>", $isOfficeFile ? '_blank' : '', "class='fileAction btn btn-link text-primary' title='{$this->lang->file->preview}' {$dataToggle}");
  1194. }
  1195. if(common::hasPriv('file', 'download')) $html .= html::a($downloadLink, "<i class='icon icon-download'></i>", '_blank', "class='fileAction btn btn-link text-primary' title='{$this->lang->file->downloadFile}'");
  1196. if(common::hasPriv($objectType, 'edit', $object))
  1197. {
  1198. if($showEdit and common::hasPriv('file', 'edit')) $html .= html::a('###', "<i class='icon icon-pencil-alt'></i>", '', "id='renameFile$file->id' class='fileAction btn btn-link edit text-primary' onclick='showRenameBox($file->id)' title='{$this->lang->file->edit}'");
  1199. if($showDelete and common::hasPriv('file', 'delete')) $html .= html::a('###', "<i class='icon icon-trash'></i>", '', "class='fileAction btn btn-link text-primary' onclick='deleteFile($file->id, this)' title='{$this->lang->delete}'");
  1200. }
  1201. $html .= '</span>';
  1202. $html .= '</li>';
  1203. $fileTitle = $file->title;
  1204. if(strrpos($fileTitle, '.') !== false)
  1205. {
  1206. /* Fix the file name exe.exe */
  1207. $title = explode('.', $fileTitle);
  1208. $extension = end($title);
  1209. if($file->extension == 'txt' && $extension != $file->extension) $file->extension = $extension;
  1210. array_pop($title);
  1211. $fileTitle = implode('.', $title);
  1212. }
  1213. $html .= "<li class='file hidden'><div>";
  1214. $html .= "<div class='renameFile w-300px' id='renameBox{$file->id}'><i class='icon icon-file-text'></i>";
  1215. $html .= "<div class='input-group'>";
  1216. $html .= "<input type='text' id='fileName{$file->id}' value='{$fileTitle}' class='form-control'/>";
  1217. $html .= "<input type='hidden' id='extension{$file->id}' value='{$file->extension}'/>";
  1218. $html .= "<strong class='input-group-addon'>.{$file->extension}</strong></div>";
  1219. $html .= "<div class='input-group-btn'>";
  1220. $html .= "<button type='button' class='btn btn-success file-name-confirm' onclick='setFileName({$file->id})' style='border-radius: 0px 2px 2px 0px; border-left-color: transparent;'><i class='icon icon-check'></i></button>";
  1221. $html .= "<button type='button' class='btn btn-gray file-name-cancel' onclick='showFile({$file->id})' style='border-radius: 0px 2px 2px 0px; border-left-color: transparent;'><i class='icon icon-close'></i></button>";
  1222. $html .= '</div></div></div></li>';
  1223. return $html;
  1224. }
  1225. /**
  1226. * Get last modified timestamp of file.
  1227. *
  1228. * @param object $file
  1229. * @access public
  1230. * @return int|false
  1231. */
  1232. public function fileMTime($file)
  1233. {
  1234. if(empty($file->realPath) or !file_exists($file->realPath)) return false;
  1235. return filemtime($file->realPath);
  1236. }
  1237. /**
  1238. * Get file size.
  1239. *
  1240. * @param object $file
  1241. * @access public
  1242. * @return int
  1243. */
  1244. public function fileSize($file)
  1245. {
  1246. if(empty($file->realPath) or !file_exists($file->realPath)) return false;
  1247. return filesize($file->realPath);
  1248. }
  1249. /**
  1250. * Save file to local storage temporarily.
  1251. *
  1252. * @param object $file
  1253. * @access public
  1254. * @return string
  1255. */
  1256. public function saveAsTempFile($file)
  1257. {
  1258. if(empty($file->realPath)) return '';
  1259. /* If the storage type is local, do nothing. */
  1260. if($this->config->file->storageType == 'fs') return $file->realPath;
  1261. return '';
  1262. }
  1263. /**
  1264. * 保存默认文件。
  1265. * Save default files.
  1266. *
  1267. * @param array $fileList
  1268. * @param string $objectType
  1269. * @param int|array $objectID
  1270. * @param string|int $extra
  1271. * @access public
  1272. * @param int|mixed[] $objectIdList
  1273. */
  1274. public function saveDefaultFiles($fileList, $objectType, $objectIdList, $extra = '')
  1275. {
  1276. if(empty($fileList)) return;
  1277. if(is_int($objectIdList)) $objectIdList = array($objectIdList);
  1278. if(!empty($_POST['deleteFiles']))
  1279. {
  1280. foreach($this->post->deleteFiles as $deletedFileID) unset($fileList[$deletedFileID]);
  1281. }
  1282. if(!empty($_POST['renameFiles']))
  1283. {
  1284. foreach($this->post->renameFiles as $renamedFileID => $newName) $fileList[$renamedFileID]['title'] = $newName;
  1285. }
  1286. foreach($objectIdList as $objectID)
  1287. {
  1288. $fileIdList = '';
  1289. foreach($fileList as $file)
  1290. {
  1291. unset($file['id']);
  1292. $file['objectType'] = $objectType;
  1293. $file['objectID'] = $objectID;
  1294. $file['extra'] = $extra;
  1295. $fileIdList .= ',' . $this->fileTao->saveFile($file, 'url,deleted,realPath,webPath,name,url');
  1296. }
  1297. if($objectType == 'story') $this->dao->update(TABLE_STORYSPEC)->set("files = CONCAT(files, '{$fileIdList}')")->where('story')->eq($objectID)->exec();
  1298. }
  1299. foreach($objectIdList as $objectID)
  1300. {
  1301. $uid = $this->post->uid ? $this->post->uid : '';
  1302. $this->updateObjectID($uid, $objectID, $objectType);
  1303. $this->saveUpload($objectType, $objectID, $extra);
  1304. }
  1305. }
  1306. /**
  1307. * Process file differcences for object.
  1308. *
  1309. * @param string $objectType
  1310. * @param object $oldObject
  1311. * @param object $newObject
  1312. * @param string $extra
  1313. * @param string $filesName
  1314. * @param string $labelsName
  1315. * @access public
  1316. * @return void
  1317. */
  1318. public function processFileDiffsForObject($objectType, $oldObject, $newObject, $extra = '', $filesName = 'files', $labelsName = 'labels')
  1319. {
  1320. if(empty($oldObject->id)) return;
  1321. $deleteFiles = array();
  1322. if(!empty($newObject->deleteFiles))
  1323. {
  1324. if(!isset($this->config->file->logicalDeletionType[$objectType]))
  1325. {
  1326. $this->dao->delete()->from(TABLE_FILE)->where('id')->in($newObject->deleteFiles)->exec();
  1327. foreach($newObject->deleteFiles as $fileID)
  1328. {
  1329. if(!isset($oldObject->{$filesName}[$fileID])) continue;
  1330. $this->unlinkFile($oldObject->{$filesName}[$fileID]);
  1331. $deleteFiles[] = isset($oldObject->{$filesName}[$fileID]) ? $oldObject->{$filesName}[$fileID]->title : '';
  1332. }
  1333. }
  1334. else
  1335. {
  1336. $this->dao->update(TABLE_FILE)->set('deleted')->eq(1)->where('id')->in($newObject->deleteFiles)->exec();
  1337. foreach($newObject->deleteFiles as $fileID) $deleteFiles[$fileID] = isset($oldObject->{$filesName}[$fileID]) ? $oldObject->{$filesName}[$fileID]->title : '';
  1338. }
  1339. }
  1340. $renameFiles = array();
  1341. if(!empty($newObject->renameFiles))
  1342. {
  1343. foreach($newObject->renameFiles as $renamedFileID => $newName)
  1344. {
  1345. $this->dao->update(TABLE_FILE)->set('title')->eq($newName)->where('id')->in($renamedFileID)->exec();
  1346. $renameFiles[$renamedFileID] = array('old' => isset($oldObject->{$filesName}[$renamedFileID]) ? $oldObject->{$filesName}[$renamedFileID]->title : '', 'new' => $newName);
  1347. }
  1348. }
  1349. $addedFiles = $this->saveUpload($objectType, $oldObject->id, $extra, $filesName, $labelsName);
  1350. if(defined('RUN_MODE') && RUN_MODE === 'api')
  1351. {
  1352. $uidFiles = $this->getUploadByUID($this->post->uid, $filesName, $labelsName);
  1353. $addedFiles = $addedFiles + $uidFiles;
  1354. }
  1355. if(!isset($oldObject->{$filesName})) $oldObject->{$filesName} = array();
  1356. $files = array_diff(array_keys($oldObject->{$filesName}), array_keys($deleteFiles));
  1357. $files = array_merge($files, array_keys($addedFiles));
  1358. $newObject->addedFiles = $addedFiles;
  1359. $newObject->renameFiles = $renameFiles;
  1360. $newObject->deleteFiles = $deleteFiles;
  1361. $newObject->{$filesName} = implode(',', $files);
  1362. }
  1363. }