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

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