Esempio n. 1
0
 public function get_index()
 {
     if (!$this->check_board()) {
         return $this->response->setData(['error' => _i('No board selected.')])->setStatusCode(422);
     }
     $page = $this->getQuery('page');
     if (!$page) {
         return $this->response->setData(['error' => _i('The "page" parameter is missing.')])->setStatusCode(422);
     }
     if (!ctype_digit((string) $page)) {
         return $this->response->setData(['error' => _i('The value for "page" is invalid.')])->setStatusCode(422);
     }
     $page = intval($page);
     try {
         $options = ['per_page' => $this->radix->getValue('threads_per_page'), 'per_thread' => 5, 'order' => 'by_thread'];
         $board = Board::forge($this->getContext())->getLatest()->setRadix($this->radix)->setPage($page)->setOptions($options);
         foreach ($board->getCommentsUnsorted() as $comment) {
             $this->apify($comment);
         }
         $this->response->setData($board->getComments());
     } catch (\Foolz\Foolfuuka\Model\BoardThreadNotFoundException $e) {
         return $this->response->setData(['error' => _i('Thread not found.')]);
     } catch (\Foolz\Foolfuuka\Model\BoardException $e) {
         return $this->response->setData(['error' => _i('Encountered an unknown error.')])->setStatusCode(500);
     }
     return $this->response;
 }
Esempio n. 2
0
 /**
  * Gets a post by num or doc_d
  *
  * @return  \Foolz\FoolFuuka\Model\Board  The current object
  * @throws  BoardMalformedInputException  If the $num is not a valid post number
  * @throws  BoardMissingOptionsException  If doc_id or num has not been specified
  * @throws  BoardPostNotFoundException    If the post has not been found
  */
 protected function p_getPostComment()
 {
     extract($this->options);
     $query = $this->dc->qb()->select('*')->from($this->radix->getTable(), 'r');
     if (isset($num)) {
         if (!static::isValidPostNumber($num)) {
             throw new BoardMalformedInputException();
         }
         $num_arr = static::splitPostNumber($num);
         $query->where('num = :num')->andWhere('subnum = :subnum')->setParameter(':num', $num_arr['num'])->setParameter(':subnum', $num_arr['subnum']);
     } elseif (isset($doc_id)) {
         $query->where('doc_id = :doc_id')->setParameter(':doc_id', $doc_id);
     } else {
         throw new BoardMissingOptionsException(_i('No posts found with the submitted options.'));
     }
     $result = $query->leftJoin('r', $this->radix->getTable('_images'), 'mg', 'mg.media_id = r.media_id')->execute()->fetchAll();
     if (!count($result)) {
         throw new BoardPostNotFoundException(_i('Post not found.'));
     }
     foreach ($result as $bulk) {
         $data = new CommentBulk();
         $data->import($bulk, $this->radix);
         $this->comments_unsorted[] = $data;
     }
     foreach ($this->comments_unsorted as $comment) {
         $this->comments[$data->comment->getPostNum('_')] = $data;
     }
     return $this;
 }
Esempio n. 3
0
 public function radix_redirect($filename = null)
 {
     $redirect = $this->uri->create([$this->radix->shortname]) . $filename;
     if ($this->radix->archive) {
         $redirect = ($this->radix->getValue('images_url') ?: '//images.4chan.org/' . $this->radix->shortname . '/src/') . $filename;
     }
     $this->builder->createLayout('redirect')->getParamManager()->setParam('url', $redirect);
     $this->builder->getProps()->addTitle(_i('Redirecting'));
     return $this->response->setContent($this->builder->build());
 }
