commitb9e9f86d39Author: caoyanyi <caoyanyi@easycorp.ltd> Date: Fri Apr 26 13:35:56 2024 +0800 * Adjust mr actions style. commit1ddb2067b2Author: hufangzhou <hufangzhou@easycorp.ltd> Date: Fri Apr 26 13:24:05 2024 +0800 * Fix bug #48463. commit94528ed5b0Author: xieqiyu <xieqiyu@easycorp.ltd> Date: Fri Apr 26 13:17:29 2024 +0800 * Fix bug#48410. commitd1b38c54f9Author: caoyanyi <caoyanyi@easycorp.ltd> Date: Fri Apr 26 13:13:38 2024 +0800 - Remove mr repos switch block. commitc94da3e17aAuthor: sunhao <sunhao@easycorp.ltd> Date: Fri Apr 26 11:30:43 2024 +0800 * my: optimize UI of manage contacts page. commit2e74f33d22Author: 宋辰轩 <songchenxuan@easycorp.ltd> Date: Fri Apr 26 11:13:16 2024 +0800 * Fix bug #48317. commit45b6beccaeAuthor: liwenrui <liwenrui@easycorp.ltd> Date: Fri Apr 26 11:13:06 2024 +0800 * handle direct client download without selecting os. commit82be78538fAuthor: wangyuting <wangyuting@easycorp.ltd> Date: Fri Apr 26 10:43:42 2024 +0800 * Fix ipd project only one execution bug. commitfaa6c605fcAuthor: liumengyi <liumengyi@easycorp.ltd> Date: Fri Apr 26 10:42:25 2024 +0800 * Fix bug #48499, modify the icon. commit5134736779Author: xieqiyu <xieqiyu@easycorp.ltd> Date: Fri Apr 26 10:41:35 2024 +0800 * Fix bug#48167. commit5ca23cd76fAuthor: liumengyi <liumengyi@easycorp.ltd> Date: Fri Apr 26 10:30:02 2024 +0800 * Fix bug #48453. commit23cde4b38eAuthor: sunhao <sunhao@easycorp.ltd> Date: Fri Apr 26 10:19:28 2024 +0800 * tutorial: fix tutorial start modal style. commit1d710e10bdAuthor: sunhao <sunhao@easycorp.ltd> Date: Fri Apr 26 10:18:40 2024 +0800 * zin: fix batch-form style in old iframe modal. commitc9092603f0Author: liumengyi <liumengyi@easycorp.ltd> Date: Fri Apr 26 10:15:27 2024 +0800 * Fix bug #48495. commitc0aeb9f69dAuthor: sunguangming <sunguangming@easycorp.ltd> Date: Fri Apr 26 10:12:39 2024 +0800 * Fix bug #48431. commit4348fd543aAuthor: 宋辰轩 <songchenxuan@easycorp.ltd> Date: Fri Apr 26 10:11:45 2024 +0800 * Fix bug #48445. commitc9df08b1a1Author: 宋辰轩 <songchenxuan@easycorp.ltd> Date: Fri Apr 26 10:07:01 2024 +0800 * Fix bug for screen browse. commitebc469c0a4Author: wangyuting <wangyuting@easycorp.ltd> Date: Fri Apr 26 09:55:44 2024 +0800 * Adjust recentproject block css. commit9337fa109aAuthor: wangyuting <wangyuting@easycorp.ltd> Date: Fri Apr 26 09:50:34 2024 +0800 * Adjust recentproject block css. commit07903733bfAuthor: 王怡栋 <wangyidong@cnezsoft.com> Date: Fri Apr 26 09:33:39 2024 +0800 * Fix bug #48473. commita347de8354Author: wangyuting <wangyuting@easycorp.ltd> Date: Fri Apr 26 09:34:29 2024 +0800 * Add progress to recentproject block. task #116976. commite6e6c6047eAuthor: xieqiyu <xieqiyu@easycorp.ltd> Date: Fri Apr 26 09:29:12 2024 +0800 * Modify the problem of code review. commita53a577b69Author: sunguangming <sunguangming@easycorp.ltd> Date: Fri Apr 26 09:13:43 2024 +0800 * Finish task #45906. commit774cb4efbcAuthor: wangyuting <wangyuting@easycorp.ltd> Date: Fri Apr 26 09:03:42 2024 +0800 * Adjust review by me data to DAO search. commit976ef156b6Author: tianshujie <tianshujie@easycorp.ltd> Date: Fri Apr 26 09:00:51 2024 +0800 * Add class for copy project button. commit36f2c24a97Author: tianshujie <tianshujie@easycorp.ltd> Date: Fri Apr 26 09:00:39 2024 +0800 * Fix bug #48480. commitae9ef45621Author: xieqiyu <xieqiyu@easycorp.ltd> Date: Thu Apr 25 21:12:27 2024 +0800 * Optimated code. commit20eefb90dcAuthor: sunhao <sunhao@easycorp.ltd> Date: Thu Apr 25 19:42:50 2024 +0800 * zin: optimize UI for loading old pages. commit56006bca4fAuthor: sunhao <sunhao@easycorp.ltd> Date: Thu Apr 25 18:18:24 2024 +0800 * theme: update themes for old pages. commit0dd32057e4Author: liumengyi <liumengyi@easycorp.ltd> Date: Thu Apr 25 17:15:25 2024 +0800 * Modify style of productplan desc. commit272cdc4a61Author: liumengyi <liumengyi@easycorp.ltd> Date: Thu Apr 25 17:03:07 2024 +0800 * Fix bug #48413, replace executionLang with productplanLang. commit1f0b96aab2Author: tanghucheng <tanghucheng@easycorp.ltd> Date: Thu Apr 25 16:59:05 2024 +0800 * Program in the product lines can be empty. commitfb6bb48ee6Author: 宋辰轩 <songchenxuan@easycorp.ltd> Date: Thu Apr 25 17:02:29 2024 +0800 * Remove update bi guide img file. commit66bcb21455Author: liumengyi <liumengyi@easycorp.ltd> Date: Thu Apr 25 16:48:22 2024 +0800 * Fix bug #48464, set icon by project model. commitc5738c4129Author: 宋辰轩 <songchenxuan@easycorp.ltd> Date: Thu Apr 25 16:47:41 2024 +0800 * Fix bug for preview pivot. commitcc0a8aabceAuthor: hufangzhou <hufangzhou@easycorp.ltd> Date: Thu Apr 25 16:39:20 2024 +0800 + Add the or vision image. commit73d70324fdAuthor: hufangzhou <hufangzhou@easycorp.ltd> Date: Thu Apr 25 16:34:31 2024 +0800 * Change lang item. commita54780438aAuthor: liumengyi <liumengyi@easycorp.ltd> Date: Thu Apr 25 16:33:11 2024 +0800 * Modify the style of bugSteps. commitff5be095b4Author: tianshujie <tianshujie@easycorp.ltd> Date: Thu Apr 25 16:32:24 2024 +0800 * Fix bug #48453. commit0432554143Author: tianshujie <tianshujie@easycorp.ltd> Date: Thu Apr 25 16:21:52 2024 +0800 + Add comment. commit9155a82131Author: caoyanyi <caoyanyi@easycorp.ltd> Date: Thu Apr 25 16:07:55 2024 +0800 * Adjust jenkins job form style. commitdc272445cfAuthor: liumengyi <liumengyi@easycorp.ltd> Date: Thu Apr 25 15:59:31 2024 +0800 * Fix bug if the execution doesn't have the products. commit1fce0df29bAuthor: caoyanyi <caoyanyi@easycorp.ltd> Date: Thu Apr 25 15:48:41 2024 +0800 * Adjust app create tips. commita48e48394eAuthor: liumengyi <liumengyi@easycorp.ltd> Date: Thu Apr 25 15:47:09 2024 +0800 * Fix bug #48452. commitc500f9ce58Author: tianshujie <tianshujie@easycorp.ltd> Date: Thu Apr 25 15:27:17 2024 +0800 * Fix bug #48449. commit076b33652cAuthor: liumengyi <liumengyi@easycorp.ltd> Date: Thu Apr 25 15:11:57 2024 +0800 * Fix bug #48429, don't append relatedBugs to excludeBugs when linking bugs. commitbf59668dffAuthor: caoyanyi <caoyanyi@easycorp.ltd> Date: Fri Apr 19 11:22:22 2024 +0800 + Add page data for repo code view. commit91e83ce12bAuthor: caoyanyi <caoyanyi@easycorp.ltd> Date: Thu Apr 25 15:10:34 2024 +0800 * Adjust repo branch switch style. commit16147ac595Author: tianshujie <tianshujie@easycorp.ltd> Date: Thu Apr 25 15:09:44 2024 +0800 * Fix bug #48444. commitda77572ec8Author: wangyuting <wangyuting@easycorp.ltd> Date: Thu Apr 25 15:06:20 2024 +0800 * Adjust executionstatistic block burn. task #116977. commit73235c93c8Author: liumengyi <liumengyi@easycorp.ltd> Date: Thu Apr 25 15:03:45 2024 +0800 * Fix bug #48436, add ztfRun to the actions in execution::testcase. commit248b65ba6aAuthor: xieqiyu <xieqiyu@easycorp.ltd> Date: Thu Apr 25 15:02:39 2024 +0800 * Fix bug#48420. commit388f54b5ecAuthor: xieqiyu <xieqiyu@easycorp.ltd> Date: Thu Apr 25 15:01:03 2024 +0800 * Fix bug#48427. commit2c4122b084Author: liumengyi <liumengyi@easycorp.ltd> Date: Thu Apr 25 14:58:02 2024 +0800 * Fix bug #48441, keep in current tab when changing the stepsType. commitc9b094b3abAuthor: tianshujie <tianshujie@easycorp.ltd> Date: Thu Apr 25 14:58:13 2024 +0800 * Fix bug #48438. commitae10a1c686Author: liumengyi <liumengyi@easycorp.ltd> Date: Thu Apr 25 14:54:28 2024 +0800 * Fix bug #48435, replace `$this->session` with `$app->session`. commitc81031dab9Author: liumengyi <liumengyi@easycorp.ltd> Date: Thu Apr 25 14:49:42 2024 +0800 * Fix bug #48430, replace `{runID}`. commit0635779f1fAuthor: 王怡栋 <wangyidong@cnezsoft.com> Date: Thu Apr 25 14:48:58 2024 +0800 * Fix bug #48419. commit2b1358fb75Author: zhouxin <zhouxin@easycorp.ltd> Date: Thu Apr 25 14:46:08 2024 +0800 * Fix bug #47408. commitaef64e2c85Author: liumengyi <liumengyi@easycorp.ltd> Date: Thu Apr 25 14:45:30 2024 +0800 * Fix bug #48423, replace `<br />` with `<p></p>`. commit392a031495Author: tianshujie <tianshujie@easycorp.ltd> Date: Thu Apr 25 14:41:55 2024 +0800 * Show the product line when the program is not selected. commit1449ea4d06Author: tanghucheng <tanghucheng@easycorp.ltd> Date: Thu Apr 25 14:41:09 2024 +0800 * Fix bug #116431. commit9ce8dfe3eeAuthor: tianshujie <tianshujie@easycorp.ltd> Date: Thu Apr 25 14:31:03 2024 +0800 * Fix bug #48433. commit62a8779806Author: sunguangming <sunguangming@easycorp.ltd> Date: Thu Apr 25 14:28:56 2024 +0800 * Add loadProductModules function. commit8ec4efde62Author: liumengyi <liumengyi@easycorp.ltd> Date: Thu Apr 25 14:27:36 2024 +0800 * Add return type. commit035f15b16eAuthor: sunhao <sunhao@easycorp.ltd> Date: Thu Apr 25 14:21:50 2024 +0800 * disable loading effect on loading modal with same url. commit1b7aac3f6fAuthor: zhouxin <zhouxin@easycorp.ltd> Date: Thu Apr 25 14:19:34 2024 +0800 * Fix bug #47654. commit730ab12d86Author: sunhao <sunhao@easycorp.ltd> Date: Thu Apr 25 13:59:51 2024 +0800 * zin: add favicon to base page. commite7051b2d2fAuthor: liumengyi <liumengyi@easycorp.ltd> Date: Thu Apr 25 13:58:51 2024 +0800 * Fix bug #48421, removes duplicate projects. commit3089e95041Author: liumengyi <liumengyi@easycorp.ltd> Date: Thu Apr 25 13:43:47 2024 +0800 * Fix bug #48176. commit1409059467Author: tianshujie <tianshujie@easycorp.ltd> Date: Thu Apr 25 13:41:20 2024 +0800 * Fix bug #48418. commit6934f36b9eAuthor: zhouxin <zhouxin@easycorp.ltd> Date: Thu Apr 25 13:35:10 2024 +0800 * Remove useless method. commit908f7dd087Author: wangyuting <wangyuting@easycorp.ltd> Date: Thu Apr 25 13:30:56 2024 +0800 * Adjust teamachievement block css. task #116978. commitabb2d04bd0Author: zhouxin <zhouxin@easycorp.ltd> Date: Thu Apr 25 13:25:53 2024 +0800 * Optimize code. commit042726dd6aAuthor: tanghucheng <tanghucheng@easycorp.ltd> Date: Thu Apr 25 11:16:45 2024 +0800 * Fix bug #48143, Add $config->story->dtable->fieldList['roadmap']['width']. commitb3b3a4884bAuthor: tanghucheng <tanghucheng@easycorp.ltd> Date: Thu Apr 25 11:00:11 2024 +0800 * Fix bug #48415. commitb2878c8644Author: liumengyi <liumengyi@easycorp.ltd> Date: Thu Apr 25 10:59:32 2024 +0800 - Remove `var_dump`. commitb8e22a5446Author: liumengyi <liumengyi@easycorp.ltd> Date: Thu Apr 25 10:59:02 2024 +0800 * Optimize code for uploading files. commit328cf2c605Author: tianshujie <tianshujie@easycorp.ltd> Date: Thu Apr 25 10:57:27 2024 +0800 - Remove unused style. commit98f501ad66Author: sunhao <sunhao@easycorp.ltd> Date: Thu Apr 25 10:57:18 2024 +0800 * zui: update zui3 lib. commit138ae9b7bbAuthor: caoyanyi <caoyanyi@easycorp.ltd> Date: Thu Apr 25 10:55:12 2024 +0800 * Adjust repo page language. commit2a4abbde8dAuthor: 宋辰轩 <songchenxuan@easycorp.ltd> Date: Thu Apr 25 10:24:32 2024 +0800 * Fix bug #48350. commitc2c122b678Author: wangyuting <wangyuting@easycorp.ltd> Date: Thu Apr 25 10:22:08 2024 +0800 * Adjust welcome block css. task #116979. commit1cba83c80fAuthor: sunhao <sunhao@easycorp.ltd> Date: Thu Apr 25 10:21:24 2024 +0800 * kanban: optimize label style. commita92ca103b9Author: tianshujie <tianshujie@easycorp.ltd> Date: Thu Apr 25 10:18:16 2024 +0800 * Fix bug #48402. commitdecae20945Author: tianshujie <tianshujie@easycorp.ltd> Date: Thu Apr 25 10:11:24 2024 +0800 * Adjust select execution title style. commit8fc680ecc9Author: 宋辰轩 <songchenxuan@easycorp.ltd> Date: Thu Apr 25 10:03:04 2024 +0800 * Add delete file. commit0c08073647Author: tianshujie <tianshujie@easycorp.ltd> Date: Thu Apr 25 09:39:41 2024 +0800 * The date field was not validated when fixing the bug import task. commitb6aa30da6fAuthor: liyang <liyang@easycorp.ltd> Date: Thu Apr 25 09:41:02 2024 +0800 Add delete btn in import repo page. commit188bdfff90Author: liyang <liyang@easycorp.ltd> Date: Thu Apr 25 09:30:58 2024 +0800 Hide the hidden function of the imported code library page commit22e899b21fAuthor: sunguangming <sunguangming@easycorp.ltd> Date: Thu Apr 25 09:26:39 2024 +0800 * Fix bug #48372. commitdea8d5785aAuthor: tianshujie <tianshujie@easycorp.ltd> Date: Thu Apr 25 09:25:57 2024 +0800 * Remove the box when it is out of focus. commit94117eac4aAuthor: liumengyi <liumengyi@easycorp.ltd> Date: Thu Apr 25 09:21:31 2024 +0800 * Modify testtask's order in qastatistic block. commit16558b955dAuthor: 宋辰轩 <songchenxuan@easycorp.ltd> Date: Thu Apr 25 09:18:46 2024 +0800 * Use ajaxGetOldDropMenu commiteb7f606fe5Author: hufangzhou <hufangzhou@easycorp.ltd> Date: Thu Apr 25 09:07:24 2024 +0800 * Add todo-calendar privilege to or interface. commit119fef4faeAuthor: wangyuting <wangyuting@easycorp.ltd> Date: Thu Apr 25 09:06:33 2024 +0800 * Adjust closedSetting to closeSetting. commit1179f36595Author: tanghucheng <tanghucheng@easycorp.ltd> Date: Thu Apr 25 08:58:31 2024 +0800 * Fix bug #47758, unset percent when disabled. commitb143356a55Author: sunguangming <sunguangming@easycorp.ltd> Date: Thu Apr 25 08:56:35 2024 +0800 * Fix bug #48379. commite1ec138058Author: liumengyi <liumengyi@easycorp.ltd> Date: Thu Apr 25 08:52:51 2024 +0800 * Optimize code for bug #48271. commit8ca008736aAuthor: liwenrui <liwenrui@easycorp.ltd> Date: Thu Apr 25 08:49:31 2024 +0800 * tweak again, actually only wanted to add padding to the top. commit388006b340Author: liwenrui <liwenrui@easycorp.ltd> Date: Thu Apr 25 08:49:05 2024 +0800 * tweak zeneditor to add some padding back. commit0e0121b201Author: liwenrui <liwenrui@easycorp.ltd> Date: Thu Apr 25 08:27:23 2024 +0800 * update zeneditor and remove paragraph margin within. commit5e3b42a9f3Author: sunhao <sunhao@easycorp.ltd> Date: Wed Apr 24 17:32:20 2024 +0800 * zui: update zui3 lib. commit3e543c1403Author: wangyuting <wangyuting@easycorp.ltd> Date: Wed Apr 24 17:30:35 2024 +0800 * Optimize code. commitce8e34849dAuthor: wangyuting <wangyuting@easycorp.ltd> Date: Wed Apr 24 17:29:17 2024 +0800 * Fix project menu bug. bug #48121. commitbff8da8abaAuthor: sunhao <sunhao@easycorp.ltd> Date: Wed Apr 24 17:20:38 2024 +0800 * zin: sort fields after reload form. commit01e15f6635Author: liumengyi <liumengyi@easycorp.ltd> Date: Wed Apr 24 16:53:10 2024 +0800 * Fix bug #46381. commitb89caabfceAuthor: hufangzhou <hufangzhou@easycorp.ltd> Date: Wed Apr 24 16:53:21 2024 +0800 * Adjust the default filename for export execution. commit17d5d65a17Author: liumengyi <liumengyi@easycorp.ltd> Date: Wed Apr 24 16:47:28 2024 +0800 * Fix bug #48367, get linked bugs. commit3ee2f3ea5cAuthor: 宋辰轩 <songchenxuan@easycorp.ltd> Date: Wed Apr 24 16:45:43 2024 +0800 * Fix bug for preview pivot menu. commit2c00f9a657Author: xieqiyu <xieqiyu@easycorp.ltd> Date: Wed Apr 24 16:29:25 2024 +0800 * Fix bug#48334. commit484e24fd9dAuthor: liumengyi <liumengyi@easycorp.ltd> Date: Wed Apr 24 16:24:24 2024 +0800 * Fix bug #48343. commit1aa110645cAuthor: liwenrui <liwenrui@easycorp.ltd> Date: Wed Apr 24 16:01:19 2024 +0800 * fix ai prompt menu injected multiple times, bug #48358. commita2333979b5Author: zhouxin <zhouxin@easycorp.ltd> Date: Wed Apr 24 15:59:09 2024 +0800 * Fix height of project deviation pivot. commit7a3b20222fAuthor: liumengyi <liumengyi@easycorp.ltd> Date: Wed Apr 24 15:54:16 2024 +0800 * Replace runID in testcase::browse. commitab05d56301Author: tianshujie <tianshujie@easycorp.ltd> Date: Wed Apr 24 15:45:01 2024 +0800 * No iterative project hides execution column. commitdffcd9e5c7Author: sunguangming <sunguangming@easycorp.ltd> Date: Wed Apr 24 15:46:18 2024 +0800 * Fix sql error. commit22d89bb361Author: xieqiyu <xieqiyu@easycorp.ltd> Date: Wed Apr 24 15:43:11 2024 +0800 * Fix bug#48349. commit603195b1e1Author: tianshujie <tianshujie@easycorp.ltd> Date: Wed Apr 24 15:41:27 2024 +0800 * Adjust execution column style of the build list. commit989f2b8ca5Author: liumengyi <liumengyi@easycorp.ltd> Date: Wed Apr 24 15:27:01 2024 +0800 * Fix bug #48344, set runID. commit2db2a7f9dcAuthor: caoyanyi <caoyanyi@easycorp.ltd> Date: Wed Apr 24 15:25:54 2024 +0800 - Remove gitfox for 20.0. commit3a1f824210Author: xieqiyu <xieqiyu@easycorp.ltd> Date: Wed Apr 24 15:24:46 2024 +0800 * Fix bug#48351. commitaf0b038283Author: tianshujie <tianshujie@easycorp.ltd> Date: Wed Apr 24 15:14:43 2024 +0800 * Fix bug #48348. commitd4dc98d604Author: tanghucheng <tanghucheng@easycorp.ltd> Date: Wed Apr 24 15:13:00 2024 +0800 * Fix bug #48140, Enabled child stage for ipd project. commit08e0f83941Author: tianshujie <tianshujie@easycorp.ltd> Date: Wed Apr 24 15:07:36 2024 +0800 * Adjust height of the batchcreate stakeholder page. commit8e1dfa7634Author: tianshujie <tianshujie@easycorp.ltd> Date: Wed Apr 24 14:58:29 2024 +0800 * Fix bug #48345. commit0562ebb542Author: sunguangming <sunguangming@easycorp.ltd> Date: Wed Apr 24 14:55:42 2024 +0800 * Add story pairs to search form. commit2c062163a5Author: tanghucheng <tanghucheng@easycorp.ltd> Date: Wed Apr 24 14:40:10 2024 +0800 * Fix bug #48314. commitb812069307Author: qixinzhi <qixinzhi@easycorp.ltd> Date: Wed Apr 24 06:38:51 2024 +0000 * Auto column width with cell max value length. commitf9c1a3e850Author: qixinzhi <qixinzhi@easycorp.ltd> Date: Wed Apr 24 06:38:04 2024 +0000 * Delete screen 3 scheme. commit3ffa92734fAuthor: hufangzhou <hufangzhou@easycorp.ltd> Date: Wed Apr 24 14:35:14 2024 +0800 * Fix bug #48239. commit99e1864ca8Author: liumengyi <liumengyi@easycorp.ltd> Date: Wed Apr 24 14:30:08 2024 +0800 * Fix bug #46381. commit81c3ebaa0aAuthor: caoyanyi <caoyanyi@easycorp.ltd> Date: Wed Apr 24 14:28:26 2024 +0800 + Add time fliter for commit search. commit14d5bf7cf0Author: caoyanyi <caoyanyi@easycorp.ltd> Date: Wed Apr 24 09:34:53 2024 +0800 * Adjust repo create page linkage. commit6880ef70afAuthor: liumengyi <liumengyi@easycorp.ltd> Date: Wed Apr 24 14:22:28 2024 +0800 * Replace `->size` with `set::width` and `set::height` in echarts. commitb4ea64832aAuthor: caoyanyi <caoyanyi@easycorp.ltd> Date: Wed Apr 24 14:15:13 2024 +0800 * Adjust job view repo name. commit8168c98ba8Author: tianshujie <tianshujie@easycorp.ltd> Date: Wed Apr 24 14:13:53 2024 +0800 * Fix bug #48262. commitd480ef87bdAuthor: liumengyi <liumengyi@easycorp.ltd> Date: Wed Apr 24 13:51:05 2024 +0800 * Fix bug #48025, modify id. commit003a73d5cbAuthor: liumengyi <liumengyi@easycorp.ltd> Date: Wed Apr 24 13:49:12 2024 +0800 * Fix bug #48025, modify id. commit3ad257eb53Author: daitingting <daitingting@xirangit.com> Date: Wed Apr 24 05:43:53 2024 +0000 * Fix bug #48261. commit77e6744f1dAuthor: tianshujie <tianshujie@easycorp.ltd> Date: Wed Apr 24 13:42:13 2024 +0800 * Fix bug #48294. commitf10b3f5710Author: tianshujie <tianshujie@easycorp.ltd> Date: Wed Apr 24 13:36:01 2024 +0800 * Reset code. commit068f97bc38Author: liumengyi <liumengyi@easycorp.ltd> Date: Wed Apr 24 13:18:34 2024 +0800 * Fix bug #48308, set control of stage. commit078d566703Author: wangyuting <wangyuting@easycorp.ltd> Date: Wed Apr 24 13:18:41 2024 +0800 * Adjust edit feedback data toggle. commit34738e42d9Author: zhouxin <zhouxin@easycorp.ltd> Date: Wed Apr 24 13:13:29 2024 +0800 * Adjust lang. commiteddba468d0Author: liumengyi <liumengyi@easycorp.ltd> Date: Wed Apr 24 13:07:40 2024 +0800 * Fix bug #48310. commitfc673b76ceAuthor: liumengyi <liumengyi@easycorp.ltd> Date: Wed Apr 24 11:26:49 2024 +0800 * Fix bug #48310. commit7840c1ab35Author: sunguangming <sunguangming@easycorp.ltd> Date: Wed Apr 24 11:24:40 2024 +0800 * Remove dm driver from convert date. commitd47e91c07eAuthor: hufangzhou <hufangzhou@easycorp.ltd> Date: Wed Apr 24 11:20:10 2024 +0800 * Fix bug #48293. commitd475b63a74Author: daitingting <daitingting@xirangit.com> Date: Wed Apr 24 03:16:57 2024 +0000 * Fix bug #48261. commit655cc652beAuthor: 王怡栋 <wangyidong@cnezsoft.com> Date: Wed Apr 24 11:14:54 2024 +0800 * Fix bug for select usage when install. commit425fade713Author: tianshujie <tianshujie@easycorp.ltd> Date: Wed Apr 24 11:14:19 2024 +0800 * Fix bug #48303. commit723797107bAuthor: tianshujie <tianshujie@easycorp.ltd> Date: Wed Apr 24 11:09:27 2024 +0800 * Fix bug #48303. commit6740a4c699Author: tianshujie <tianshujie@easycorp.ltd> Date: Wed Apr 24 11:06:32 2024 +0800 * Fix bug #48306. commitc42f7ad83eAuthor: zhouxin <zhouxin@easycorp.ltd> Date: Wed Apr 24 11:02:21 2024 +0800 * Fix bug #48073. commite38f2f6531Author: liumengyi <liumengyi@easycorp.ltd> Date: Wed Apr 24 10:42:42 2024 +0800 * Set search form open. commit2cb511cf52Author: 王怡栋 <wangyidong@cnezsoft.com> Date: Wed Apr 24 10:07:43 2024 +0800 * Fix error for sso module. commitf8b093e0ebAuthor: xieqiyu <xieqiyu@easycorp.ltd> Date: Wed Apr 24 10:21:37 2024 +0800 * Fix the bug of duration and BSA default value. commitc3e9c7d7cdAuthor: liumengyi <liumengyi@easycorp.ltd> Date: Wed Apr 24 10:20:25 2024 +0800 * Fix bug #48243. commitc7cf68be63Author: hufangzhou <hufangzhou@easycorp.ltd> Date: Wed Apr 24 10:17:03 2024 +0800 * Fix bug #48280, #48289. commitb9cdcf5bcdAuthor: sunhao <sunhao@easycorp.ltd> Date: Wed Apr 24 10:17:34 2024 +0800 * my: fix cannot update badges in main navbar. commit3aa9609ce2Author: tianshujie <tianshujie@easycorp.ltd> Date: Wed Apr 24 10:16:21 2024 +0800 * Fix bug #48294. commitfb26e627deAuthor: sunhao <sunhao@easycorp.ltd> Date: Wed Apr 24 10:10:56 2024 +0800 Revert "* zin: fix cannot find page method on update." This reverts commit367a4ae49d. commit63a70421edAuthor: tianshujie <tianshujie@easycorp.ltd> Date: Wed Apr 24 09:45:07 2024 +0800 * Fix bug #48271. commitc1a66b708bAuthor: sunguangming <sunguangming@easycorp.ltd> Date: Wed Apr 24 09:40:09 2024 +0800 * Modify upgrade method. commitc68b2170f9Author: 王怡栋 <wangyidong@cnezsoft.com> Date: Wed Apr 24 09:18:09 2024 +0800 * Fix for sso login from zdoo. commit580b4f170fAuthor: liumengyi <liumengyi@easycorp.ltd> Date: Wed Apr 24 09:30:37 2024 +0800 * Fix bug #48272, update program's consumed, left and progress. commit1d9791a55eAuthor: 王怡栋 <wangyidong@cnezsoft.com> Date: Tue Apr 23 17:27:07 2024 +0800 * Remove useless code. commit40f0e10c1fAuthor: caoyanyi <caoyanyi@easycorp.ltd> Date: Tue Apr 23 17:18:03 2024 +0800 * Update standard sql. # Conflicts: # db/update20.0.beta2.sql # module/block/config/feedback.php # module/block/zen.php # module/custom/lang/vi.php # module/mr/ui/browsebyexecution.html.php # module/repo/lang/de.php # module/repo/lang/en.php # module/repo/lang/fr.php # module/repo/lang/zh-cn.php # module/space/lang/de.php # module/space/lang/en.php # module/space/lang/fr.php # module/space/lang/zh-cn.php # module/space/ui/createapplication.html.php # www/js/zui3/zen-editor/index.esm.js # www/js/zui3/zen-editor/p-0aaccc7e.js # www/js/zui3/zen-editor/p-276dd30f.js # www/js/zui3/zen-editor/p-2b13cc9a.js # www/js/zui3/zen-editor/p-38d18c5d.js # www/js/zui3/zen-editor/p-3f6cad83.js # www/js/zui3/zen-editor/p-51e3a048.js # www/js/zui3/zen-editor/p-617d463f.js # www/js/zui3/zen-editor/p-62c0132e.js # www/js/zui3/zen-editor/p-6d22490c.js # www/js/zui3/zen-editor/p-a563f50b.js # www/js/zui3/zen-editor/p-bf31230c.js # www/js/zui3/zen-editor/p-c6c95773.js # www/js/zui3/zen-editor/p-d83dd3f0.js # www/js/zui3/zen-editor/p-d8bedd14.js # www/js/zui3/zen-editor/p-eaaa4129.js # www/js/zui3/zen-editor/zen-editor.esm.js
1245 lines
44 KiB
PHP
Executable File
1245 lines
44 KiB
PHP
Executable File
<?php
|
|
declare(strict_types=1);
|
|
/**
|
|
* The model file of file module of ZenTaoPMS.
|
|
*
|
|
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
|
|
* @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
|
|
* @author Chunsheng Wang <chunsheng@cnezsoft.com>
|
|
* @package file
|
|
* @version $Id: model.php 4976 2013-07-02 08:15:31Z wyd621@gmail.com $
|
|
* @link https://www.zentao.net
|
|
*/
|
|
?>
|
|
<?php
|
|
class fileModel extends model
|
|
{
|
|
public $savePath = '';
|
|
public $webPath = '';
|
|
public $now = 0;
|
|
|
|
/**
|
|
* Construct function.
|
|
*
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
$this->now = time();
|
|
$this->setSavePath();
|
|
$this->setWebPath();
|
|
}
|
|
|
|
/**
|
|
* Get files of an object.
|
|
*
|
|
* @param string $objectType
|
|
* @param int|array $objectID
|
|
* @param string $extra
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function getByObject(string $objectType, int|array $objectID, string $extra = ''): array
|
|
{
|
|
$files = $this->dao->select('*')->from(TABLE_FILE)
|
|
->where('objectType')->eq($objectType)
|
|
->andWhere('objectID')->in($objectID)
|
|
->andWhere('extra')->ne('editor')
|
|
->beginIF($extra)->andWhere('extra')->in($extra)->fi()
|
|
->andWhere('deleted')->eq('0')
|
|
->orderBy('id')
|
|
->fetchAll('id');
|
|
|
|
foreach($files as $file) $this->setFileWebAndRealPaths($file);
|
|
return $files;
|
|
}
|
|
|
|
/**
|
|
* Delete files by object.
|
|
*
|
|
* @param string $objectType
|
|
* @param int $objectID
|
|
* @access public
|
|
* @return bool
|
|
*/
|
|
public function deleteByObject($objectType, $objectID)
|
|
{
|
|
$files = $this->dao->select('*')->from(TABLE_FILE)
|
|
->where('objectType')->eq($objectType)
|
|
->andWhere('objectID')->in($objectID)
|
|
->andWhere('deleted')->eq('0')
|
|
->fetchAll('id');
|
|
|
|
foreach($files as $file)
|
|
{
|
|
$this->dao->update(TABLE_FILE)->set('deleted')->eq(1)->where('id')->eq($file->id)->exec();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get info of a file.
|
|
*
|
|
* @param int $fileID
|
|
* @access public
|
|
* @return object|false
|
|
*/
|
|
public function getById(int $fileID): object|false
|
|
{
|
|
$file = $this->dao->findById($fileID)->from(TABLE_FILE)->fetch();
|
|
if(empty($file)) return false;
|
|
|
|
$this->setFileWebAndRealPaths($file);
|
|
return $file;
|
|
}
|
|
|
|
/**
|
|
* Get files by ID list.
|
|
*
|
|
* @param string|array $fileIdList
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function getByIdList(string|array $fileIdList): array
|
|
{
|
|
if(empty($fileIdList)) return array();
|
|
|
|
$files = $this->dao->select('*')->from(TABLE_FILE)->where('id')->in($fileIdList)->orderBy('id')->fetchAll('id');
|
|
foreach($files as $file) $this->setFileWebAndRealPaths($file);
|
|
|
|
return $files;
|
|
}
|
|
|
|
/**
|
|
* Save upload.
|
|
*
|
|
* @param string $objectType
|
|
* @param int $objectID
|
|
* @param int|string $extra
|
|
* @param string $filesName
|
|
* @param string $labelsName
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function saveUpload(string $objectType = '', int $objectID = 0, int|string $extra = '', string $filesName = 'files', string $labelsName = 'labels'): array|false
|
|
{
|
|
$fileTitles = array();
|
|
$now = helper::today();
|
|
$files = $this->getUpload($filesName, $labelsName);
|
|
|
|
foreach($files as $file)
|
|
{
|
|
if($file['size'] == 0) continue;
|
|
if(!move_uploaded_file($file['tmpname'], $this->savePath . $this->getSaveName($file['pathname']))) return false;
|
|
|
|
$file = $this->compressImage($file);
|
|
|
|
$file['objectType'] = $objectType;
|
|
$file['objectID'] = $objectID;
|
|
$file['addedBy'] = $this->app->user->account;
|
|
$file['addedDate'] = $now;
|
|
$file['extra'] = $extra;
|
|
unset($file['tmpname']);
|
|
$this->dao->insert(TABLE_FILE)->data($file)->exec();
|
|
$fileTitles[$this->dao->lastInsertId()] = $file['title'];
|
|
}
|
|
return $fileTitles;
|
|
}
|
|
|
|
/**
|
|
* Save a file.
|
|
*
|
|
* @param array $file
|
|
* @param string $objectType
|
|
* @param int $objectID
|
|
* @param string $extra
|
|
* @param string $filesName
|
|
* @param string $labelsName
|
|
* @access public
|
|
* @return object
|
|
*/
|
|
public function saveAFile(array $file, string $objectType = '', int $objectID = 0, string $extra = '', string $filesName = 'files', string $labelsName = 'labels'): object|false
|
|
{
|
|
$now = helper::today();
|
|
|
|
if($file['size'] == 0) return false;
|
|
if(!move_uploaded_file($file['tmpname'], $this->savePath . $this->getSaveName($file['pathname']))) return false;
|
|
|
|
$file = $this->compressImage($file);
|
|
|
|
$file['objectType'] = $objectType;
|
|
$file['objectID'] = $objectID;
|
|
$file['addedBy'] = $this->app->user->account;
|
|
$file['addedDate'] = $now;
|
|
$file['extra'] = $extra;
|
|
unset($file['tmpname']);
|
|
$this->dao->insert(TABLE_FILE)->data($file)->exec();
|
|
|
|
$fileTitle = new stdclass();
|
|
$fileTitle->id = $this->dao->lastInsertId();
|
|
$fileTitle->title = $file['title'];
|
|
|
|
return $fileTitle;
|
|
}
|
|
|
|
/**
|
|
* Get counts of uploaded files.
|
|
*
|
|
* @access public
|
|
* @return int
|
|
*/
|
|
public function getCount(): int
|
|
{
|
|
return count($this->getUpload());
|
|
}
|
|
|
|
/**
|
|
* Get info of uploaded files.
|
|
*
|
|
* @param string $htmlTagName
|
|
* @param string $labelsName
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function getUpload(string $htmlTagName = 'files', string $labelsName = 'labels'): array
|
|
{
|
|
$files = array();
|
|
if(!isset($_FILES[$htmlTagName])) return $files;
|
|
if(!is_array($_FILES[$htmlTagName]['error']) && $_FILES[$htmlTagName]['error'] != 0) return $_FILES[$htmlTagName];
|
|
|
|
$this->app->loadClass('purifier', true);
|
|
$config = HTMLPurifier_Config::createDefault();
|
|
$config->set('Cache.DefinitionImpl', null);
|
|
$purifier = new HTMLPurifier($config);
|
|
|
|
/* If the file var name is an array. */
|
|
if(is_array($_FILES[$htmlTagName]['name']))
|
|
{
|
|
extract($_FILES[$htmlTagName]);
|
|
foreach($name as $id => $filename)
|
|
{
|
|
if(empty($filename)) continue;
|
|
if(!validater::checkFileName($filename)) continue;
|
|
|
|
$title = isset($_POST[$labelsName][$id]) ? $_POST[$labelsName][$id] : '';
|
|
$file['extension'] = $this->getExtension($filename);
|
|
$file['pathname'] = $this->setPathName($id, $file['extension']);
|
|
$file['title'] = (!empty($title) and $title != $filename) ? htmlSpecialString($title) : $filename;
|
|
$file['title'] = $purifier->purify($file['title']);
|
|
$file['size'] = $size[$id];
|
|
$file['tmpname'] = $tmp_name[$id];
|
|
$files[] = $file;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(empty($_FILES[$htmlTagName]['name'])) return $files;
|
|
extract($_FILES[$htmlTagName]);
|
|
if(!validater::checkFileName($name)) return array();
|
|
$title = isset($_POST[$labelsName][0]) ? $_POST[$labelsName][0] : '';
|
|
$file['extension'] = $this->getExtension($name);
|
|
$file['pathname'] = $this->setPathName(0, $file['extension']);
|
|
$file['title'] = (!empty($title) && $title != $name) ? htmlSpecialString($title) : $name;
|
|
$file['title'] = $purifier->purify($file['title']);
|
|
$file['size'] = $size;
|
|
$file['tmpname'] = $tmp_name;
|
|
return array($file);
|
|
}
|
|
return $files;
|
|
}
|
|
|
|
/**
|
|
* get uploaded file from zui.uploader.
|
|
*
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function getChunkedFile(): array
|
|
{
|
|
$this->app->loadClass('purifier', true);
|
|
$config = HTMLPurifier_Config::createDefault();
|
|
$config->set('Cache.DefinitionImpl', null);
|
|
$purifier = new HTMLPurifier($config);
|
|
|
|
$name = urldecode(zget($_SERVER, 'HTTP_X_FILENAME', ''));
|
|
if(!validater::checkFileName($name) or empty($name)) return array();
|
|
|
|
$file = array();
|
|
$file['id'] = 0;
|
|
$file['extension'] = $this->getExtension($name);
|
|
$file['title'] = $purifier->purify(substr($name, 0, strpos($name, $file['extension']) - 1));
|
|
$file['size'] = zget($_SERVER, 'HTTP_X_FILESIZE', 0);
|
|
$file['pathname'] = md5($file['title']);
|
|
$file['chunks'] = zget($_SERVER, 'HTTP_X_TOTAL_CHUNKS', 0);
|
|
$file['chunkIndex'] = zget($_SERVER, 'HTTP_X_CHUNK_INDEX', 0);
|
|
|
|
if(stripos($this->config->file->allowed, ",{$file['extension']},") === false) $file['pathname'] = $file['pathname'] . '.notAllowed';
|
|
return $file;
|
|
}
|
|
|
|
/**
|
|
* Save uploaded file by chunk.
|
|
*
|
|
* @param array $file
|
|
* @param int $uid
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function saveChunkedFile(array $file, string $uid): array
|
|
{
|
|
if($file['chunks'] == 0) return array();
|
|
|
|
$tmpFilePath = $this->app->getTmpRoot() . 'uploadfiles/';
|
|
$tmpFileSavePath = $tmpFilePath . $uid . '/';
|
|
if(!is_dir($tmpFileSavePath)) mkdir($tmpFileSavePath, 0777, true);
|
|
|
|
$file['realpath'] = $tmpFileSavePath . basename($this->getSaveName($file['pathname']));
|
|
file_put_contents($file['realpath'], file_get_contents('php://input'), $file['chunkIndex'] == 0 ? 0 : FILE_APPEND);
|
|
|
|
$uploadFile = array();
|
|
$uploadFile['extension'] = $file['extension'];
|
|
$uploadFile['pathname'] = $file['pathname'];
|
|
$uploadFile['title'] = $file['title'];
|
|
$uploadFile['realpath'] = $file['realpath'];
|
|
$uploadFile['size'] = $file['size'];
|
|
|
|
return $uploadFile;
|
|
}
|
|
|
|
/**
|
|
* Get extension of a file.
|
|
*
|
|
* @param string $filename
|
|
* @access public
|
|
* @return string
|
|
*/
|
|
public function getExtension(string $filename): string
|
|
{
|
|
$extension = trim(strtolower(pathinfo($filename, PATHINFO_EXTENSION)));
|
|
if($extension and strpos($extension, '::') !== false) $extension = substr($extension, 0, strpos($extension, '::'));
|
|
|
|
if(empty($extension) or stripos(",{$this->config->file->dangers},", ",{$extension},") !== false) return 'txt';
|
|
if(empty($extension) or stripos(",{$this->config->file->allowed},", ",{$extension},") === false) return 'txt';
|
|
if($extension == 'php') return 'txt';
|
|
return $extension;
|
|
}
|
|
|
|
/**
|
|
* Get save name.
|
|
*
|
|
* @param string $pathName
|
|
* @access public
|
|
* @return string
|
|
*/
|
|
public function getSaveName(string $pathName): string
|
|
{
|
|
$position = strrpos($pathName, '.');
|
|
return $position === false ? $pathName : substr($pathName, 0, $position);
|
|
}
|
|
|
|
/**
|
|
* Get real path name.
|
|
*
|
|
* @param string $pathName
|
|
* @access public
|
|
* @return string
|
|
*/
|
|
public function getRealPathName(string $pathName): string
|
|
{
|
|
$realPath = $this->savePath . $pathName;
|
|
if(file_exists($realPath)) return $pathName;
|
|
|
|
return $this->getSaveName($pathName);
|
|
}
|
|
|
|
/**
|
|
* Get export tpl.
|
|
*
|
|
* @param string $module
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function getExportTemplate(string $module): array
|
|
{
|
|
return $this->dao->select('id,title,content,public')->from(TABLE_USERTPL)
|
|
->where('type')->eq("export$module")
|
|
->andwhere('account', true)->eq($this->app->user->account)
|
|
->orWhere('public')->eq('1')
|
|
->markRight(1)
|
|
->orderBy('id')
|
|
->fetchAll();
|
|
}
|
|
|
|
/**
|
|
* Get tmp import path.
|
|
*
|
|
* @access public
|
|
* @return string
|
|
*/
|
|
public function getPathOfImportedFile(): string
|
|
{
|
|
$path = $this->app->getTmpRoot() . 'import';
|
|
if(!is_dir($path)) mkdir($path, 0755, true);
|
|
|
|
return $path;
|
|
}
|
|
|
|
/**
|
|
* Save export template.
|
|
*
|
|
* @param string $module
|
|
* @access public
|
|
* @return int|false
|
|
*/
|
|
public function saveExportTemplate(string $module): int|false
|
|
{
|
|
$template = fixer::input('post')
|
|
->add('account', $this->app->user->account)
|
|
->add('type', "export$module")
|
|
->join('content', ',')
|
|
->get();
|
|
|
|
if($template->title == $this->lang->file->defaultTPL)
|
|
{
|
|
dao::$errors[] = sprintf($this->lang->error->unique, $this->lang->file->tplTitle, $this->lang->file->defaultTPL);
|
|
return false;
|
|
}
|
|
|
|
$condition = "`type`='export$module' and account='{$this->app->user->account}'";
|
|
$this->dao->insert(TABLE_USERTPL)->data($template)->batchCheck('title, content', 'notempty')->check('title', 'unique', $condition)->exec();
|
|
return $this->dao->lastInsertId();
|
|
}
|
|
|
|
/**
|
|
* Set path name of the uploaded file to be saved.
|
|
*
|
|
* @param int $fileID
|
|
* @param string $extension
|
|
* @access public
|
|
* @return string
|
|
*/
|
|
public function setPathName(int $fileID, string $extension): string
|
|
{
|
|
$sessionID = session_id();
|
|
$randString = substr($sessionID, mt_rand(0, strlen($sessionID) - 5), 3);
|
|
return date('Ym/dHis', $this->now) . $fileID . mt_rand(0, 10000) . $randString . '.' . $extension;
|
|
}
|
|
|
|
/**
|
|
* Set save path.
|
|
*
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function setSavePath(): void
|
|
{
|
|
$savePath = $this->app->getAppRoot() . "www/data/upload/{$this->app->company->id}/" . date('Ym/', $this->now);
|
|
if(!file_exists($savePath))
|
|
{
|
|
mkdir($savePath, 0777, true);
|
|
touch($savePath . 'index.html');
|
|
}
|
|
$this->savePath = dirname($savePath) . '/';
|
|
}
|
|
|
|
/**
|
|
* Set the web path of upload files.
|
|
*
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function setWebPath(): void
|
|
{
|
|
$this->webPath = $this->app->getWebRoot() . "data/upload/{$this->app->company->id}/";
|
|
}
|
|
|
|
/**
|
|
* Set paths: realPath and webPath.
|
|
*
|
|
* @param object $file
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function setFileWebAndRealPaths(object $file): void
|
|
{
|
|
if($file->objectType == 'traincourse' or $file->objectType == 'traincontents')
|
|
{
|
|
$file->realPath = $this->app->getWwwRoot() . 'data/course/' . $file->pathname;
|
|
$file->webPath = $this->config->webRoot . 'data/course/' . $file->pathname;
|
|
}
|
|
else
|
|
{
|
|
$pathName = $this->getRealPathName($file->pathname);
|
|
$file->realPath = $this->savePath . $pathName;
|
|
$file->webPath = $this->webPath . $pathName;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Insert the set image size code.
|
|
*
|
|
* @param string|null $content
|
|
* @param int $maxSize
|
|
* @access public
|
|
* @return string|null
|
|
*/
|
|
public function setImgSize(string|null $content, int $maxSize = 0): string
|
|
{
|
|
$content = (string)$content;
|
|
if(empty($content)) return $content;
|
|
|
|
$readLinkReg = str_replace(array('%fileID%', '/', '.', '?'), array('[0-9]+', '\/', '\.', '\?'), helper::createLink('file', 'read', 'fileID=(%fileID%)', '\w+'));
|
|
|
|
$content = preg_replace('/ src="(' . $readLinkReg . ')" /', ' onload="setImageSize(this,' . $maxSize . ')" src="$1" ', $content);
|
|
$content = preg_replace('/ src="{([0-9]+)(\.(\w+))?}" /', ' onload="setImageSize(this,' . $maxSize . ')" src="' . helper::createLink('file', 'read', "fileID=$1", "$3") . '" ', $content);
|
|
|
|
return str_replace(' src="data/upload', ' onload="setImageSize(this,' . $maxSize . ')" src="data/upload', $content);
|
|
}
|
|
|
|
/**
|
|
* Check file exists or not.
|
|
*
|
|
* @param object $file
|
|
* @access public
|
|
* @return bool
|
|
*/
|
|
public function fileExists(object $file): bool
|
|
{
|
|
if(empty($file->realPath)) return false;
|
|
return file_exists($file->realPath);
|
|
}
|
|
|
|
/**
|
|
* Unlink file.
|
|
*
|
|
* @param object $file
|
|
* @access public
|
|
* @return bool|null
|
|
*/
|
|
public function unlinkFile(object $file): bool|null
|
|
{
|
|
if(empty($file->realPath)) return false;
|
|
return @unlink($file->realPath);
|
|
}
|
|
|
|
/**
|
|
* Replace a file.
|
|
*
|
|
* @access public
|
|
* @return bool
|
|
*/
|
|
public function replaceFile(int $fileID, string $postName = 'upFile'): bool
|
|
{
|
|
$files = $this->getUpload($postName);
|
|
if(empty($files)) return false;
|
|
|
|
$pathName = $this->dao->select('pathname')->from(TABLE_FILE)->where('id')->eq($fileID)->fetch('pathname');
|
|
if(empty($pathName)) return false;
|
|
|
|
$file = $files[0];
|
|
$realPathName = $this->savePath . $this->getRealPathName($pathName);
|
|
$parentDir = dirname($realPathName);
|
|
if(!is_dir($parentDir) and is_writable(dirname($parentDir))) mkdir($parentDir);
|
|
move_uploaded_file($file['tmpname'], $realPathName);
|
|
|
|
$file['pathname'] = $pathName;
|
|
$file = $this->compressImage($file);
|
|
|
|
$fileInfo = new stdclass();
|
|
$fileInfo->addedBy = $this->app->user->account;
|
|
$fileInfo->addedDate = helper::now();
|
|
$fileInfo->size = $file['size'];
|
|
$this->dao->update(TABLE_FILE)->data($fileInfo)->where('id')->eq($fileID)->exec();
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check file priv.
|
|
*
|
|
* @param object $file
|
|
* @access public
|
|
* @return bool
|
|
*/
|
|
public function checkPriv(object $file): bool
|
|
{
|
|
if(!$file->objectType || !$file->objectID) return true;
|
|
|
|
$objectType = $file->objectType;
|
|
$objectID = $file->objectID;
|
|
$table = $this->config->objectTables[$objectType];
|
|
|
|
if(!$table) return true;
|
|
|
|
$groupName = zget($this->config->file->objectGroup, $objectType, '');
|
|
if(!empty($groupName))
|
|
{
|
|
$groupID = $this->dao->findByID($objectID)->from($table)->fetch($groupName);
|
|
if($objectType == 'testcase' && $groupID == 0) return true;
|
|
return $this->loadModel($groupName)->checkPriv((int)$groupID);
|
|
}
|
|
|
|
if($objectType == 'release')
|
|
{
|
|
$release = $this->dao->select('project,product')->from(TABLE_RELEASE)->where('id')->eq($objectID)->fetch();
|
|
return $this->loadModel('product')->checkPriv($release->product);
|
|
}
|
|
|
|
if($objectType == 'doc')
|
|
{
|
|
$doc = $this->dao->findById($objectID)->from(TABLE_DOC)->fetch();
|
|
return $this->loadModel('doc')->checkPrivDoc($doc);
|
|
}
|
|
|
|
if($objectType == 'feedback')
|
|
{
|
|
$productID = $this->dao->findById($objectID)->from(TABLE_FEEDBACK)->fetch('product');
|
|
$grantProducts = $this->loadModel('feedback')->getGrantProducts();
|
|
return in_array($productID, array_keys($grantProducts));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Compress image to config configured size.
|
|
*
|
|
* @param string $rawImage
|
|
* @param string $target
|
|
* @param int $x
|
|
* @param int $y
|
|
* @param int $width
|
|
* @param int $height
|
|
* @param int $resizeWidth
|
|
* @param int $resizeHeight
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function cropImage($rawImage, $target, $x, $y, $width, $height, $resizeWidth = 0, $resizeHeight = 0)
|
|
{
|
|
$this->app->loadClass('phpthumb', true);
|
|
|
|
if(!extension_loaded('gd')) return false;
|
|
|
|
$croper = phpThumbFactory::create($rawImage);
|
|
if($resizeWidth > 0) $croper->resize($resizeWidth, $resizeHeight);
|
|
$croper->crop($x, $y, $width, $height);
|
|
$croper->save($target);
|
|
}
|
|
|
|
/**
|
|
* Paste image in kindeditor at firefox and chrome.
|
|
*
|
|
* @param string $data
|
|
* @param string $uid
|
|
* @param bool $safe
|
|
* @access public
|
|
* @return string
|
|
*/
|
|
public function pasteImage(string $data, string $uid = '', bool $safe = false): string
|
|
{
|
|
if(empty($data)) return '';
|
|
|
|
$dataLength = strlen($data);
|
|
if(ini_get('pcre.backtrack_limit') < $dataLength) ini_set('pcre.backtrack_limit', $dataLength);
|
|
preg_match_all('/<img src="(data:image\/(\S+);base64,(\S+))".*\/>/U', $data, $out);
|
|
if($out[3])
|
|
{
|
|
foreach($out[3] as $key => $base64Image)
|
|
{
|
|
$base64Image = str_replace('\"', '"', $base64Image);
|
|
$extension = strtolower($out[2][$key]);
|
|
if(!in_array($extension, $this->config->file->imageExtensions)) helper::end();
|
|
$imageData = base64_decode($base64Image);
|
|
|
|
$file['extension'] = $extension;
|
|
$file['pathname'] = $this->setPathName($key, $file['extension']);
|
|
$file['size'] = strlen($imageData);
|
|
$file['addedBy'] = $this->app->user->account;
|
|
$file['addedDate'] = helper::today();
|
|
$file['title'] = str_replace(".$extension", '', basename($file['pathname']));
|
|
|
|
$imagePath = $this->savePath . $this->getSaveName($file['pathname']);
|
|
if(is_writable($imagePath)) file_put_contents($imagePath, $imageData);
|
|
$this->dao->insert(TABLE_FILE)->data($file)->exec();
|
|
$fileID = $this->dao->lastInsertID();
|
|
if($uid) $_SESSION['album'][$uid][] = $fileID;
|
|
|
|
$data = str_replace($out[1][$key], helper::createLink('file', 'read', "fileID=$fileID", $file['extension']), $data);
|
|
}
|
|
}
|
|
elseif($safe)
|
|
{
|
|
$data = fixer::stripDataTags(rawurldecode($data));
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Parse CSV.
|
|
*
|
|
* @param string $csvFile
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function parseCSV(string $csvFile): array
|
|
{
|
|
/* Parse file only in zentao. */
|
|
if(strpos($csvFile, $this->app->getBasePath()) !== 0) return array();
|
|
if(!file_exists($csvFile)) return array();
|
|
|
|
$fh = fopen($csvFile, "r");
|
|
$data = array();
|
|
while(($row = fgetcsv($fh)) !== false) $data[] = $row;
|
|
fclose($fh);
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Process editor.
|
|
*
|
|
* @param object $data
|
|
* @param string|array $editorList
|
|
* @param mixed $uid
|
|
* @access public
|
|
* @return object
|
|
*/
|
|
public function processImgURL(object $data, string|array $editorList, mixed $uid = ''): object
|
|
{
|
|
if(!is_string($uid)) return $data;
|
|
|
|
if(is_string($editorList)) $editorList = explode(',', str_replace(' ', '', $editorList));
|
|
if(empty($editorList)) return $data;
|
|
|
|
$readLinkReg = helper::createLink('file', 'read', 'fileID=(%fileID%)', '(%viewType%)');
|
|
$readLinkReg = str_replace(array('%fileID%', '%viewType%', '?', '/'), array('[0-9]+', '\w+', '\?', '\/'), $readLinkReg);
|
|
$imageIdList = array();
|
|
foreach($editorList as $editorID)
|
|
{
|
|
if(empty($editorID) or empty($data->$editorID)) continue;
|
|
|
|
$imgURL = $this->config->requestType == 'GET' ? '{$2.$1}' : '{$1.$2}';
|
|
|
|
$content = $this->pasteImage($data->$editorID, $uid);
|
|
if($content) $data->$editorID = $content;
|
|
|
|
$data->$editorID = preg_replace("/ src=\"$readLinkReg\" /", ' src="' . $imgURL . '" ', $data->$editorID);
|
|
$data->$editorID = preg_replace("/ src=\"" . htmlSpecialString($readLinkReg) . "\" /", ' src="' . $imgURL . '" ', $data->$editorID);
|
|
|
|
preg_match_all('/ src="{([0-9]+)\.\w+}"/', $data->$editorID, $matches);
|
|
if($matches[1])
|
|
{
|
|
foreach($matches[1] as $imageID) $imageIdList[$imageID] = $imageID;
|
|
}
|
|
}
|
|
|
|
if(!empty($_SESSION['album'][$uid]))
|
|
{
|
|
foreach($_SESSION['album'][$uid] as $imageID)
|
|
{
|
|
if(isset($imageIdList[$imageID])) $_SESSION['album']['used'][$uid][$imageID] = $imageID;
|
|
}
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Compress image
|
|
*
|
|
* @param array $file
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function compressImage(array $file): array
|
|
{
|
|
if(!extension_loaded('gd') or !function_exists('imagecreatefromjpeg')) return $file;
|
|
|
|
$pathName = $file['pathname'];
|
|
$fileName = $this->savePath . $this->getSaveName($pathName);
|
|
$suffix = $file['extension'];
|
|
$lowerSuffix = strtolower($suffix);
|
|
|
|
if(!in_array($lowerSuffix, $this->config->file->image2Compress)) return $file;
|
|
|
|
$quality = 85;
|
|
$newSuffix = '.jpg';
|
|
$compressedName = str_replace($suffix, $newSuffix, $pathName);
|
|
|
|
$res = $lowerSuffix == '.bmp' ? $this->imagecreatefrombmp($fileName) : imagecreatefromjpeg($fileName);
|
|
imagejpeg($res, $fileName, $quality);
|
|
|
|
$file['pathname'] = $compressedName;
|
|
$file['extension'] = ltrim($newSuffix, '.');
|
|
$file['size'] = filesize($fileName);
|
|
return $file;
|
|
}
|
|
|
|
/**
|
|
* Read 24bit BMP files
|
|
* Author: de77
|
|
* Licence: MIT
|
|
* Webpage: de77.com
|
|
* Version: 07.02.2010
|
|
* Source : https://github.com/acustodioo/pic/blob/master/imagecreatefrombmp.function.php
|
|
*
|
|
* @param string $filename
|
|
* @access public
|
|
* @return resource
|
|
*/
|
|
public function imagecreatefrombmp(string $filename): GdImage|false
|
|
{
|
|
$f = fopen($filename, "rb");
|
|
|
|
//read header
|
|
$header = fread($f, 54);
|
|
$header = unpack('c2identifier/Vfile_size/Vreserved/Vbitmap_data/Vheader_size/'.
|
|
'Vwidth/Vheight/vplanes/vbits_per_pixel/Vcompression/Vdata_size/'.
|
|
'Vh_resolution/Vv_resolution/Vcolors/Vimportant_colors', $header);
|
|
|
|
if ($header['identifier1'] != 66 or $header['identifier2'] != 77) return false;
|
|
if ($header['bits_per_pixel'] != 24) return false;
|
|
|
|
$wid2 = ceil((3 * $header['width']) / 4) * 4;
|
|
|
|
$wid = $header['width'];
|
|
$hei = $header['height'];
|
|
|
|
$img = imagecreatetruecolor($header['width'], $header['height']);
|
|
|
|
//read pixels
|
|
for($y = $hei - 1; $y >= 0; $y--)
|
|
{
|
|
$row = fread($f, $wid2);
|
|
$pixels = str_split($row, 3);
|
|
|
|
for ($x = 0; $x < $wid; $x++) imagesetpixel($img, $x, $y, $this->dwordize($pixels[$x]));
|
|
}
|
|
fclose($f);
|
|
return $img;
|
|
}
|
|
|
|
/**
|
|
* Dwordize for imagecreatefrombmp
|
|
*
|
|
* @param string $str
|
|
* @access private
|
|
* @return int
|
|
*/
|
|
private function dwordize(string $str): int
|
|
{
|
|
$a = ord($str[0]);
|
|
$b = ord($str[1]);
|
|
$c = ord($str[2]);
|
|
return $c * 256 * 256 + $b * 256 + $a;
|
|
}
|
|
|
|
/**
|
|
* Update objectID.
|
|
*
|
|
* @param string $uid
|
|
* @param int $objectID
|
|
* @param string $objectType
|
|
* @access public
|
|
* @return bool
|
|
*/
|
|
public function updateObjectID(string|bool $uid, int $objectID, string $objectType): bool
|
|
{
|
|
if(empty($uid)) return true;
|
|
if(empty($_SESSION['album']['used'][$uid])) return true;
|
|
|
|
$data = new stdclass();
|
|
$data->objectID = $objectID;
|
|
$data->objectType = $objectType;
|
|
if(!defined('RUN_MODE') || RUN_MODE != 'api') $data->extra = 'editor';
|
|
|
|
$this->dao->update(TABLE_FILE)->data($data)->where('id')->in($_SESSION['album']['used'][$uid])->exec();
|
|
return !dao::isError();
|
|
}
|
|
|
|
/**
|
|
* Revert real src.
|
|
*
|
|
* @param object $data
|
|
* @param string $fields
|
|
* @access public
|
|
* @return object
|
|
*/
|
|
public function replaceImgURL(object $data, string $fields): object
|
|
{
|
|
$moduleName = $this->app->getModuleName();
|
|
$methodName = $this->app->getMethodName();
|
|
if(is_string($fields)) $fields = explode(',', str_replace(' ', '', $fields));
|
|
|
|
$textareaFields = $this->dao->select('id,field')->from(TABLE_WORKFLOWFIELD)->where('module')->eq($moduleName)->andWhere('control')->eq('richtext')->andWhere('buildin')->eq('0')->fetchPairs();
|
|
if($textareaFields) $fields = array_merge($fields, $textareaFields);
|
|
|
|
foreach($fields as $field)
|
|
{
|
|
if(empty($field) or empty($data->$field)) continue;
|
|
$data->$field = preg_replace('/ src="{([0-9]+)(\.(\w+))?}" /', ' src="' . helper::createLink('file', 'read', "fileID=$1", "$3") . '" ', $data->$field);
|
|
|
|
/* Convert plain text URLs into HTML hyperlinks. */
|
|
if(isset($this->config->file->convertURL['common'][$methodName]) or isset($this->config->file->convertURL[$moduleName][$methodName]))
|
|
{
|
|
$fieldData = $data->$field;
|
|
preg_match_all('/(<a[^>]*>.*<\/a>)/Ui', $fieldData, $aTags);
|
|
preg_match_all('/(<img[^>]*>)/i', $fieldData, $imgTags);
|
|
preg_match_all('/(<iframe[^>]*>[^<]*<\/iframe>)/i', $fieldData, $iframeTags);
|
|
preg_match_all('/(<pre[^>]*>.*<\/pre>)/sUi', $fieldData, $preTags);
|
|
|
|
foreach($aTags[0] as $i => $aTag) $fieldData = str_replace($aTag, "<A_{$i}>", $fieldData);
|
|
foreach($imgTags[0] as $i => $imgTag) $fieldData = str_replace($imgTag, "<IMG_{$i}>", $fieldData);
|
|
foreach($iframeTags[0] as $i => $iframeTag) $fieldData = str_replace($iframeTag, "<IFRAME_{$i}>", $fieldData);
|
|
foreach($preTags[0] as $i => $preTag) $fieldData = str_replace($preTag, "<PRE_{$i}>", $fieldData);
|
|
|
|
$fieldData = preg_replace('/(http:\/\/|https:\/\/)((\w|=|\?|\.|\/|\&|-|%|;)+)/i', "<a href=\"\\0\" target=\"_blank\">\\0</a>", $fieldData);
|
|
|
|
foreach($aTags[0] as $i => $aTag) $fieldData = str_replace("<A_{$i}>", $aTag, $fieldData);
|
|
foreach($imgTags[0] as $i => $imgTag) $fieldData = str_replace("<IMG_{$i}>", $imgTag, $fieldData);
|
|
foreach($iframeTags[0] as $i => $iframeTag) $fieldData = str_replace("<IFRAME_{$i}>", $iframeTag, $fieldData);
|
|
foreach($preTags[0] as $i => $preTag) $fieldData = str_replace("<PRE_{$i}>", $preTag, $fieldData);
|
|
|
|
$data->$field = $fieldData;
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Auto delete useless image.
|
|
*
|
|
* @param string $uid
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function autoDelete(string $uid): void
|
|
{
|
|
if(empty($_SESSION['album'][$uid])) return;
|
|
|
|
foreach($_SESSION['album'][$uid] as $imageID)
|
|
{
|
|
if(isset($_SESSION['album']['used'][$uid][$imageID])) continue;
|
|
|
|
$file = $this->getById($imageID);
|
|
if(empty($file)) continue;
|
|
|
|
$this->dao->delete()->from(TABLE_FILE)->where('id')->eq($imageID)->exec();
|
|
$this->unlinkFile($file);
|
|
}
|
|
unset($_SESSION['album'][$uid]);
|
|
}
|
|
|
|
/**
|
|
* Send the download header to the client.
|
|
*
|
|
* @param string $fileName
|
|
* @param string $fileType
|
|
* @param string $content
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function sendDownHeader(string $fileName, string $fileType, string $content, string $type = 'content'): void
|
|
{
|
|
/* Clean the ob content to make sure no space or utf-8 bom output. */
|
|
$obLevel = ob_get_level();
|
|
for($i = 0; $i < $obLevel; $i++) ob_end_clean();
|
|
|
|
/* Set the downloading cookie, thus the export form page can use it to judge whether to close the window or not. */
|
|
helper::setcookie('downloading', 1, 0, $this->config->webRoot, '', $this->config->cookieSecure, false);
|
|
|
|
/* Append the extension name auto. */
|
|
$extension = $fileType ? ('.' . $fileType) : '';
|
|
if(strpos($fileName, $extension) === false) $fileName .= $extension;
|
|
|
|
/* Judge the content type. */
|
|
$mimes = $this->config->file->mimes;
|
|
$contentType = isset($mimes[$fileType]) ? $mimes[$fileType] : $mimes['default'];
|
|
$fileName = str_replace('+', ' ', urlencode($fileName));
|
|
|
|
helper::header('Content-type', $contentType);
|
|
helper::header('Content-Disposition', "attachment; filename=\"$fileName\"");
|
|
helper::header('Pragma', 'no-cache');
|
|
helper::header('Expires', '0');
|
|
if($type == 'content') helper::end($content);
|
|
if($type == 'file' and file_exists($content))
|
|
{
|
|
if(stripos($content, $this->app->getBasePath()) !== 0) helper::end();
|
|
|
|
set_time_limit(0);
|
|
$chunkSize = 10 * 1024 * 1024;
|
|
$handle = fopen($content, "r");
|
|
while(!feof($handle)) echo fread($handle, $chunkSize);
|
|
fclose($handle);
|
|
helper::end();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get image size.
|
|
*
|
|
* @param object $file
|
|
* @access public
|
|
* @return array|false
|
|
*/
|
|
public function getImageSize(object $file): array|false
|
|
{
|
|
if(empty($file->realPath)) return array();
|
|
if($this->config->file->storageType == 'fs')
|
|
{
|
|
return file_exists($file->realPath) ? getimagesize($file->realPath) : array(0, 0, $file->extension);
|
|
}
|
|
elseif($this->config->file->storageType == 's3')
|
|
{
|
|
$this->app->loadClass('ossclient', true);
|
|
|
|
$config = $this->config->file;
|
|
$ossClient = new ossclient($config->accessKeyId, $config->accessKeySecret, $config->endpoint, $config->bucket);
|
|
$info = $ossClient->getImageInfo($file->pathname);
|
|
|
|
return array($info->ImageWidth->value, $info->ImageHeight->value, 'img');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get file pairs.
|
|
*
|
|
* @param string $idList
|
|
* @param string $value
|
|
* @access public
|
|
* @return array
|
|
*/
|
|
public function getPairs(string $idList, string $value = 'title'): array
|
|
{
|
|
return $this->dao->select("id,$value")->from(TABLE_FILE)->where('id')->in($idList)->fetchPairs();
|
|
}
|
|
|
|
/**
|
|
* Update test case version.
|
|
*
|
|
* @param object $file
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function updateTestcaseVersion(object $file): void
|
|
{
|
|
$oldCase = $this->dao->select('*')->from(TABLE_CASE)->where('fromCaseID')->eq($file->objectID)->fetch();
|
|
if(empty($oldCase)) return;
|
|
|
|
$isLibCase = $oldCase->lib && empty($oldCase->product);
|
|
if($isLibCase)
|
|
{
|
|
$fromcaseVersion = $oldCase->fromCaseVersion + 1;
|
|
$this->dao->update(TABLE_CASE)->set('`fromCaseVersion`')->eq($fromcaseVersion)->where('`fromCaseID`')->eq($file->objectID)->exec();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process file info for object.
|
|
*
|
|
* @param string $objectType
|
|
* @param object $oldObject
|
|
* @param object $newObject
|
|
* @param string $extra
|
|
* @param string $filesName
|
|
* @param string $labelsName
|
|
* @access public
|
|
* @return void
|
|
*/
|
|
public function processFile4Object(string $objectType, object $oldObject, object $newObject, string $extra = '', string $filesName = 'files', string $labelsName = 'labels'): void
|
|
{
|
|
if(empty($oldObject->id)) return;
|
|
|
|
$oldFiles = empty($oldObject->files) ? '' : implode(',', array_keys($oldObject->files));
|
|
$deleteFiles = $newObject->deleteFiles ?? null;
|
|
if(!empty($deleteFiles))
|
|
{
|
|
$this->dao->delete()->from(TABLE_FILE)->where('id')->in($deleteFiles)->exec();
|
|
foreach($deleteFiles as $fileID)
|
|
{
|
|
$this->unlinkFile($oldObject->files[$fileID]);
|
|
$oldFiles = empty($oldFiles) ? '' : trim(str_replace(",$fileID,", ',', ",$oldFiles,"), ',');
|
|
}
|
|
}
|
|
|
|
$this->updateObjectID($this->post->uid, $oldObject->id, $objectType);
|
|
$addedFiles = $this->saveUpload($objectType, $oldObject->id, $extra, $filesName, $labelsName);
|
|
$addedFiles = empty($addedFiles) ? '' : ',' . implode(',', array_keys($addedFiles));
|
|
|
|
$newObject->files = trim($oldFiles . $addedFiles, ',');
|
|
$oldObject->files = $oldFiles;
|
|
}
|
|
|
|
/**
|
|
* Print file in view/edit page.
|
|
*
|
|
* @param object $file
|
|
* @param string $method
|
|
* @param bool $showDelete
|
|
* @param bool $showEdit
|
|
* @param object|null $object
|
|
* @access public
|
|
* @return string
|
|
*/
|
|
public function printFile(object $file, string $method, bool $showDelete, bool $showEdit, object|null $object): string
|
|
{
|
|
if(!common::hasPriv('file', 'download')) return '';
|
|
|
|
$html = '';
|
|
|
|
$sessionString = session_name() . '=' . session_id();
|
|
$uploadDate = $this->lang->file->uploadDate . substr($file->addedDate, 0, 10);
|
|
$fileTitle = "<i class='icon icon-file-text'></i> " . $file->title;
|
|
$fileSize = helper::formatKB((float)$file->size);
|
|
if(strpos($file->title, ".{$file->extension}") === false && $file->extension != 'txt') $fileTitle .= ".{$file->extension}";
|
|
$imageWidth = 0;
|
|
if(stripos('jpg|jpeg|gif|png|bmp', $file->extension) !== false)
|
|
{
|
|
$imageSize = $this->getImageSize($file);
|
|
$imageWidth = $imageSize[0] ?? 0;
|
|
}
|
|
|
|
$downloadLink = helper::createLink('file', 'download', "fileID=$file->id");
|
|
$downloadLink .= strpos($downloadLink, '?') === false ? '?' : '&';
|
|
$downloadLink .= $sessionString;
|
|
|
|
$objectType = zget($this->config->file->objectType, $file->objectType);
|
|
|
|
$html .= "<li class='mb-2 file' title='{$uploadDate}'>" . html::a($downloadLink, $fileTitle . " <span class='text-gray'>({$fileSize})</span>", '_blank', "id='fileTitle$file->id'");
|
|
if(strpos('view,edit', $method) !== false)
|
|
{
|
|
if(common::hasPriv($objectType, 'view', $object)) $html = $this->buildFileActions($html, $downloadLink, $imageWidth, $showEdit, $showDelete, $file, $object);
|
|
}
|
|
else
|
|
{
|
|
if(common::hasPriv($objectType, 'edit', $object))
|
|
{
|
|
$html .= "<span class='right-icon'> ";
|
|
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}'");
|
|
if(common::hasPriv('file', 'delete')) $html .= html::a('###', $this->lang->delete, '', "class='fileAction btn btn-link' onclick='deleteFile($file->id)' title='{$this->lang->delete}'");
|
|
$html .= '</span>';
|
|
}
|
|
}
|
|
|
|
$html .= '</li>';
|
|
|
|
return $html;
|
|
}
|
|
|
|
/**
|
|
* Build file actions.
|
|
*
|
|
* @param string $html
|
|
* @param string $downloadLink
|
|
* @param bool $showEdit
|
|
* @param bool $showEdit
|
|
* @param bool $showDelete
|
|
* @param object $file
|
|
* @param object|null $object
|
|
* @access public
|
|
* @return string
|
|
*/
|
|
public function buildFileActions(string $html, string $downloadLink, int $imageWidth, bool $showEdit, bool $showDelete, object $file, object|null $object): string
|
|
{
|
|
$objectType = zget($this->config->file->objectType, $file->objectType);
|
|
$html .= "<span class='right-icon hidden'> ";
|
|
|
|
/* Determines whether the file supports preview. */
|
|
if($file->extension == 'txt')
|
|
{
|
|
$extension = 'txt';
|
|
if(($position = strrpos($file->title, '.')) !== false) $extension = substr($file->title, $position + 1);
|
|
$file->extension = $extension;
|
|
}
|
|
|
|
/* For the open source version of the file judgment. */
|
|
$canPreview = false;
|
|
if(stripos('txt|jpg|jpeg|gif|png|bmp|mp4', $file->extension) !== false) $canPreview = true;
|
|
if(isset($this->config->file->libreOfficeTurnon) and $this->config->file->libreOfficeTurnon == 1)
|
|
{
|
|
$officeTypes = 'doc|docx|xls|xlsx|ppt|pptx|pdf';
|
|
if(stripos($officeTypes, $file->extension) !== false) $canPreview = true;
|
|
}
|
|
|
|
if($canPreview) $html .= html::a($downloadLink, "<i class='icon icon-eye'></i>", '_blank', "class='fileAction btn btn-link text-primary' title='{$this->lang->file->preview}' onclick=\"return downloadFile($file->id, '$file->extension', $imageWidth, '$file->title')\"");
|
|
if(common::hasPriv('file', 'download')) $html .= html::a(helper::createLink('file', 'download', "fileID=$file->id"), "<i class='icon icon-download'></i>", '_blank', "class='fileAction btn btn-link text-primary' title='{$this->lang->file->downloadFile}'");
|
|
if(common::hasPriv($objectType, 'edit', $object))
|
|
{
|
|
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}'");
|
|
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}'");
|
|
}
|
|
|
|
$html .= '</span>';
|
|
$html .= '</li>';
|
|
|
|
if(strrpos($file->title, '.') !== false)
|
|
{
|
|
/* Fix the file name exe.exe */
|
|
$title = explode('.', $file->title);
|
|
$extension = end($title);
|
|
if($file->extension == 'txt' && $extension != $file->extension) $file->extension = $extension;
|
|
array_pop($title);
|
|
$file->title = join('.', $title);
|
|
}
|
|
|
|
$html .= "<li class='file hidden'><div>";
|
|
$html .= "<div class='renameFile w-300px' id='renameBox{$file->id}'><i class='icon icon-file-text'></i>";
|
|
$html .= "<div class='input-group'>";
|
|
$html .= "<input type='text' id='fileName{$file->id}' value='{$file->title}' class='form-control'/>";
|
|
$html .= "<input type='hidden' id='extension{$file->id}' value='{$file->extension}'/>";
|
|
$html .= "<strong class='input-group-addon'>.{$file->extension}</strong></div>";
|
|
$html .= "<div class='input-group-btn'>";
|
|
$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>";
|
|
$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>";
|
|
$html .= '</div></div></div></li>';
|
|
|
|
return $html;
|
|
}
|
|
|
|
/**
|
|
* Get last modified timestamp of file.
|
|
*
|
|
* @param object $file
|
|
* @access public
|
|
* @return int|false
|
|
*/
|
|
public function fileMTime(object $file): int|false
|
|
{
|
|
if(empty($file->realPath) or !file_exists($file->realPath)) return false;
|
|
return filemtime($file->realPath);
|
|
}
|
|
|
|
/**
|
|
* Get file size.
|
|
*
|
|
* @param object $file
|
|
* @access public
|
|
* @return int
|
|
*/
|
|
public function fileSize(object $file): int|false
|
|
{
|
|
if(empty($file->realPath) or !file_exists($file->realPath)) return false;
|
|
return filesize($file->realPath);
|
|
}
|
|
|
|
/**
|
|
* Save file to local storage temporarily.
|
|
*
|
|
* @param object $file
|
|
* @access public
|
|
* @return string
|
|
*/
|
|
public function saveAsTempFile(object $file): string
|
|
{
|
|
if(empty($file->realPath)) return '';
|
|
|
|
/* If the storage type is local, do nothing. */
|
|
if($this->config->file->storageType == 'fs') return $file->realPath;
|
|
return '';
|
|
}
|
|
}
|