/** * Get content as a string of HTML. * * @return string */ public function getFormatted($skipCache = false) { $parsed_html = $this->{$this->formattable['parsed_html']}; $parsed_at = $this->{$this->formattable['parsed_at']}; if (!$skipCache && !is_null($parsed_html)) { return $parsed_html; } $ContentFormatter = new ContentFormatter(); $this->{$this->formattable['parsed_html']} = $parsed_html = $ContentFormatter->formatPage($this, $this->formattable['input_text']); $this->{$this->formattable['parsed_at']} = $parsed_at = $this->freshTimestamp(); if (!mb_check_encoding($parsed_html, 'UTF-8')) { return "<tt style=\"color:red;\">Invalid encoding. This should never happen!</tt>"; } // This is a partial update. // We don't want to force the model to save early. static::where([$this->primaryKey => $this->{$this->primaryKey}])->update([$this->formattable['parsed_html'] => $parsed_html, $this->formattable['parsed_at'] => $parsed_at]); return $parsed_html; }
public function getSidebarContent() { $ContentFormatter = new ContentFormatter(); return $ContentFormatter->formatSidebar($this->getConfig('boardSidebarText')); }
/** * 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; }
/** * 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"]); }
/** * Renders the post edit form. */ public function getReport(Request $request, Board $board, $post, $global = false) { // Validate the request parameters. if (!($post = $this->validatePost($board, $post)) instanceof Post) { // If the response isn't a Post, it's a redirect or error. // Return the message. return $post; } $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)) { $user = $this->user; $report = Report::where('post_id', '=', $post->post_id)->where('global', $global === "global")->where('board_uri', $board->board_uri)->where(function ($query) use($user) { $query->where('reporter_ip', inet_pton(Request::ip())); if (!$user->isAnonymous()) { $query->orWhere('user_id', $user->user_id); } })->first(); } return $this->view(static::VIEW_MOD, ['actions' => $actions, 'form' => "report", 'board' => $board, 'post' => $post, 'report' => $report ?: false, 'reportText' => $reportText, 'reportGlobal' => $global === "global"]); }
/** * Parses the post text for citations. * * @return Collection */ public function getCitesFromText() { return ContentFormatter::getCites($this); }
/** * Returns the fully rendered HTML content of this post. * * @return string */ public function getBodyFormatted() { $ContentFormatter = new ContentFormatter(); return $ContentFormatter->formatPost($this); }
/** * 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)); } }