Esempio n. 4
0
 /**
  * Delete the post and eventually the entire thread if it's OP
  * Also deletes the images when it's the only post with that image
  *
  * @param null $password
  * @param bool $force
  * @param bool $thread
  * @throws CommentSendingDatabaseException
  * @throws CommentDeleteWrongPassException
  * @return array|bool
  */
 protected function p_delete($password = null, $force = false, $thread = false)
 {
     if (!$this->getAuth()->hasAccess('comment.passwordless_deletion') && $force !== true) {
         if (!password_verify($password, $this->comment->getDelpass())) {
             throw new CommentDeleteWrongPassException(_i('You did not provide the correct deletion password.'));
         }
     }
     try {
         $this->dc->getConnection()->beginTransaction();
         // check that the post isn't already in deleted
         $has_deleted = $this->dc->qb()->select('COUNT(*) as found')->from($this->radix->getTable('_deleted'), 'd')->where('doc_id = :doc_id')->setParameter(':doc_id', $this->comment->doc_id)->execute()->fetch();
         if (!$has_deleted['found']) {
             // throw into _deleted table
             $this->dc->getConnection()->executeUpdate('INSERT INTO ' . $this->radix->getTable('_deleted') . ' ' . $this->dc->qb()->select('*')->from($this->radix->getTable(), 't')->where('doc_id = ' . $this->dc->getConnection()->quote($this->comment->doc_id))->getSQL());
         }
         // delete post
         $this->dc->qb()->delete($this->radix->getTable())->where('doc_id = :doc_id')->setParameter(':doc_id', $this->comment->doc_id)->execute();
         // purge reports
         $this->dc->qb()->delete($this->dc->p('reports'))->where('board_id = :board_id')->andWhere('doc_id = :doc_id')->setParameter(':board_id', $this->radix->id)->setParameter(':doc_id', $this->comment->doc_id)->execute();
         // clear cache
         $this->radix_coll->clearCache();
         // remove image file
         if (isset($this->media)) {
             $media_sql = $this->dc->qb()->select('COUNT(*)')->from($this->radix->getTable(), 't')->where('media_id = :media_id')->setParameter(':media_id', $this->media->media_id)->getSQL();
             $this->dc->qb()->update($this->radix->getTable('_images'))->set('total', '(' . $media_sql . ')')->where('media_id = :media_id')->setParameter(':media_id', $this->media->media_id)->execute();
             $has_image = $this->dc->qb()->select('total')->from($this->radix->getTable('_images'), 'ti')->where('media_id = :media_id')->setParameter(':media_id', $this->media->media_id)->execute()->fetch();
             if (!$has_image || !$has_image['total']) {
                 $media = new Media($this->getContext(), $this->bulk);
                 $media->delete();
             }
         }
         // if this is OP, delete replies too
         if ($this->comment->op) {
             // delete thread data
             $this->dc->qb()->delete($this->radix->getTable('_threads'))->where('thread_num = :thread_num')->setParameter(':thread_num', $this->comment->thread_num)->execute();
             // process each comment
             $comments = $this->dc->qb()->select('doc_id')->from($this->radix->getTable(), 'b')->where('thread_num = :thread_num')->setParameter(':thread_num', $this->comment->thread_num)->execute()->fetchAll();
             foreach ($comments as $comment) {
                 $post = Board::forge($this->getContext())->getPost()->setOptions('doc_id', $comment['doc_id'])->setRadix($this->radix)->getComments();
                 $post = current($post);
                 $post = new Comment($this->getContext(), $post);
                 $post->delete(null, true, true);
             }
         } else {
             // if this is not triggered by a thread deletion, update the thread table
             if ($thread === false && !$this->radix->archive) {
                 $time_last = '
                 (
                     COALESCE(GREATEST(
                         time_op,
                         (
                             SELECT MAX(timestamp) FROM ' . $this->radix->getTable() . ' xr
                             WHERE thread_num = ' . $this->dc->getConnection()->quote($this->comment->thread_num) . ' AND subnum = 0
                         )
                     ), time_op)
                 )';
                 $time_bump = '
                 (
                     COALESCE(GREATEST(
                         time_op,
                         (
                             SELECT MAX(timestamp) FROM ' . $this->radix->getTable() . ' xr
                             WHERE thread_num = ' . $this->dc->getConnection()->quote($this->comment->thread_num) . ' AND subnum = 0
                                 AND (email <> \'sage\' OR email IS NULL)
                         )
                     ), time_op)
                 )';
                 $time_ghost = '
                 (
                     SELECT MAX(timestamp) FROM ' . $this->radix->getTable() . ' xr
                     WHERE thread_num = ' . $this->dc->getConnection()->quote($this->comment->thread_num) . ' AND subnum <> 0
                 )';
                 $time_ghost_bump = '
                 (
                     SELECT MAX(timestamp) FROM ' . $this->radix->getTable() . ' xr
                     WHERE thread_num = ' . $this->dc->getConnection()->quote($this->comment->thread_num) . ' AND subnum <> 0
                         AND (email <> \'sage\' OR email IS NULL)
                 )';
                 // update thread information
                 $this->dc->qb()->update($this->radix->getTable('_threads'))->set('time_last', $time_last)->set('time_bump', $time_bump)->set('time_ghost', $time_ghost)->set('time_ghost_bump', $time_ghost_bump)->set('time_last_modified', ':time')->set('nreplies', 'nreplies - 1')->set('nimages', $this->media === null ? 'nimages' : 'nimages - 1')->where('thread_num = :thread_num')->setParameter(':time', $this->getRadixTime())->setParameter(':thread_num', $this->comment->thread_num)->execute();
             }
         }
         $this->dc->getConnection()->commit();
         $this->clearCache();
         if ($thread === false) {
             $this->audit->log(Audit::AUDIT_DEL_POST, ['radix' => $this->radix->id, 'doc_id' => $this->comment->doc_id, 'thread_num' => $this->comment->thread_num, 'num' => $this->comment->num, 'subnum' => $this->comment->subnum]);
         }
     } catch (\Doctrine\DBAL\DBALException $e) {
         $this->logger->error('\\Foolz\\FoolFuuka\\Model\\CommentInsert: ' . $e->getMessage());
         $this->dc->getConnection()->rollBack();
         throw new CommentSendingDatabaseException(_i('Something went wrong when deleting the post in the database. Try again.'));
     }
     return $this;
 }
Esempio n. 5
0
 /**
  * Inserts the media uploaded (static::forgeFromUpload())
  *
  * @param  string   $microtime  The time in microseconds to use for the local filename
  * @param  boolean  $is_op      True if the thumbnail sizes should be for OP, false if they should be for a reply
  *
  * @return  \Foolz\Foolfuuka\Model\Media       The current object updated with the insert data
  * @throws  MediaInsertInvalidFormatException  In case the media is not a valid format
  * @throws  MediaInsertDomainException         In case the media uploaded is in example too small or validations don't pass
  * @throws  MediaInsertRepostException         In case the media has been reposted too recently according to control panel settings
  * @throws  MediaThumbnailCreationException	   If the thumbnail fails to generate
  */
 public function p_insert($microtime, $is_op)
 {
     $file = $this->temp_file;
     $this->op = $is_op;
     $data = \Foolz\Plugin\Hook::forge('Foolz\\Foolfuuka\\Model\\Media::insert.result.media_data')->setParams(['dimensions' => getimagesize($file->getPathname()), 'file' => $file, 'name' => $file->getClientOriginalName(), 'path' => $file->getPathname(), 'hash' => base64_encode(pack("H*", md5(file_get_contents($file->getPathname())))), 'size' => $file->getClientSize(), 'time' => $microtime, 'media_orig' => $microtime . '.' . strtolower($file->getClientOriginalExtension()), 'preview_orig' => $microtime . 's.' . strtolower($file->getClientOriginalExtension())])->execute()->getParams();
     if (!($getimagesize = $data['dimensions'])) {
         throw new MediaInsertInvalidFormatException(_i('The file you uploaded is not allowed.'));
     }
     // if width and height are lower than 25 reject the image
     if ($getimagesize[0] < 25 || $getimagesize[1] < 25) {
         throw new MediaInsertDomainException(_i('The file you uploaded is too small.'));
     }
     if ($getimagesize[0] > $this->radix->getValue('max_image_size_width') || $getimagesize[1] > $this->radix->getValue('max_image_size_height')) {
         throw new MediaInsertDomainException(_i('The dimensions of the file you uploaded is too large.'));
     }
     $this->media->media_w = $getimagesize[0];
     $this->media->media_h = $getimagesize[1];
     $this->media->media_filename = $data['name'];
     $this->media->media_hash = $data['hash'];
     $this->media->media_size = $data['size'];
     $this->media->media_orig = $data['media_orig'];
     $this->media->preview_orig = $data['preview_orig'];
     $do_thumb = true;
     $do_full = true;
     try {
         $duplicate = CommentBulk::forge($this->radix, null, $this->media_factory->getByMediaHash($this->radix, $this->media->media_hash));
         $duplicate = new Media($this->getContext(), $duplicate);
         // we want the current media to work with the same filenames as previously stored
         $this->media->media_id = $duplicate->media->media_id;
         $this->media->media = $duplicate->media->media;
         $this->media->media_orig = $duplicate->media->media;
         $this->media->preview_op = $duplicate->media->preview_op;
         $this->media->preview_reply = $duplicate->media->preview_reply;
         if (!$this->getAuth()->hasAccess('comment.limitless_comment') && $this->radix->getValue('min_image_repost_time')) {
             // if it's -1 it means that image reposting is disabled, so this image shouldn't pass
             if ($this->radix->getValue('min_image_repost_time') == -1) {
                 throw new MediaInsertRepostException(_i('This image has already been posted once. This board doesn\'t allow image reposting.'));
             }
             // we don't have to worry about archives with weird timestamps, we can't post images there
             $duplicate_entry = $this->dc->qb()->select('COUNT(*) as count, MAX(timestamp) as max_timestamp')->from($this->radix->getTable(), 'r')->where('media_id = :media_id')->andWhere('timestamp > :timestamp')->setParameter('media_id', $duplicate->media->media_id)->setParameter('timestamp', time() - $this->radix->getValue('min_image_repost_time'))->setMaxResults(1)->execute()->fetch();
             if ($duplicate_entry['count']) {
                 $datetime = new \DateTime(date('Y-m-d H:i:s', $duplicate_entry['max_timestamp'] + $this->radix->getValue('min_image_repost_time')));
                 $remain = $datetime->diff(new \DateTime());
                 throw new MediaInsertRepostException(_i('This image has been posted recently. You will be able to post it again in %s.', ($remain->d > 0 ? $remain->d . ' ' . _i('day(s)') : '') . ' ' . ($remain->h > 0 ? $remain->h . ' ' . _i('hour(s)') : '') . ' ' . ($remain->i > 0 ? $remain->i . ' ' . _i('minute(s)') : '') . ' ' . ($remain->s > 0 ? $remain->s . ' ' . _i('second(s)') : '')));
             }
         }
         // if we're here, we got the media
         $duplicate_dir = $duplicate->getDir();
         if ($duplicate_dir !== null && file_exists($duplicate_dir)) {
             $do_full = false;
         }
         $duplicate->op = $is_op;
         $duplicate_dir_thumb = $duplicate->getDir(true, true);
         if ($duplicate_dir_thumb !== null && file_exists($duplicate_dir_thumb)) {
             $duplicate_dir_thumb_size = getimagesize($duplicate_dir_thumb);
             $this->media->preview_w = $duplicate_dir_thumb_size[0];
             $this->media->preview_h = $duplicate_dir_thumb_size[1];
             $do_thumb = false;
         }
     } catch (MediaNotFoundException $e) {
     }
     if ($do_thumb) {
         $thumb_width = $this->radix->getValue('thumbnail_reply_width');
         $thumb_height = $this->radix->getValue('thumbnail_reply_height');
         if ($is_op) {
             $thumb_width = $this->radix->getValue('thumbnail_op_width');
             $thumb_height = $this->radix->getValue('thumbnail_op_height');
         }
         if (!file_exists($this->pathFromFilename(true, $is_op))) {
             mkdir($this->pathFromFilename(true, $is_op), 0777, true);
         }
         $return = \Foolz\Plugin\Hook::forge('Foolz\\Foolfuuka\\Model\\Media::insert.result.create_thumbnail')->setObject($this)->setParams(['thumb_width' => $thumb_width, 'thumb_height' => $thumb_height, 'exec' => str_replace(' ', '\\ ', $this->preferences->get('foolframe.imagick.convert_path')), 'is_op' => $is_op, 'media' => $file, 'thumb' => $this->pathFromFilename(true, $is_op, true)])->execute()->get();
         if ($return instanceof \Foolz\Plugin\Void) {
             if ($this->radix->getValue('enable_animated_gif_thumbs') && strtolower($file->getClientOriginalExtension()) === 'gif') {
                 exec(str_replace(' ', '\\ ', $this->preferences->get('foolframe.imagick.convert_path')) . " " . $data['path'] . " -coalesce -treedepth 4 -colors 256 -quality 80 -background none " . "-resize \"" . $thumb_width . "x" . $thumb_height . ">\" " . $this->pathFromFilename(true, $is_op, true));
             } else {
                 exec(str_replace(' ', '\\ ', $this->preferences->get('foolframe.imagick.convert_path')) . " " . $data['path'] . "[0] -quality 80 -background none " . "-resize \"" . $thumb_width . "x" . $thumb_height . ">\" " . $this->pathFromFilename(true, $is_op, true));
             }
         }
         if (!file_exists($this->pathFromFilename(true, $is_op, true))) {
             throw new MediaThumbnailCreationException(_i('The thumbnail failed to generate.'));
         }
         $thumb_getimagesize = getimagesize($this->pathFromFilename(true, $is_op, true));
         $this->media->preview_w = $thumb_getimagesize[0];
         $this->media->preview_h = $thumb_getimagesize[1];
         if ($do_full) {
             if (!file_exists($this->pathFromFilename())) {
                 mkdir($this->pathFromFilename(), 0777, true);
             }
             copy($data['path'], $this->pathFromFilename(false, false, true));
         }
     }
     if (function_exists('exif_read_data') && in_array(strtolower($file->getClientOriginalExtension()), ['jpg', 'jpeg', 'tiff'])) {
         $exif_data = null;
         getimagesize($data['path'], $exif_data);
         if (!isset($exif_data['APP1']) || strpos($exif_data['APP1'], 'Exif') === 0) {
             $exif = exif_read_data($data['path']);
             if ($exif !== false) {
                 $this->media->exif = $exif;
             }
         }
     }
     if (!$this->media->media_id) {
         $this->dc->getConnection()->insert($this->radix->getTable('_images'), ['media_hash' => $this->media->media_hash, 'media' => $this->media->media_orig, 'preview_op' => $this->op ? $this->media->preview_orig : null, 'preview_reply' => !$this->op ? $this->media->preview_orig : null, 'total' => 1, 'banned' => 0]);
         $this->media->media_id = $this->dc->getConnection()->lastInsertId($this->radix->getTable('_images_media_id_seq'));
     } else {
         $media_sql = $this->dc->qb()->select('COUNT(*)')->from($this->radix->getTable(), 't')->where('media_id = :media_id')->setParameter(':media_id', $this->media->media_id)->getSQL();
         $query = $this->dc->qb()->update($this->radix->getTable('_images'));
         if ($this->media === null) {
             $query->set('media', ':media_orig')->setParameter(':media_orig', $this->media->preview_orig);
         }
         if ($this->op && $this->media->preview_op === null) {
             $query->set('preview_op', ':preview_orig')->setParameter(':preview_orig', $this->media->preview_orig);
         }
         if (!$this->op && $this->media->preview_reply === null) {
             $query->set('preview_reply', ':preview_orig')->setParameter(':preview_orig', $this->media->preview_orig);
         }
         $query->set('total', '(' . $media_sql . ')');
         $query->where('media_id = :media_id')->setParameter(':media_id', $this->media->media_id)->execute();
     }
     return $this;
 }
Esempio n. 6
0
 /**
  * Takes an uploaded file and makes an object. It doesn't do the ->insert()
  *
  * @param  Radix $radix  The Radix where this Media belongs
  *
  * @return  \Foolz\Foolfuuka\Model\Media            A new Media object with the upload data
  * @throws  MediaUploadNoFileException              If there's no file uploaded
  * @throws  MediaUploadMultipleNotAllowedException  If there's multiple uploads
  * @throws  MediaUploadInvalidException             If the file format is not allowed
  */
 protected function p_forgeFromUpload(Request $request, Radix $radix)
 {
     $config = Hook::forge('Foolz\\Foolfuuka\\Model\\Media::upload.config')->setParams(['ext_whitelist' => ['jpg', 'jpeg', 'gif', 'png'], 'mime_whitelist' => ['image/jpeg', 'image/png', 'image/gif']])->execute()->getParams();
     if (!$request->files->count()) {
         throw new MediaUploadNoFileException(_i('You must upload an image or your image was too large.'));
     }
     if ($request->files->count() !== 1) {
         throw new MediaUploadMultipleNotAllowedException(_i('You can\'t upload multiple images.'));
     }
     /** @var UploadedFile[] $files */
     $files = $request->files->all();
     $max_size = $radix->getValue('max_image_size_kilobytes') * 1024;
     foreach ($files as $file) {
         if (!$file->isValid()) {
             if ($file->getError() === UPLOAD_ERR_INI_SIZE) {
                 throw new MediaUploadInvalidException(_i('The server is misconfigured: the FoolFuuka upload size should be lower than PHP\'s upload limit.'));
             }
             if ($file->getError() === UPLOAD_ERR_PARTIAL) {
                 throw new MediaUploadInvalidException(_i('You uploaded the file partially.'));
             }
             if ($file->getError() === UPLOAD_ERR_CANT_WRITE) {
                 throw new MediaUploadInvalidException(_i('The image couldn\'t be saved on the disk.'));
             }
             if ($file->getError() === UPLOAD_ERR_EXTENSION) {
                 throw new MediaUploadInvalidException(_i('A PHP extension broke and made processing the image impossible.'));
             }
             throw new MediaUploadInvalidException(_i('Unexpected upload error.'));
         }
         if (mb_strlen($file->getFilename(), 'utf-8') > 64) {
             throw new MediaUploadInvalidException(_i('You uploaded a file with a too long filename.'));
         }
         if (!in_array(strtolower($file->getClientOriginalExtension()), $config['ext_whitelist'])) {
             throw new MediaUploadInvalidException(_i('You uploaded a file with an invalid extension.'));
         }
         if (!in_array(strtolower($file->getMimeType()), $config['mime_whitelist'])) {
             throw new MediaUploadInvalidException(_i('You uploaded a file with an invalid mime type.'));
         }
         if ($file->getClientSize() > $max_size && !$this->getAuth()->hasAccess('media.limitless_media')) {
             throw new MediaUploadInvalidException(_i('You uploaded a too big file. The maxmimum allowed filesize is %s', $radix->getValue('max_image_size_kilobytes')));
         }
     }
     $media = new Media($this->getContext());
     $media->setTempFile($radix, $files['file_image']);
     return $media;
 }