public function submit($data, $media) { // some beginners' validation, while through validation will happen in the Comment model $validator = new Validator(); $validator->add('thread_num', _i('Thread Number'), [new Assert\NotBlank()])->add('name', _i('Name'), [new Assert\Length(['max' => 64])])->add('email', _i('Email'), [new Assert\Length(['max' => 64])])->add('title', _i('Title'), [new Assert\Length(['max' => 64])])->add('delpass', _i('Deletion pass'), [new Assert\Length(['min' => 3, 'max' => 32])]); // no empty posts without images if ($media === null) { $validator->add('comment', _i('Comment'), [new Assert\NotBlank(), new Assert\Length(['min' => 3])]); } // this is for redirecting, not for the database $limit = false; if (isset($data['last_limit'])) { $limit = intval($data['last_limit']); unset($data['last_limit']); } $validator->validate($data); if (!$validator->getViolations()->count()) { try { $data['poster_ip'] = Inet::ptod($this->getRequest()->getClientIp()); $bulk = new CommentBulk(); $bulk->import($data, $this->radix); $comment = new CommentInsert($this->getContext(), $bulk); $comment->insert($media, $data); } catch (\Foolz\Foolfuuka\Model\CommentSendingRequestCaptchaException $e) { if ($this->getRequest()->isXmlHttpRequest()) { return $this->response->setData(['captcha' => true]); } else { return $this->error(_i('Your message looked like spam. Make sure you have JavaScript enabled to display the reCAPTCHA to submit the comment.')); } } catch (\Foolz\Foolfuuka\Model\CommentSendingException $e) { if ($this->getRequest()->isXmlHttpRequest()) { return $this->response->setData(['error' => $e->getMessage()]); } else { return $this->error($e->getMessage()); } } } else { if ($this->getRequest()->isXmlHttpRequest()) { return $this->response->setData(['error' => $validator->getViolations()->getText()]); } else { return $this->error($validator->getViolations()->getHtml()); } } if ($this->request->isXmlHttpRequest()) { $latest_doc_id = $this->getPost('latest_doc_id'); if ($latest_doc_id && ctype_digit((string) $latest_doc_id)) { try { $board = Board::forge($this->getContext())->getThread($comment->comment->thread_num)->setRadix($this->radix)->setOptions(['type' => 'from_doc_id', 'latest_doc_id' => $latest_doc_id]); $comments = $board->getComments(); } catch (\Foolz\Foolfuuka\Model\BoardThreadNotFoundException $e) { return $this->error(_i('Thread not found.')); } catch (\Foolz\Foolfuuka\Model\BoardException $e) { return $this->error(_i('Unknown error.')); } $comment_obj = new Comment($this->getContext()); $comment_obj->setControllerMethod($limit ? 'last/' . $limit : 'thread'); $media_obj = new Media($this->getContext()); $m = null; foreach ($board->getCommentsUnsorted() as $bulk) { $comment_obj->setBulk($bulk, $this->radix); if ($bulk->media) { $media_obj->setBulk($bulk, $this->radix); $m = $media_obj; } else { $m = null; } if ($this->builder) { $this->param_manager->setParam('controller_method', $limit ? 'last/' . $limit : 'thread'); $partial = $this->builder->createPartial('board_comment', 'board_comment'); $partial->getParamManager()->setParam('p', $comment_obj)->setParam('p_media', $m); $bulk->comment->formatted = $partial->build(); $partial->clearBuilt(); } } $this->response->setData(['success' => _i('Message sent.')] + $comments); } else { if ($this->builder) { $this->param_manager->setParam('controller_method', $limit ? 'last/' . $limit : 'thread'); $partial = $this->builder->createPartial('board_comment', 'board_comment'); $partial->getParamManager()->setParam('p', new Comment($this->getContext(), $comment->bulk))->setParam('p_media', new Media($this->getContext(), $comment->bulk)); $bulk->comment->formatted = $partial->build(); $partial->clearBuilt(); } $this->response->setData(['success' => _i('Message sent.'), 'thread_num' => $comment->comment->thread_num, $comment->comment->thread_num => ['posts' => [$comment->bulk]]]); } } else { $this->builder->createLayout('redirect')->getParamManager()->setParam('url', $this->uri->create([$this->radix->shortname, !$limit ? 'thread' : 'last/' . $limit, $comment->comment->thread_num]) . '#' . $comment->comment->num); $this->builder->getProps()->addTitle(_i('Redirecting')); $this->response->setContent($this->builder->build()); } return $this->response; }
$data_array = json_decode($data); foreach ($data_array as $key => $item) { ?> <div class="image_reposts_image"> <div class="image_reposts_number"> <strong>#<?php echo $key + 1; ?> </strong> - Reposts: <?php echo $item->total; ?> </div> <?php $bulk = new CommentBulk(); $bulk->import((array) $item, $this->radix); $media = new Media($this->getContext(), $bulk); $media->op = true; ?> <a href="<?php echo $this->uri->create([$this->radix->shortname, 'search', 'image', $media->getSafeMediaHash()]); ?> "> <img src="<?php echo $media->getThumbLink($this->getRequest()); ?> " /> </a> </div> <?php
/** * Gets the search results * * @return \Foolz\Foolfuuka\Model\Search The current object * @throws SearchEmptyResultException If there's no results to display * @throws SearchRequiresSphinxException If the search submitted requires Sphinx to run * @throws SearchSphinxOfflineException If the Sphinx server is unreachable * @throws SearchInvalidException If the values of the search weren't compatible with the domain */ protected function p_getSearchComments() { $this->profiler->log('Board::getSearchComments Start'); extract($this->options); // set all empty fields to null $search_fields = ['boards', 'subject', 'text', 'username', 'tripcode', 'email', 'capcode', 'uid', 'poster_ip', 'filename', 'image', 'deleted', 'ghost', 'filter', 'type', 'start', 'end', 'results', 'order']; foreach ($search_fields as $field) { if (!isset($args[$field])) { $args[$field] = null; } } // populate an array containing all boards that would be searched $boards = []; if ($args['boards'] !== null) { foreach ($args['boards'] as $board) { $b = $this->radix_coll->getByShortname($board); if ($b) { $boards[] = $b; } } } // search all boards if none selected if (count($boards) == 0) { $boards = $this->radix_coll->getAll(); } // if image is set, get either the media_hash or media_id if ($args['image'] !== null) { if (substr($args['image'], -2) !== '==') { $args['image'] .= '=='; } // if board is set, retrieve media_id if ($this->radix !== null) { try { $media = $this->media_factory->getByMediaHash($this->radix, $args['image']); } catch (MediaNotFoundException $e) { $this->comments_unsorted = []; $this->comments = []; $this->profiler->log('Board::getSearchComments Ended Prematurely'); throw new SearchEmptyResultException(_i('No results found.')); } $args['image'] = $media->media_id; } } if ($this->radix === null && !$this->preferences->get('foolfuuka.sphinx.global')) { // global search requires sphinx throw new SearchRequiresSphinxException(_i('Sorry, this action requires the Sphinx to be installed and running.')); } elseif ($this->radix === null && $this->preferences->get('foolfuuka.sphinx.global') || $this->radix !== null && $this->radix->sphinx) { // configure sphinx connection params $sphinx = explode(':', $this->preferences->get('foolfuuka.sphinx.listen')); $conn = new SphinxConnnection(); $conn->setParams(['host' => $sphinx[0], 'port' => $sphinx[1], 'options' => [MYSQLI_OPT_CONNECT_TIMEOUT => 5]]); $conn->silenceConnectionWarning(true); // establish connection try { SphinxQL::forge($conn); } catch (\Foolz\SphinxQL\ConnectionException $e) { throw new SearchSphinxOfflineException(_i('The search backend is currently unavailable.')); } // determine if all boards will be used for search or not if ($this->radix == null) { $indexes = []; foreach ($boards as $radix) { if (!$radix->sphinx) { continue; } $indexes[] = $radix->shortname . '_ancient'; $indexes[] = $radix->shortname . '_main'; $indexes[] = $radix->shortname . '_delta'; } } else { $indexes = [$this->radix->shortname . '_ancient', $this->radix->shortname . '_main', $this->radix->shortname . '_delta']; } // start search query $query = SphinxQL::forge()->select('id', 'board')->from($indexes); // parse search params if ($args['subject'] !== null) { $query->match('title', $args['subject']); } if ($args['text'] !== null) { if (mb_strlen($args['text'], 'utf-8') < 1) { return []; } $query->match('comment', $args['text'], true); } if ($args['username'] !== null) { $query->match('name', $args['username']); } if ($args['tripcode'] !== null) { $query->match('trip', '"' . $args['tripcode'] . '"'); } if ($args['email'] !== null) { $query->match('email', $args['email']); } if ($args['capcode'] !== null) { if ($args['capcode'] === 'user') { $query->where('cap', ord('N')); } elseif ($args['capcode'] === 'mod') { $query->where('cap', ord('M')); } elseif ($args['capcode'] === 'admin') { $query->where('cap', ord('A')); } elseif ($args['capcode'] === 'dev') { $query->where('cap', ord('D')); } } if ($args['uid'] !== null) { $query->match('pid', $args['uid']); } if ($this->getAuth()->hasAccess('comment.see_ip') && $args['poster_ip'] !== null) { $query->where('pip', (int) Inet::ptod($args['poster_ip'])); } if ($args['filename'] !== null) { $query->match('media_filename', $args['filename']); } if ($args['image'] !== null) { if ($this->radix !== null) { $query->where('mid', (int) $args['image']); } else { $query->match('media_hash', '"' . $args['image'] . '"'); } } if ($args['deleted'] !== null) { if ($args['deleted'] == 'deleted') { $query->where('is_deleted', 1); } if ($args['deleted'] == 'not-deleted') { $query->where('is_deleted', 0); } } if ($args['ghost'] !== null) { if ($args['ghost'] == 'only') { $query->where('is_internal', 1); } if ($args['ghost'] == 'none') { $query->where('is_internal', 0); } } if ($args['filter'] !== null) { if ($args['filter'] == 'image') { $query->where('has_image', 0); } if ($args['filter'] == 'text') { $query->where('has_image', 1); } } if ($args['type'] !== null) { if ($args['type'] == 'sticky') { $query->where('is_sticky', 1); } if ($args['type'] == 'op') { $query->where('is_op', 1); } if ($args['type'] == 'posts') { $query->where('is_op', 0); } } if ($args['start'] !== null) { $query->where('timestamp', '>=', intval(strtotime($args['start']))); } if ($args['end'] !== null) { $query->where('timestamp', '<=', intval(strtotime($args['end']))); } if ($args['results'] !== null) { if ($args['results'] == 'op') { $query->groupBy('thread_num'); $query->withinGroupOrderBy('is_op', 'desc'); } if ($args['results'] == 'posts') { $query->where('is_op', 0); } } if ($args['order'] !== null && $args['order'] == 'asc') { $query->orderBy('timestamp', 'ASC'); } else { $query->orderBy('timestamp', 'DESC'); } $max_matches = $this->preferences->get('foolfuuka.sphinx.max_matches', 5000); // set sphinx options $query->limit($limit)->offset($page * $limit - $limit >= $max_matches ? $max_matches - 1 : $page * $limit - $limit)->option('max_matches', (int) $max_matches)->option('reverse_scan', $args['order'] === 'asc' ? 0 : 1); // submit query try { $search = $query->execute(); } catch (\Foolz\SphinxQL\DatabaseException $e) { $this->logger->error('Search Error: ' . $e->getMessage()); throw new SearchInvalidException(_i('The search backend returned an error.')); } // no results found if (!count($search)) { $this->comments_unsorted = []; $this->comments = []; throw new SearchEmptyResultException(_i('No results found.')); } $sphinx_meta = Helper::pairsToAssoc(Helper::create($conn)->showMeta()->execute()); $this->total_count = $sphinx_meta['total']; $this->total_found = $sphinx_meta['total_found']; // populate sql array for full records $sql = []; foreach ($search as $doc => $result) { $board = $this->radix_coll->getById($result['board']); $sql[] = $this->dc->qb()->select('*, ' . $result['board'] . ' AS board_id')->from($board->getTable(), 'r')->leftJoin('r', $board->getTable('_images'), 'mg', 'mg.media_id = r.media_id')->where('doc_id = ' . $this->dc->getConnection()->quote($result['id']))->getSQL(); } $result = $this->dc->getConnection()->executeQuery(implode(' UNION ', $sql))->fetchAll(); } else { // this is not implemented yet, would require some sort of MySQL search throw new SearchRequiresSphinxException(_i('Sorry, this board does not have search enabled.')); } // no results found IN DATABASE, but we might still get a search count from Sphinx if (!count($result)) { $this->comments_unsorted = []; $this->comments = []; } else { // process results foreach ($result as $key => $row) { $board = $this->radix !== null ? $this->radix : $this->radix_coll->getById($row['board_id']); $bulk = new CommentBulk(); $bulk->import($row, $board); $this->comments_unsorted[] = $bulk; unset($result[$key]); } } $this->comments[0]['posts'] = $this->comments_unsorted; return $this; }
/** * Return a Media object by the media_hash column * * @param Radix $radix The Radix where the Media can be found * @param string $value The filename * * @return \Foolz\Foolfuuka\Model\MediaData The searched object * @throws MediaNotFoundException If the media has not been found */ protected function p_getByFilename(Radix $radix, $filename) { $result = $this->dc->qb()->select('*')->from($radix->getTable(), 'r')->leftJoin('r', $radix->getTable('_images'), 'mg', 'mg.media_id = r.media_id')->where('r.media_orig = :media_orig')->setParameter(':media_orig', $filename)->execute()->fetch(); if ($result) { $bulk = new CommentBulk(); $bulk->import($result, $radix); return $bulk; } throw new MediaNotFoundException(); }
/** * Gets the search results * * @return \Foolz\FoolFuuka\Model\Search The current object * @throws SearchEmptyResultException If there's no results to display * @throws SearchRequiresSphinxException If the search submitted requires Sphinx to run * @throws SearchSphinxOfflineException If the Sphinx server is unreachable * @throws SearchInvalidException If the values of the search weren't compatible with the domain */ protected function p_getResults() { $this->profiler->log('Search::getResults Start'); extract($this->options); $boards = []; $input = $this->getUserInput(); if ($this->radix !== null) { $boards[] = $this->radix; } elseif ($input['boards'] !== null) { foreach ($input['boards'] as $board) { $b = $this->radix_coll->getByShortname($board); if ($b) { $boards[] = $b; } } } // search all boards if none selected if (count($boards) == 0) { $boards = $this->radix_coll->getAll(); } // if image is set, get either the media_hash or media_id if ($input['image'] !== null && substr($input['image'], -2) !== '==') { $input['image'] .= '=='; } if ($this->radix === null && !$this->preferences->get('foolfuuka.sphinx.global')) { throw new SearchRequiresSphinxException(_i('Sorry, the global search function has not been enabled.')); } if ($this->radix !== null && !$this->radix->sphinx) { throw new SearchRequiresSphinxException(_i('Sorry, this board does not have search enabled.')); } $sphinx = explode(':', $this->preferences->get('foolfuuka.sphinx.listen')); $conn = new SphinxConnnection(); $conn->setParams(['host' => $sphinx[0], 'port' => $sphinx[1], 'options' => [MYSQLI_OPT_CONNECT_TIMEOUT => 5]]); $indices = []; foreach ($boards as $radix) { if (!$radix->sphinx) { continue; } $indices[] = $radix->shortname . '_ancient'; $indices[] = $radix->shortname . '_main'; $indices[] = $radix->shortname . '_delta'; } // establish connection try { $query = SphinxQL::create($conn)->select('id', 'board', 'tnum')->from($indices)->setFullEscapeChars(['\\', '(', ')', '|', '-', '!', '@', '%', '~', '"', '&', '/', '^', '$', '='])->setHalfEscapeChars(['\\', '(', ')', '!', '@', '%', '~', '&', '/', '^', '$', '=']); } catch (\Foolz\SphinxQL\Exception\ConnectionException $e) { throw new SearchSphinxOfflineException($this->preferences->get('foolfuuka.sphinx.custom_message', _i('The search backend is currently unavailable.'))); } // process user input if ($input['subject'] !== null) { $query->match('title', $input['subject']); } if ($input['text'] !== null) { if (mb_strlen($input['text'], 'utf-8') < 1) { return []; } $query->match('comment', $input['text'], true); } if ($input['username'] !== null) { $query->match('name', $input['username']); } if ($input['tripcode'] !== null) { $query->match('trip', '"' . $input['tripcode'] . '"'); } if ($input['email'] !== null) { $query->match('email', $input['email']); } if ($input['capcode'] !== null) { switch ($input['capcode']) { case 'user': $query->where('cap', ord('N')); break; case 'mod': $query->where('cap', ord('M')); break; case 'dev': $query->where('cap', ord('D')); break; case 'admin': $query->where('cap', ord('A')); break; } } if ($input['uid'] !== null) { $query->match('pid', $input['uid']); } if ($input['country'] !== null) { $query->match('country', $input['country'], true); } if ($this->getAuth()->hasAccess('comment.see_ip') && $input['poster_ip'] !== null) { $query->where('pip', (int) Inet::ptod($input['poster_ip'])); } if ($input['filename'] !== null) { $query->match('media_filename', $input['filename']); } if ($input['image'] !== null) { $query->match('media_hash', '"' . $input['image'] . '"'); } if ($input['deleted'] !== null) { switch ($input['deleted']) { case 'deleted': $query->where('is_deleted', 1); break; case 'not-deleted': $query->where('is_deleted', 0); break; } } if ($input['ghost'] !== null) { switch ($input['ghost']) { case 'only': $query->where('is_internal', 1); break; case 'none': $query->where('is_internal', 0); break; } } if ($input['filter'] !== null) { switch ($input['filter']) { case 'image': $query->where('has_image', 0); break; case 'text': $query->where('has_image', 1); break; } } if ($input['type'] !== null) { switch ($input['type']) { case 'sticky': $query->where('is_sticky', 1); break; case 'op': $query->where('is_op', 1); break; case 'posts': $query->where('is_op', 0); break; } } if ($input['start'] !== null) { $query->where('timestamp', '>=', intval(strtotime($input['start']))); } if ($input['end'] !== null) { $query->where('timestamp', '<=', intval(strtotime($input['end']))); } if ($input['results'] !== null && $input['results'] == 'thread') { $query->groupBy('tnum'); $query->withinGroupOrderBy('is_op', 'desc'); } if ($input['order'] !== null && $input['order'] == 'asc') { $query->orderBy('timestamp', 'ASC'); } else { $query->orderBy('timestamp', 'DESC'); } $max_matches = $this->preferences->get('foolfuuka.sphinx.max_matches', 5000); // set sphinx options $query->limit($limit)->offset($page * $limit - $limit >= $max_matches ? $max_matches - 1 : $page * $limit - $limit)->option('max_matches', (int) $max_matches)->option('reverse_scan', $input['order'] === 'asc' ? 0 : 1); // submit query try { $this->profiler->log('Start: SphinxQL: ' . $query->compile()->getCompiled()); $search = $query->execute(); $this->profiler->log('Stop: SphinxQL'); } catch (\Foolz\SphinxQL\Exception\DatabaseException $e) { $this->logger->error('Search Error: ' . $e->getMessage()); throw new SearchInvalidException(_i('The search backend returned an error.')); } // no results found if (!count($search)) { $this->comments_unsorted = []; $this->comments = []; throw new SearchEmptyResultException(_i('No results found.')); } $sphinx_meta = Helper::pairsToAssoc(Helper::create($conn)->showMeta()->execute()); $this->total_count = $sphinx_meta['total']; $this->total_found = $sphinx_meta['total_found']; // populate sql array for full records $sql = []; foreach ($search as $doc => $result) { $board = $this->radix_coll->getById($result['board']); if ($input['results'] !== null && $input['results'] == 'thread') { $post = 'num = ' . $this->dc->getConnection()->quote($result['tnum']) . ' AND subnum = 0'; } else { $post = 'doc_id = ' . $this->dc->getConnection()->quote($result['id']); } $sql[] = $this->dc->qb()->select('*, ' . $result['board'] . ' AS board_id')->from($board->getTable(), 'r')->leftJoin('r', $board->getTable('_images'), 'mg', 'mg.media_id = r.media_id')->where($post)->getSQL(); } $result = $this->dc->getConnection()->executeQuery(implode(' UNION ', $sql))->fetchAll(); // no results found IN DATABASE, but we might still get a search count from Sphinx if (!count($result)) { $this->comments_unsorted = []; $this->comments = []; } else { // process results foreach ($result as $key => $row) { $board = $this->radix !== null ? $this->radix : $this->radix_coll->getById($row['board_id']); $bulk = new CommentBulk(); $bulk->import($row, $board); $this->comments_unsorted[] = $bulk; unset($result[$key]); } } $this->comments[0]['posts'] = $this->comments_unsorted; return $this; }
/** * 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; }