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

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