Ejemplo n.º 1
0
 /**
  * Returns rules specifically for dropzone files for a board.
  *
  * @param  Board  $board
  * @param  array  $rules (POINTER, MODIFIED)
  * @return array  Validation rules.
  */
 public static function rulesForFileHashes(Board $board, array &$rules)
 {
     global $app;
     $attachmentsMax = $board->getConfig('postAttachmentsMax', 1);
     $rules['files'][] = "array";
     $rules['files'][] = "min:1";
     $rules['files'][] = "max:{$attachmentsMax}";
     // Create an additional rule for each possible file.
     for ($attachment = 0; $attachment < $attachmentsMax; ++$attachment) {
         $rules["files.name.{$attachment}"] = ["string", "required_with:files.hash.{$attachment}", "between:1,254", "file_name"];
         $rules["files.hash.{$attachment}"] = ["string", "required_with:files.name.{$attachment}", "md5", "exists:files,hash,banned,0"];
         $rules["files.spoiler.{$attachment}"] = ["boolean"];
     }
 }
Ejemplo n.º 2
0
 /**
  * Manage threads that have expired.
  *
  * @param  App\Board  $board
  * @param  Carbon\Carbon  $time  Optional Carbon that will be the xed_at timestamps. Defaults to now.
  * @return void
  */
 protected function handleThreadEphemeral(Board $board, Carbon $time = null)
 {
     if (is_null($time)) {
         $time = Carbon::now();
     }
     // Get important settings.
     $threadsPerPage = (int) $board->getConfig('postsPerPage', 10);
     // Collect a list of threads which have been modified.
     $threadsToSave = [];
     // There are two groups of autoprune settings.
     // x on day since last reply
     $sageOnDay = (int) $board->getConfig('epheSageThreadDays', false);
     $lockOnDay = (int) $board->getConfig('epheLockThreadDays', false);
     $deleteOnDay = (int) $board->getConfig('epheDeleteThreadDays', false);
     // x on page (meaning the thread has fallen to this page)
     $sageOnPage = (int) $board->getConfig('epheSageThreadPage', false);
     $lockOnPage = (int) $board->getConfig('epheLockThreadPage', false);
     $deleteOnPage = (int) $board->getConfig('epheDeleteThreadPage', false);
     // Don't do anything unless we have to.
     if ($sageOnDay || $lockOnDay || $deleteOnDay || $sageOnPage || $lockOnPage || $deleteOnPage) {
         $this->comment("       Pruning /{$board->board_uri}/...");
         // Modify threads based on these settings.
         foreach ($board->threads as $threadIndex => $thread) {
             $threadPage = (int) (floor($threadIndex / $threadsPerPage) + 1);
             $modified = false;
             $replyLast = clone $thread->reply_last;
             // x on day since last reply
             // This is asking if:
             // 1) The setting is set ($x > 0)
             // 2) the last reply date + the number of days permitted by each setting is < now.
             if (!$thread->isBumplocked() && ($sageOnDay > 0 && $replyLast->addDays($sageOnDay)->isPast() || $sageOnPage > 0 && $sageOnPage <= $threadPage)) {
                 $this->comment("           Bumplocking #{$thread->board_id}");
                 $modified = true;
                 $thread->bumplocked_at = $time;
             }
             if (!$thread->isLocked() && ($lockOnDay > 0 && $replyLast->addDays($lockOnDay)->isPast() || $lockOnPage > 0 && $lockOnPage <= $threadPage)) {
                 $this->comment("           Locking #{$thread->board_id}");
                 $modified = true;
                 $thread->locked_at = $time;
             }
             if (!$thread->isDeleted() && ($deleteOnDay > 0 && $replyLast->addDays($sageOnDay)->isPast() || $deleteOnPage > 0 && $deleteOnPage <= $threadPage)) {
                 $this->comment("           Deleting #{$thread->board_id}");
                 $modified = true;
                 $thread->deleted_at = $time;
             }
             if ($modified) {
                 $threadsToSave[] = $thread;
             }
         }
         if (count($threadsToSave)) {
             // Save all at once.
             $board->threads()->saveMany($threadsToSave);
         } else {
             $this->comment("           Nothing to do.");
         }
     }
 }
