Просмотр файла app/Controllers/Load/DownController.php

Размер файла: 19.9Kb
  1. <?php
  2.  
  3. declare(strict_types=1);
  4.  
  5. namespace App\Controllers\Load;
  6.  
  7. use App\Models\File;
  8. use App\Models\Load;
  9. use App\Models\User;
  10. use Exception;
  11. use App\Classes\Validator;
  12. use App\Controllers\BaseController;
  13. use App\Models\Comment;
  14. use App\Models\Down;
  15. use App\Models\Flood;
  16. use App\Models\Reader;
  17. use Illuminate\Database\Query\JoinClause;
  18. use Illuminate\Http\Request;
  19. use PhpZip\ZipFile;
  20.  
  21. class DownController extends BaseController
  22. {
  23. /**
  24. * Просмотр загрузки
  25. *
  26. * @param int $id
  27. *
  28. * @return string
  29. */
  30. public function index(int $id): string
  31. {
  32. $down = Down::query()
  33. ->select('downs.*', 'pollings.vote')
  34. ->where('downs.id', $id)
  35. ->leftJoin('pollings', static function (JoinClause $join) {
  36. $join->on('downs.id', 'pollings.relate_id')
  37. ->where('pollings.relate_type', Down::$morphName)
  38. ->where('pollings.user_id', getUser('id'));
  39. })
  40. ->with('category.parent')
  41. ->first();
  42.  
  43. if (! $down) {
  44. abort(404, __('loads.down_not_exist'));
  45. }
  46.  
  47. if (! isAdmin(User::ADMIN) && (! $down->active && getUser() && getUser('id') !== $down->user_id)) {
  48. abort('default', __('loads.down_not_verified'));
  49. }
  50.  
  51. $allowDownload = getUser() || setting('down_guest_download');
  52.  
  53. return view('loads/down', compact('down', 'allowDownload'));
  54. }
  55.  
  56. /**
  57. * Редактирование загрузки
  58. *
  59. * @param int $id
  60. * @param Request $request
  61. * @param Validator $validator
  62. *
  63. * @return string
  64. */
  65. public function edit(int $id, Request $request, Validator $validator): string
  66. {
  67. /** @var Down $down */
  68. $down = Down::query()->where('user_id', getUser('id'))->find($id);
  69.  
  70. if (! $down) {
  71. abort(404, __('loads.down_not_exist'));
  72. }
  73.  
  74. if ($down->active) {
  75. abort('default', __('loads.down_verified'));
  76. }
  77.  
  78. if ($request->isMethod('post')) {
  79. $title = $request->input('title');
  80. $text = $request->input('text');
  81. $files = (array) $request->file('files');
  82.  
  83. $validator->equal($request->input('token'), $_SESSION['token'], __('validator.token'))
  84. ->length($title, 3, 50, ['title' => __('validator.text')])
  85. ->length($text, 50, 5000, ['text' => __('validator.text')]);
  86.  
  87. $duplicate = Down::query()->where('title', $title)->where('id', '<>', $down->id)->count();
  88. $validator->empty($duplicate, ['title' => __('loads.down_name_exists')]);
  89.  
  90. $existFiles = $down->files ? $down->files->count() : 0;
  91. $validator->notEmpty(count($files) + $existFiles, ['files' => __('validator.file_upload_one')]);
  92. $validator->lte(count($files) + $existFiles, setting('maxfiles'), ['files' => __('validator.files_max', ['max' => setting('maxfiles')])]);
  93.  
  94. if ($validator->isValid()) {
  95. $rules = [
  96. 'maxsize' => setting('fileupload'),
  97. 'extensions' => explode(',', setting('allowextload')),
  98. 'minweight' => 100,
  99. ];
  100.  
  101. foreach ($files as $file) {
  102. $validator->file($file, $rules, ['files' => __('validator.file_upload_failed')]);
  103. }
  104. }
  105.  
  106. if ($validator->isValid()) {
  107. $down->update([
  108. 'title' => $title,
  109. 'text' => $text,
  110. ]);
  111.  
  112. foreach ($files as $file) {
  113. $down->uploadAndConvertFile($file);
  114. }
  115.  
  116. clearCache(['statLoads', 'recentDowns']);
  117. setFlash('success', __('loads.down_edited_success'));
  118. redirect('/downs/' . $down->id);
  119. } else {
  120. setInput($request->all());
  121. setFlash('danger', $validator->getErrors());
  122. }
  123. }
  124.  
  125. return view('loads/edit', compact('down'));
  126. }
  127.  
  128. /**
  129. * Удаление файла
  130. *
  131. * @param int $id
  132. * @param int $fid
  133. *
  134. * @throws Exception
  135. */
  136. public function deleteFile(int $id, int $fid): void
  137. {
  138. /** @var Down $down */
  139. $down = Down::query()->where('user_id', getUser('id'))->find($id);
  140.  
  141. if (! $down) {
  142. abort(404, __('loads.down_not_exist'));
  143. }
  144.  
  145. /** @var File $file */
  146. $file = $down->files()->find($fid);
  147.  
  148. if (! $file) {
  149. abort(404, __('loads.down_not_exist'));
  150. }
  151.  
  152. deleteFile(HOME . $file->hash);
  153.  
  154. setFlash('success', __('loads.file_deleted_success'));
  155. $file->delete();
  156.  
  157. redirect('/downs/edit/' . $down->id);
  158. }
  159.  
  160. /**
  161. * Создание загрузки
  162. *
  163. * @param Request $request
  164. * @param Validator $validator
  165. * @param Flood $flood
  166. *
  167. * @return string
  168. */
  169. public function create(Request $request, Validator $validator, Flood $flood): string
  170. {
  171. $cid = int($request->input('cid'));
  172.  
  173. if (! isAdmin() && ! setting('downupload')) {
  174. abort('default', __('loads.down_closed'));
  175. }
  176.  
  177. if (! $user = getUser()) {
  178. abort(403, __('main.not_authorized'));
  179. }
  180.  
  181. $loads = Load::query()
  182. ->where('parent_id', 0)
  183. ->with('children')
  184. ->orderBy('sort')
  185. ->get();
  186.  
  187. if ($loads->isEmpty()) {
  188. abort('default', __('loads.empty_loads'));
  189. }
  190.  
  191. if ($request->isMethod('post')) {
  192. $title = $request->input('title');
  193. $text = $request->input('text');
  194. $files = (array) $request->file('files');
  195.  
  196. /** @var Load $category */
  197. $category = Load::query()->find($cid);
  198.  
  199. $validator
  200. ->equal($request->input('token'), $_SESSION['token'], __('validator.token'))
  201. ->length($title, 3, 50, ['title' => __('validator.text')])
  202. ->length($text, 50, 5000, ['text' => __('validator.text')])
  203. ->false($flood->isFlood(), ['msg' => __('validator.flood', ['sec' => $flood->getPeriod()])])
  204. ->notEmpty($category, ['category' => __('loads.load_not_exist')]);
  205.  
  206. if ($category) {
  207. $validator->empty($category->closed, ['category' => __('loads.load_closed')]);
  208.  
  209. $duplicate = Down::query()->where('title', $title)->count();
  210. $validator->empty($duplicate, ['title' => __('loads.down_name_exists')]);
  211. }
  212.  
  213. $validator->notEmpty($files, ['files' => __('validator.file_upload_one')]);
  214. $validator->lte(count($files), setting('maxfiles'), ['files' => __('validator.files_max', ['max' => setting('maxfiles')])]);
  215.  
  216. if ($validator->isValid()) {
  217. $rules = [
  218. 'maxsize' => setting('fileupload'),
  219. 'extensions' => explode(',', setting('allowextload')),
  220. 'minweight' => 100,
  221. ];
  222.  
  223. foreach ($files as $file) {
  224. $validator->file($file, $rules, ['files' => __('validator.file_upload_failed')]);
  225. }
  226. }
  227.  
  228. if ($validator->isValid()) {
  229. /** @var Down $down */
  230. $down = Down::query()->create([
  231. 'category_id' => $category->id,
  232. 'title' => $title,
  233. 'text' => $text,
  234. 'user_id' => $user->id,
  235. 'created_at' => SITETIME,
  236. 'active' => isAdmin(User::ADMIN),
  237. ]);
  238.  
  239. foreach ($files as $file) {
  240. $down->uploadAndConvertFile($file);
  241. }
  242.  
  243. if (isAdmin(User::ADMIN)) {
  244. $down->category->increment('count_downs');
  245. clearCache(['statLoads', 'recentDowns']);
  246. } else {
  247. $admins = User::query()->whereIn('level', [User::BOSS, User::ADMIN])->get();
  248.  
  249. if ($admins->isNotEmpty()) {
  250. $text = textNotice('down_upload', ['url' => '/admin/downs/edit/' . $down->id, 'title' => $down->title]);
  251.  
  252. foreach ($admins as $admin) {
  253. $admin->sendMessage($user, $text);
  254. }
  255. }
  256. }
  257.  
  258. $flood->saveState();
  259.  
  260. setFlash('success', __('loads.file_uploaded_success'));
  261. redirect('/downs/' . $down->id);
  262. } else {
  263. setInput($request->all());
  264. setFlash('danger', $validator->getErrors());
  265. }
  266. }
  267.  
  268. return view('loads/create', compact('loads', 'cid'));
  269. }
  270.  
  271. /**
  272. * Голосование
  273. *
  274. * @param int $id
  275. * @param Request $request
  276. * @param Validator $validator
  277. *
  278. * @return void
  279. */
  280. public function vote(int $id, Request $request, Validator $validator): void
  281. {
  282. $score = int($request->input('score'));
  283.  
  284. /** @var Down $down */
  285. $down = Down::query()->find($id);
  286.  
  287. if (! $down) {
  288. abort(404, __('loads.down_not_exist'));
  289. }
  290.  
  291. $validator
  292. ->equal($request->input('token'), $_SESSION['token'], ['score' => __('validator.token')])
  293. ->true(getUser(), ['score' => __('main.not_authorized')])
  294. ->between($score, 1, 5, ['score' => __('loads.down_voted_required')])
  295. ->notEmpty($down->active, ['score' => __('loads.down_not_verified')])
  296. ->notEqual($down->user_id, getUser('id'), ['score' => __('loads.down_voted_forbidden')]);
  297.  
  298. if ($validator->isValid()) {
  299. $polling = $down->polling()->first();
  300. if ($polling) {
  301. $down->increment('rating', $score - $polling->vote);
  302.  
  303. $polling->update([
  304. 'vote' => $score,
  305. 'created_at' => SITETIME
  306. ]);
  307. } else {
  308. $down->polling()->create([
  309. 'user_id' => getUser('id'),
  310. 'vote' => $score,
  311. 'created_at' => SITETIME,
  312. ]);
  313.  
  314. $down->increment('rating', $score);
  315. $down->increment('rated');
  316. }
  317.  
  318. setFlash('success', __('loads.down_voted_success'));
  319. } else {
  320. setFlash('danger', $validator->getErrors());
  321. }
  322.  
  323. redirect('/downs/' . $down->id);
  324. }
  325.  
  326. /**
  327. * Скачивание файла
  328. *
  329. * @param int $id
  330. * @param Validator $validator
  331. *
  332. * @return void
  333. */
  334. public function download(int $id, Validator $validator): void
  335. {
  336. /** @var File $file */
  337. $file = File::query()->where('relate_type', Down::$morphName)->find($id);
  338.  
  339. if (! $file || ! $file->relate) {
  340. abort(404, __('loads.down_not_exist'));
  341. }
  342.  
  343. if (! $file->relate->active && ! isAdmin(User::ADMIN)) {
  344. abort('default', __('loads.down_not_verified'));
  345. }
  346.  
  347. $validator->true(file_exists(HOME . $file->hash), __('loads.down_not_exist'));
  348.  
  349. if ($validator->isValid()) {
  350. Reader::countingStat($file->relate);
  351. $file->download();
  352. } else {
  353. setFlash('danger', $validator->getErrors());
  354. redirect('/downs/' . $file->relate->id);
  355. }
  356. }
  357.  
  358. /**
  359. * Комментарии
  360. *
  361. * @param int $id
  362. * @param Request $request
  363. * @param Validator $validator
  364. * @param Flood $flood
  365. *
  366. * @return string
  367. */
  368. public function comments(int $id, Request $request, Validator $validator, Flood $flood): string
  369. {
  370. /** @var Down $down */
  371. $down = Down::query()->find($id);
  372.  
  373. if (! $down) {
  374. abort(404, __('loads.down_not_exist'));
  375. }
  376.  
  377. if (! $down->active) {
  378. abort('default', __('loads.down_not_verified'));
  379. }
  380.  
  381. if ($request->isMethod('post')) {
  382. $msg = $request->input('msg');
  383.  
  384. $validator
  385. ->true(getUser(), __('main.not_authorized'))
  386. ->equal($request->input('token'), $_SESSION['token'], __('validator.token'))
  387. ->length($msg, 5, setting('comment_length'), ['msg' => __('validator.text')])
  388. ->false($flood->isFlood(), ['msg' => __('validator.flood', ['sec' => $flood->getPeriod()])]);
  389.  
  390. if ($validator->isValid()) {
  391. /** @var Comment $comment */
  392. $comment = $down->comments()->create([
  393. 'text' => antimat($msg),
  394. 'user_id' => getUser('id'),
  395. 'created_at' => SITETIME,
  396. 'ip' => getIp(),
  397. 'brow' => getBrowser(),
  398. ]);
  399.  
  400. $user = getUser();
  401. $user->increment('allcomments');
  402. $user->increment('point');
  403. $user->increment('money', 5);
  404.  
  405. $down->increment('count_comments');
  406.  
  407. $flood->saveState();
  408. sendNotify($msg, '/downs/comment/' . $down->id . '/' . $comment->id, $down->title);
  409.  
  410. setFlash('success', __('main.comment_added_success'));
  411. redirect('/downs/end/' . $down->id);
  412. } else {
  413. setInput($request->all());
  414. setFlash('danger', $validator->getErrors());
  415. }
  416. }
  417.  
  418. $comments = $down->comments()
  419. ->orderBy('created_at')
  420. ->with('user')
  421. ->paginate(setting('comments_per_page'));
  422.  
  423. return view('loads/comments', compact('down', 'comments'));
  424. }
  425.  
  426. /**
  427. * Подготовка к редактированию комментария
  428. *
  429. * @param int $id
  430. * @param int $cid
  431. * @param Request $request
  432. * @param Validator $validator
  433. *
  434. * @return string
  435. */
  436. public function editComment(int $id, int $cid, Request $request, Validator $validator): string
  437. {
  438. $down = Down::query()->find($id);
  439.  
  440. if (! $down) {
  441. abort(404, __('loads.down_not_exist'));
  442. }
  443.  
  444. $page = int($request->input('page', 1));
  445.  
  446. if (! getUser()) {
  447. abort(403, __('main.not_authorized'));
  448. }
  449.  
  450. $comment = $down->comments()
  451. ->where('id', $cid)
  452. ->where('user_id', getUser('id'))
  453. ->first();
  454.  
  455. if (! $comment) {
  456. abort('default', __('main.comment_deleted'));
  457. }
  458.  
  459. if ($comment->created_at + 600 < SITETIME) {
  460. abort('default', __('main.editing_impossible'));
  461. }
  462.  
  463. if ($request->isMethod('post')) {
  464. $msg = $request->input('msg');
  465. $page = int($request->input('page', 1));
  466.  
  467. $validator
  468. ->equal($request->input('token'), $_SESSION['token'], __('validator.token'))
  469. ->length($msg, 5, setting('comment_length'), ['msg' => __('validator.text')]);
  470.  
  471. if ($validator->isValid()) {
  472. $msg = antimat($msg);
  473.  
  474. $comment->update([
  475. 'text' => $msg,
  476. ]);
  477.  
  478. setFlash('success', __('main.comment_edited_success'));
  479. redirect('/downs/comments/' . $id . '?page=' . $page);
  480. } else {
  481. setInput($request->all());
  482. setFlash('danger', $validator->getErrors());
  483. }
  484. }
  485.  
  486. return view('loads/editcomment', compact('down', 'comment', 'page'));
  487. }
  488.  
  489. /**
  490. * Переадресация на последнюю страницу
  491. *
  492. * @param int $id
  493. *
  494. * @return void
  495. */
  496. public function end(int $id): void
  497. {
  498. /** @var Down $down */
  499. $down = Down::query()->find($id);
  500.  
  501. if (! $down) {
  502. abort(404, __('loads.down_not_exist'));
  503. }
  504.  
  505. $total = $down->comments()->count();
  506.  
  507. $end = ceil($total / setting('comments_per_page'));
  508. redirect('/downs/comments/' . $down->id . '?page=' . $end);
  509. }
  510.  
  511. /**
  512. * Просмотр zip архива
  513. *
  514. * @param int $id
  515. *
  516. * @return string
  517. */
  518. public function zip(int $id): string
  519. {
  520. /** @var File $file */
  521. $file = File::query()->where('relate_type', Down::$morphName)->find($id);
  522.  
  523. if (! $file || ! $file->relate) {
  524. abort(404, __('loads.down_not_exist'));
  525. }
  526.  
  527. if (! $file->relate->active && ! isAdmin(User::ADMIN)) {
  528. abort('default', __('loads.down_not_verified'));
  529. }
  530.  
  531. if ($file->extension !== 'zip') {
  532. abort('default', __('loads.archive_only_zip'));
  533. }
  534.  
  535. try {
  536. $archive = new ZipFile();
  537. $archive->openFile(HOME . $file->hash);
  538.  
  539. $down = $file->relate;
  540. $getDocuments = array_values($archive->getAllInfo());
  541. $viewExt = Down::getViewExt();
  542.  
  543. $documents = paginate($getDocuments, setting('ziplist'));
  544. } catch (Exception $e) {
  545. abort('default', __('loads.archive_not_open'));
  546. }
  547.  
  548. return view('loads/zip', compact('down', 'file', 'documents', 'viewExt'));
  549. }
  550.  
  551. /**
  552. * Просмотр файла в zip архиве
  553. *
  554. * @param int $id
  555. * @param int $fid
  556. *
  557. * @return string
  558. */
  559. public function zipView(int $id, int $fid): string
  560. {
  561. /** @var File $file */
  562. $file = File::query()->where('relate_type', Down::$morphName)->find($id);
  563.  
  564. if (! $file || ! $file->relate) {
  565. abort(404, __('loads.down_not_exist'));
  566. }
  567.  
  568. if (! $file->relate->active && ! isAdmin(User::ADMIN)) {
  569. abort('default', __('loads.down_not_verified'));
  570. }
  571.  
  572. if ($file->extension !== 'zip') {
  573. abort('default', __('loads.archive_only_zip'));
  574. }
  575.  
  576. try {
  577. $archive = new ZipFile();
  578. $archive->openFile(HOME . $file->hash);
  579. $getDocuments = array_values($archive->getAllInfo());
  580. $document = $getDocuments[$fid] ?? null;
  581.  
  582. $content = $archive[$document->getName()];
  583.  
  584. if ($document->getSize() > 0 && preg_match("/\.(gif|png|bmp|jpg|jpeg)$/", $document->getName())) {
  585. $ext = getExtension($document->getName());
  586.  
  587. header('Content-type: image/' . $ext);
  588. header('Content-Length: ' . strlen($content));
  589. header('Content-Disposition: inline; filename="' . $document->getName() . '";');
  590. exit($content);
  591. }
  592.  
  593. if (! isUtf($content)) {
  594. $content = winToUtf($content);
  595. }
  596.  
  597. $down = $file->relate;
  598. } catch (Exception $e) {
  599. abort('default', __('loads.file_not_read'));
  600. }
  601.  
  602. return view('loads/zip_view', compact('down', 'file', 'document', 'content'));
  603. }
  604.  
  605. /**
  606. * RSS комментариев
  607. *
  608. * @param int $id
  609. *
  610. * @return string
  611. */
  612. public function rss(int $id): string
  613. {
  614. $down = Down::query()->where('id', $id)->with('lastComments')->first();
  615.  
  616. if (! $down) {
  617. abort(404, __('loads.down_not_exist'));
  618. }
  619.  
  620. return view('loads/rss_comments', compact('down'));
  621. }
  622.  
  623. /**
  624. * Переход к сообщению
  625. *
  626. * @param int $id
  627. * @param int $cid
  628. *
  629. * @return void
  630. */
  631. public function viewComment(int $id, int $cid): void
  632. {
  633. /** @var Down $down */
  634. $down = Down::query()->find($id);
  635.  
  636. if (! $down) {
  637. abort(404, __('loads.down_not_exist'));
  638. }
  639.  
  640. $total = $down->comments()
  641. ->where('id', '<=', $cid)
  642. ->orderBy('created_at')
  643. ->count();
  644.  
  645. $end = ceil($total / setting('comments_per_page'));
  646. redirect('/downs/comments/' . $down->id . '?page=' . $end . '#comment_' . $cid);
  647. }
  648. }