Ejemplo n.º 3
0
 /**
  * Renders the post edit form.
  */
 public function getReport(Request $request, Board $board, Post $post, $global = false)
 {
     if (!$post->exists) {
         abort(404);
     }
     $actions = ["report"];
     $ContentFormatter = new ContentFormatter();
     $reportText = "";
     if ($global === "global") {
         if (!$post->canReportGlobally($this->user)) {
             abort(403);
         }
         $actions[] = "global";
         $reportText = $ContentFormatter->formatReportText($this->option('globalReportText'));
     } else {
         if (!$post->canReport($this->user)) {
             abort(403);
         }
         $reportText = $ContentFormatter->formatReportText($board->getConfig('boardReportText'));
     }
     if (!isset($report)) {
         $report = Report::where('post_id', '=', $post->post_id)->where('global', $global === "global")->where('board_uri', $board->board_uri)->whereByIPOrUser($this->user)->first();
     }
     return $this->view(static::VIEW_MOD, ['actions' => $actions, 'form' => "report", 'board' => $board, 'post' => $post, 'report' => $report ?: false, 'reportText' => $reportText, 'reportGlobal' => $global === "global"]);
 }
Ejemplo n.º 4
0
 /**
  * Pushes the post to the specified board, as a new thread or as a reply.
  * This autoatically handles concurrency issues. Creating a new reply without
  * using this method is forbidden by the `creating` event in ::boot.
  *
  *
  * @param  App\Board  &$board
  * @param  App\Post   &$thread
  * @return void
  */
 public function submitTo(Board &$board, &$thread = null)
 {
     $this->board_uri = $board->board_uri;
     $this->author_ip = new IP();
     $this->author_country = $board->getConfig('postsAuthorCountry', false) ? new Geolocation() : null;
     $this->reply_last = $this->freshTimestamp();
     $this->bumped_last = $this->reply_last;
     $this->setCreatedAt($this->reply_last);
     $this->setUpdatedAt($this->reply_last);
     if (!is_null($thread) && !$thread instanceof Post) {
         $thread = $board->getLocalThread($thread);
     }
     if ($thread instanceof Post) {
         $this->reply_to = $thread->post_id;
         $this->reply_to_board_id = $thread->board_id;
     }
     // Handle tripcode, if any.
     if (preg_match('/^([^#]+)?(##|#)(.+)$/', $this->author, $match)) {
         // Remove password from name.
         $this->author = $match[1];
         // Whether a secure tripcode was requested, currently unused.
         $secure_tripcode_requested = $match[2] == '##';
         // Convert password to tripcode, store tripcode hash in DB.
         $this->insecure_tripcode = ContentFormatter::formatInsecureTripcode($match[3]);
     }
     // Ensure we're using a valid flag.
     if (!$this->flag_id || !$board->hasFlag($this->flag_id)) {
         $this->flag_id = null;
     }
     // Store the post in the database.
     DB::transaction(function () use($board, $thread) {
         // The objective of this transaction is to prevent concurrency issues in the database
         // on the unique joint index [`board_uri`,`board_id`] which is generated procedurally
         // alongside the primary autoincrement column `post_id`.
         // First instruction is to add +1 to posts_total and set the last_post_at on the Board table.
         DB::table('boards')->where('board_uri', $this->board_uri)->increment('posts_total', 1, ['last_post_at' => $this->reply_last]);
         // Second, we record this value and lock the table.
         $boards = DB::table('boards')->where('board_uri', $this->board_uri)->lockForUpdate()->select('posts_total')->get();
         $posts_total = $boards[0]->posts_total;
         // Third, we store a unique checksum for this post for duplicate tracking.
         $board->checksums()->create(['checksum' => $this->getChecksum()]);
         // Optionally, we also expend the adventure.
         $adventure = BoardAdventure::getAdventure($board);
         if ($adventure) {
             $this->adventure_id = $adventure->adventure_id;
             $adventure->expended_at = $this->created_at;
             $adventure->save();
         }
         // We set our board_id and save the post.
         $this->board_id = $posts_total;
         $this->author_id = $this->makeAuthorId();
         $this->password = $this->makePassword($this->password);
         $this->save();
         // Optionally, the OP of this thread needs a +1 to reply count.
         if ($thread instanceof static) {
             // We're not using the Model for this because it fails under high volume.
             $threadNewValues = ['updated_at' => $thread->updated_at, 'reply_last' => $this->created_at, 'reply_count' => $thread->replies()->count(), 'reply_file_count' => $thread->replyFiles()->count()];
             if (!$this->isBumpless() && !$thread->isBumplocked()) {
                 $threadNewValues['bumped_last'] = $this->created_at;
             }
             DB::table('posts')->where('post_id', $thread->post_id)->update($threadNewValues);
         }
         // Queries and locks are handled automatically after this closure ends.
     });
     // Process uploads.
     $uploads = [];
     // Check file uploads.
     if (is_array($files = Input::file('files'))) {
         $uploads = array_filter($files);
         if (count($uploads) > 0) {
             foreach ($uploads as $uploadIndex => $upload) {
                 if (file_exists($upload->getPathname())) {
                     FileStorage::createAttachmentFromUpload($upload, $this);
                 }
             }
         }
     } else {
         if (is_array($files = Input::get('files'))) {
             $uniques = [];
             $hashes = $files['hash'];
             $names = $files['name'];
             $spoilers = isset($files['spoiler']) ? $files['spoiler'] : [];
             $storages = FileStorage::whereIn('hash', $hashes)->get();
             foreach ($hashes as $index => $hash) {
                 if (!isset($uniques[$hash])) {
                     $uniques[$hash] = true;
                     $storage = $storages->where('hash', $hash)->first();
                     if ($storage && !$storage->banned) {
                         $spoiler = isset($spoilers[$index]) ? $spoilers[$index] == 1 : false;
                         $upload = $storage->createAttachmentWithThis($this, $names[$index], $spoiler, false);
                         $upload->position = $index;
                         $uploads[] = $upload;
                     }
                 }
             }
             $this->attachmentLinks()->saveMany($uploads);
             FileStorage::whereIn('hash', $hashes)->increment('upload_count');
         }
     }
     // Finally fire event on OP, if it exists.
     if ($thread instanceof Post) {
         $thread->setRelation('board', $board);
         Event::fire(new ThreadNewReply($thread));
     }
     return $this;
 }
Ejemplo n.º 5
0
 /**
  * Pushes the post to the specified board, as a new thread or as a reply.
  * This autoatically handles concurrency issues. Creating a new reply without
  * using this method is forbidden by the `creating` event in ::boot.
  *
  *
  * @param  App\Board  &$board
  * @param  App\Post   &$thread
  * @return void
  */
 public function submitTo(Board &$board, &$thread = null)
 {
     $this->board_uri = $board->board_uri;
     $this->author_ip = inet_pton(Request::ip());
     // Hash the salt because I honestly don't know if $this->save() is safe enough to parse unsanized information into.
     $this->author_salt = hash(env('APP_HASH'), Cookie::get('author_salt'));
     $this->author_country = $board->getConfig('postsAuthorCountry', false) ? new Geolocation() : null;
     $this->reply_last = $this->freshTimestamp();
     $this->bumped_last = $this->reply_last;
     $this->setCreatedAt($this->reply_last);
     $this->setUpdatedAt($this->reply_last);
     if (!is_null($thread) && !$thread instanceof Post) {
         $thread = $board->getLocalThread($thread);
         $this->reply_to = $thread->post_id;
         $this->reply_to_board_id = $thread->board_id;
     }
     // Handle tripcode, if any.
     if (preg_match('/^([^#]+)?(##|#)(.+)$/', $this->author, $match)) {
         // Remove password from name.
         $this->author = $match[1];
         // Whether a secure tripcode was requested, currently unused.
         $secure_tripcode_requested = $match[2] == '##';
         // Convert password to tripcode, store tripcode hash in DB.
         $this->insecure_tripcode = ContentFormatter::formatInsecureTripcode($match[3]);
     }
     // Store the post in the database.
     DB::transaction(function () use($board, $thread) {
         // The objective of this transaction is to prevent concurrency issues in the database
         // on the unique joint index [`board_uri`,`board_id`] which is generated procedurally
         // alongside the primary autoincrement column `post_id`.
         // First instruction is to add +1 to posts_total and set the last_post_at on the Board table.
         DB::table('boards')->where('board_uri', $this->board_uri)->increment('posts_total');
         DB::table('boards')->where('board_uri', $this->board_uri)->update(['last_post_at' => $this->created_at]);
         // Second, we record this value and lock the table.
         $boards = DB::table('boards')->where('board_uri', $this->board_uri)->lockForUpdate()->select('posts_total')->get();
         $posts_total = $boards[0]->posts_total;
         // Optionally, the OP of this thread needs a +1 to reply count.
         if ($thread instanceof Post) {
             if (!$this->isBumpless() && !$thread->isBumplocked()) {
                 $thread->bumped_last = $this->created_at;
                 // We explicitly set the updated_at to what it is now.
                 // If we didn't, this would change.
                 // We don't want that because it screws up the API and
                 // makes it think the OP post has had its content edited.
                 $thread->updated_at = $thread->updated_at;
             }
             $thread->reply_last = $this->created_at;
             $thread->reply_count += 1;
             $thread->save();
         }
         // Optionally, we also expend the adventure.
         $adventure = BoardAdventure::getAdventure($board);
         if ($adventure) {
             $this->adventure_id = $adventure->adventure_id;
             $adventure->expended_at = $this->created_at;
             $adventure->save();
         }
         // Finally, we set our board_id and save.
         $this->board_id = $posts_total;
         //$this->author_salt =
         $this->author_id = $this->makeAuthorId();
         $this->save();
         // Queries and locks are handled automatically after this closure ends.
     });
     // Process uploads.
     $uploads = [];
     // Check file uploads.
     if (is_array($files = Input::file('files'))) {
         $uploads = array_filter($files);
         if (count($uploads) > 0) {
             foreach ($uploads as $uploadIndex => $upload) {
                 if (file_exists($upload->getPathname())) {
                     FileStorage::createAttachmentFromUpload($upload, $this);
                 }
             }
         }
     } else {
         if (is_array($files = Input::get('files'))) {
             $uniques = [];
             $hashes = $files['hash'];
             $names = $files['name'];
             $spoilers = isset($files['spoiler']) ? $files['spoiler'] : [];
             $storages = FileStorage::whereIn('hash', $hashes)->get();
             foreach ($hashes as $index => $hash) {
                 if (!isset($uniques[$hash])) {
                     $uniques[$hash] = true;
                     $storage = $storages->where('hash', $hash)->first();
                     if ($storage && !$storage->banned) {
                         $spoiler = isset($spoilers[$index]) ? $spoilers[$index] == 1 : false;
                         $uploads[] = $storage->createAttachmentWithThis($this, $names[$index], $spoiler, false);
                     }
                 }
             }
             $this->attachmentLinks()->saveMany($uploads);
             FileStorage::whereIn('hash', $hashes)->increment('upload_count');
         }
     }
     // Finally fire event on OP, if it exists.
     if ($thread instanceof Post) {
         Event::fire(new ThreadNewReply($thread));
     }
 }