public function before() { $request = $this->getRequest(); $this->uri = $this->getContext()->getService('uri'); $this->mailer = $this->getContext()->getService('mailer'); $this->notices = $this->getContext()->getService('notices'); $this->preferences = $this->getContext()->getService('preferences'); $this->config = $this->getContext()->getService('config'); $this->security = $this->getContext()->getService('security'); $theme_instance = \Foolz\Theme\Loader::forge('foolframe_admin'); $theme_instance->addDir(VENDPATH . 'foolz/foolframe/assets/themes-admin/'); $theme_instance->addDir(VAPPPATH . 'foolz/foolframe/themes-admin/'); $theme_instance->setBaseUrl($this->uri->base() . 'foolframe/'); $theme_instance->setPublicDir(DOCROOT . 'foolframe/'); // make it possible to override the theme so other framework components can extend with their own $this->setupTheme($theme_instance); $this->builder = $this->theme->createBuilder(); $this->param_manager = $this->builder->getParamManager(); $this->builder->createLayout('base'); $this->builder->getProps()->addTitle(_i('Control Panel') . ' - ' . $this->preferences->get('foolframe.gen.website_title')); $this->param_manager->setParams(['context' => $this->getContext(), 'notices' => $this->notices, 'uri' => $this->uri, 'request' => $request]); // returns the hardcoded sidebar array (can't use functions when declaring a class variable) $sidebar = $this->getSidebarValues(); $sidebar_dynamic = Hook::forge('Foolz\\FoolFrame\\Controller\\Admin::before#var.sidebar')->setObject($this)->setParam('sidebar', [])->execute()->getParam('sidebar'); // merge if there were sidebar elements added dynamically if (!empty($sidebar_dynamic)) { $sidebar = $this->mergeSidebars($sidebar, $sidebar_dynamic); } $this->builder->createPartial('navbar', 'navbar'); $this->builder->createPartial('sidebar', 'sidebar')->getParamManager()->setParams(['sidebar' => $this->getSidebar($request, $sidebar)]); }
public function getAvailableStats() { $stats = $this->getStats(); // this variable is going to be a serialized array $enabled = $this->preferences->get('foolslide.plugins.board_statistics.enabled'); if (!$enabled) { return array(); } $enabled = unserialize($enabled); foreach ($stats as $k => $s) { if (!$enabled[$k]) { unset($stats[$k]); } } return $stats; }
public function before() { $this->config = $this->getContext()->getService('config'); $this->preferences = $this->getContext()->getService('preferences'); $this->uri = $this->getContext()->getService('uri'); $this->profiler = $this->getContext()->getService('profiler'); $this->radix_coll = $this->getContext()->getService('foolslide.radix_collection'); $this->media_factory = $this->getContext()->getService('foolslide.media_factory'); $this->comment_factory = $this->getContext()->getService('foolslide.comment_factory'); $this->ban_factory = $this->getContext()->getService('foolslide.ban_factory'); $this->report_coll = $this->getContext()->getService('foolslide.report_collection'); // this has already been forged in the foolslide bootstrap $theme_instance = \Foolz\Theme\Loader::forge('foolslide'); if ($this->getQuery('theme')) { try { $theme_name = $this->getQuery('theme', $this->getCookie('theme')) ?: $this->preferences->get('foolslide.theme.default'); $theme = $theme_instance->get($theme_name); if (!isset($theme->enabled) || !$theme->enabled) { throw new \OutOfBoundsException(); } $this->theme = $theme; } catch (\OutOfBoundsException $e) { $theme_name = 'foolz/foolslide-theme-foolslide'; $this->theme = $theme_instance->get($theme_name); } $this->builder = $this->theme->createBuilder(); $this->builder->getParamManager()->setParams(['context' => $this->getContext(), 'request' => $this->getRequest()]); } // convenience objects for saving some RAM $this->comment_obj = new Comment($this->getContext()); $this->media_obj = new Media($this->getContext()); }
/** * @param string $username * @param string $password * @param string $email * @param int $group * @param array $profile_fields * @return array|bool * @throws UpdateException */ public function createUser($username, $password, $email, $group = 1, array $profile_fields = array()) { $password = trim($password); $email = filter_var(trim($email), FILTER_VALIDATE_EMAIL); if (empty($username) || empty($password) || empty($email)) { throw new UpdateException('Username, password and email address can\'t be empty.', 1); } $same_users = $this->dc->qb()->select('*')->from($this->dc->p('users'), 'l')->where('l.username = :username')->orWhere('l.email = :email')->setParameter(':username', $username)->setParameter(':email', $email)->execute()->fetchAll(); if (count($same_users) > 0) { if (in_array(strtolower($email), array_map('strtolower', current($same_users)))) { throw new UpdateException('Email address already exists', 2); } else { throw new UpdateException('Username already exists', 3); } } $activated = (bool) $this->preferences->get('foolframe.auth.disable_registration_email'); $activation_key = null; $activation_key_hashed = null; if (!$activated) { // get a string for validation email $activation_key = sha1(uniqid() . time()); $activation_key_hashed = $this->passwordHash($activation_key); if (!$activation_key_hashed) { throw new UpdateException('Issues hashing the activation key', 4); } } $hashed_password = $this->passwordHash($password); if (!$hashed_password) { throw new UpdateException('Issues hashing the password', 4); } $user = array('username' => (string) $username, 'password' => $hashed_password, 'email' => $email, 'group_id' => (int) $group, 'activated' => (int) $activated, 'activation_key' => $activation_key_hashed, 'profile_fields' => serialize($profile_fields), 'created_at' => time()); $result = $this->dc->getConnection()->insert($this->dc->p('users'), $user); return $result ? array($this->dc->getConnection()->lastInsertId($this->dc->p('users_id_seq')), $activation_key) : false; }
public function router($method, $parameters) { $request = $this->getRequest(); $this->request = $request; $this->response = new Response(); $this->builder->getProps()->addTitle($this->preferences->get('foolframe.gen.website_title', $this->preferences->get('foolslide.gen.website_title'))); if (method_exists($this, 'action_' . $method)) { return [$this, 'action_' . $method, $parameters]; } return [$this, 'action_404', []]; }
public function parseUrls() { $text = $this->preferences->get('foolslide.plugins.nginx_cache_purge.urls'); if (!$text) { return []; } $lines = preg_split('/\\r\\n|\\r|\\n/', $text); $lines_exploded = []; foreach ($lines as $key => $line) { $explode = explode('::', $line); if (count($explode) == 0) { continue; } if (count($explode) >= 1) { $lines_exploded[$key]['url'] = rtrim(array_shift($explode), '/'); } if (count($explode) >= 1) { $lines_exploded[$key]['user'] = array_shift($explode); $lines_exploded[$key]['pass'] = array_shift($explode); } } return $lines_exploded; }
/** * Maintenance function to remove leftover _removed folders * * @param boolean $echo echo CLI output * * @return boolean true on success, false on failure */ public function removeLeftoverDirs($echo = false) { $all = $this->getAll(); $array = []; // get all directories if ($handle = opendir($this->preferences->get('foolfuuka.boards.directory'))) { while (false !== ($file = readdir($handle))) { if (in_array($file, ['..', '.'])) { continue; } if (is_dir($this->preferences->get('foolfuuka.boards.directory') . '/' . $file)) { $array[] = $file; } } closedir($handle); } else { return false; } // make sure it's a removed folder foreach ($array as $key => $dir) { if (strpos($dir, '_removed') === false) { unset($array[$key]); } foreach ($all as $a) { if ($a->shortname === $dir) { unset($array[$key]); } } } // exec the deletion foreach ($array as $dir) { $cmd = 'rm -Rv ' . $this->preferences->get('foolfuuka.boards.directory') . '/' . $dir; if ($echo) { echo $cmd . PHP_EOL; passthru($cmd); echo PHP_EOL; } else { exec($cmd) . PHP_EOL; } } return true; }
/** * Creates a path for the file * * @param boolean $thumbnail If the path should be for a thumbnail * @param boolean $is_op If the path should be for an OP * @param boolean $with_filename If the path should include the filename * * @return string The path */ public function p_pathFromFilename($thumbnail = false, $is_op = false, $with_filename = false) { $dir = $this->preferences->get('foolfuuka.boards.directory') . '/' . $this->radix->shortname . '/' . ($thumbnail ? 'thumb' : 'image') . '/'; // we first check if we have media/preview_op/preview_reply available to reuse the value if ($thumbnail) { if ($is_op && $this->media->preview_op !== null) { return $dir . '/' . substr($this->media->preview_op, 0, 4) . '/' . substr($this->media->preview_op, 4, 2) . '/' . ($with_filename ? $this->media->preview_op : ''); } elseif (!$is_op && $this->media->preview_reply !== null) { return $dir . '/' . substr($this->media->preview_reply, 0, 4) . '/' . substr($this->media->preview_reply, 4, 2) . '/' . ($with_filename ? $this->media->preview_reply : ''); } // we didn't have media/preview_op/preview_reply so fallback to making a new file return $dir . '/' . substr($this->media->preview_orig, 0, 4) . '/' . substr($this->media->preview_orig, 4, 2) . '/' . ($with_filename ? $this->media->preview_orig : ''); } else { if ($this->media->media !== null) { return $dir . '/' . substr($this->media->media, 0, 4) . '/' . substr($this->media->media, 4, 2) . '/' . ($with_filename ? $this->media->media : ''); } // we didn't have media/preview_op/preview_reply so fallback to making a new file return $dir . '/' . substr($this->media->media_orig, 0, 4) . '/' . substr($this->media->media_orig, 4, 2) . '/' . ($with_filename ? $this->media->media_orig : ''); } }
public function radix_search() { if ($this->getPost('submit_search_global')) { $this->radix = null; } $text = $this->getPost('text'); if ($this->radix !== null && $this->getPost('submit_post')) { return $this->radix_post(str_replace(',', '_', $text)); } $this->response = new StreamedResponse(); // Check all allowed search modifiers and apply only these $modifiers = ['boards', 'subject', 'text', 'username', 'tripcode', 'email', 'filename', 'capcode', 'uid', 'image', 'deleted', 'ghost', 'type', 'filter', 'start', 'end', 'order', 'page']; if ($this->getAuth()->hasAccess('comment.see_ip')) { $modifiers[] = 'poster_ip'; $modifiers[] = 'deletion_mode'; } // GET -> URL Redirection to provide URL presentable for sharing links. if ($this->getPost()) { if ($this->radix !== null) { $redirect_url = [$this->radix->shortname, 'search']; } else { $redirect_url = ['_', 'search']; } foreach ($modifiers as $modifier) { if ($this->getPost($modifier)) { if ($modifier === 'image') { array_push($redirect_url, $modifier); array_push($redirect_url, rawurlencode(Media::urlsafe_b64encode(Media::urlsafe_b64decode($this->getPost($modifier))))); } elseif ($modifier === 'boards') { if ($this->getPost('submit_search_global')) { } elseif (count($this->getPost($modifier)) == 1) { $boards = $this->getPost($modifier); $redirect_url[0] = $boards[0]; } elseif (count($this->getPost($modifier)) > 1) { $redirect_url[0] = '_'; // avoid setting this if we're just searching on all the boards $sphinx_boards = []; foreach ($this->radix_coll->getAll() as $k => $b) { if ($b->sphinx) { $sphinx_boards[] = $b; } } if (count($sphinx_boards) !== count($this->getPost($modifier))) { array_push($redirect_url, $modifier); array_push($redirect_url, rawurlencode(implode('.', $this->getPost($modifier)))); } } } else { array_push($redirect_url, $modifier); array_push($redirect_url, rawurlencode($this->getPost($modifier))); } } } return new RedirectResponse($this->uri->create($redirect_url), 303); } $search = $this->uri->uri_to_assoc($this->request->getPathInfo(), 1, $modifiers); $this->param_manager->setParam('search', $search); // latest searches system if (!is_array($cookie_array = @json_decode($this->getCookie('search_latest_5'), true))) { $cookie_array = []; } // sanitize foreach ($cookie_array as $item) { // all subitems must be array, all must have 'board' if (!is_array($item) || !isset($item['board'])) { $cookie_array = []; break; } } $search_opts = array_filter($search); $search_opts['board'] = $this->radix !== null ? $this->radix->shortname : false; unset($search_opts['page']); // if it's already in the latest searches, remove the previous entry foreach ($cookie_array as $key => $item) { if ($item === $search_opts) { unset($cookie_array[$key]); break; } } // we don't want more than 5 entries for latest searches if (count($cookie_array) > 4) { array_pop($cookie_array); } array_unshift($cookie_array, $search_opts); $this->builder->getPartial('tools_search')->getParamManager()->setParam('latest_searches', $cookie_array); $this->response->headers->setCookie(new Cookie($this->getContext(), 'search_latest_5', json_encode($cookie_array), 60 * 60 * 24 * 30)); foreach ($search as $key => $value) { if ($value !== null) { $search[$key] = trim(rawurldecode($value)); } } if ($search['boards'] !== null) { $search['boards'] = explode('.', $search['boards']); } if ($search['image'] !== null) { $search['image'] = base64_encode(Media::urlsafe_b64decode($search['image'])); } if ($this->getAuth()->hasAccess('comment.see_ip') && $search['poster_ip'] !== null) { if (!filter_var($search['poster_ip'], FILTER_VALIDATE_IP)) { return $this->error(_i('The poster IP you inserted is not a valid IP address.')); } $search['poster_ip'] = Inet::ptod($search['poster_ip']); } try { $board = Search::forge($this->getContext())->getSearch($search)->setRadix($this->radix)->setPage($search['page'] ? $search['page'] : 1); $board->getComments(); } catch (\Foolz\Foolfuuka\Model\SearchException $e) { return $this->error($e->getMessage()); } catch (\Foolz\Foolfuuka\Model\BoardException $e) { return $this->error($e->getMessage()); } catch (\Foolz\SphinxQL\ConnectionException $e) { return $this->error($this->preferences->get('foolfuuka.sphinx.custom_message', 'It appears that the search engine is offline at the moment. Please try again later.')); } // Generate the $title with all search modifiers enabled. $title = []; if ($search['text']) { array_push($title, sprintf(_i('that contain ‘%s’'), e($search['text']))); } if ($search['subject']) { array_push($title, sprintf(_i('with the subject ‘%s’'), e($search['subject']))); } if ($search['username']) { array_push($title, sprintf(_i('with the username ‘%s’'), e($search['username']))); } if ($search['tripcode']) { array_push($title, sprintf(_i('with the tripcode ‘%s’'), e($search['tripcode']))); } if ($search['filename']) { array_push($title, sprintf(_i('with the filename ‘%s’'), e($search['filename']))); } if ($search['image']) { array_push($title, sprintf(_i('with the image hash ‘%s’'), e($search['image']))); } if ($search['deleted'] == 'deleted') { array_push($title, _i('that have been deleted')); } if ($search['deleted'] == 'not-deleted') { array_push($title, _i('that has not been deleted')); } if ($search['ghost'] == 'only') { array_push($title, _i('that are by ghosts')); } if ($search['ghost'] == 'none') { array_push($title, _i('that are not by ghosts')); } if ($search['type'] == 'sticky') { array_push($title, _i('that were stickied')); } if ($search['type'] == 'op') { array_push($title, _i('that are only OP posts')); } if ($search['type'] == 'posts') { array_push($title, _i('that are only non-OP posts')); } if ($search['filter'] == 'image') { array_push($title, _i('that do not contain images')); } if ($search['filter'] == 'text') { array_push($title, _i('that only contain images')); } if ($search['capcode'] == 'user') { array_push($title, _i('that were made by users')); } if ($search['capcode'] == 'mod') { array_push($title, _i('that were made by mods')); } if ($search['capcode'] == 'admin') { array_push($title, _i('that were made by admins')); } if ($search['start']) { array_push($title, sprintf(_i('posts after %s'), e($search['start']))); } if ($search['end']) { array_push($title, sprintf(_i('posts before %s'), e($search['end']))); } if ($search['order'] == 'asc') { array_push($title, _i('in ascending order')); } if (!empty($title)) { $title = sprintf(_i('Searching for posts %s.'), implode(' ' . _i('and') . ' ', $title)); } else { $title = _i('Displaying all posts with no filters applied.'); } if ($this->radix) { $this->builder->getProps()->addTitle($title); } else { $this->builder->getProps()->addTitle('Global Search » ' . $title); } if ($board->getSearchCount() > 5000) { $search_title = sprintf(_i('%s <small>Returning only first %d of %d results found.</small>', $title, $this->preferences->get('foolfuuka.sphinx.max_matches', 5000), $board->getSearchCount())); } else { $search_title = sprintf(_i('%s <small>%d results found.</small>', $title, $board->getSearchCount())); } $this->param_manager->setParam('section_title', $search_title); $main_partial = $this->builder->createPartial('body', 'board'); $main_partial->getParamManager()->setParam('board', $board->getComments()); $pagination = $search; unset($pagination['page']); $pagination_arr = []; $pagination_arr[] = $this->radix !== null ? $this->radix->shortname : '_'; $pagination_arr[] = 'search'; foreach ($pagination as $key => $item) { if ($item || $item === 0) { $pagination_arr[] = rawurlencode($key); if (is_array($item)) { $item = implode('.', $item); } if ($key == 'poster_ip') { $item = Inet::dtop($item); } $pagination_arr[] = rawurlencode($item); } } $pagination_arr[] = 'page'; $this->param_manager->setParam('pagination', ['base_url' => $this->uri->create($pagination_arr), 'current_page' => $search['page'] ?: 1, 'total' => ceil($board->getCount() / 25)]); $this->param_manager->setParam('modifiers', ['post_show_board_name' => $this->radix === null, 'post_show_view_button' => true]); $this->profiler->logMem('Controller Chan $this', $this); $this->profiler->log('Controller Chan::search End'); $this->response->setCallback(function () { $this->builder->stream(); }); return $this->response; }
/** * 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; }
public function handleWeb(Request $request = null) { if ($request === null) { // create the request from the globals if we don't have custom input $request = Request::createFromGlobals(); } $this->container->register('uri', '\\Foolz\\FoolFrame\\Model\\Uri')->addArgument($this)->addArgument($request); $remember_me = $request->cookies->get($this->config->get('foolz/foolframe', 'config', 'config.cookie_prefix') . 'rememberme'); if (!count($this->child_contextes)) { // no app installed, we need to go to the install $this->loadInstallRoutes($this->route_collection); } else { $this->profiler->log('Start Auth rememberme'); /** @var Auth $auth */ $auth = $this->getService('auth'); if ($remember_me) { try { $auth->authenticateWithRememberMe($remember_me); } catch (WrongKeyException $e) { } } $this->profiler->log('Stop Auth rememberme'); Hook::forge('Foolz\\FoolFrame\\Model\\Context::handleWeb#obj.afterAuth')->setObject($this)->setParam('route_collection', $this->route_collection)->execute(); $this->profiler->log('Start Plugins handleWeb'); $this->getService('plugins')->handleWeb(); $this->profiler->log('Stop Plugins handleWeb'); $this->profiler->log('Start language setup'); $available_langs = $this->config->get('foolz/foolframe', 'package', 'preferences.lang.available'); $lang = $request->cookies->get('language'); if (!$lang || !array_key_exists($lang, $available_langs)) { $lang = $this->preferences->get('foolframe.lang.default'); } // HHVM does not support gettext if (function_exists('bindtextdomain')) { $locale = $lang . '.utf8'; putenv('LANG=' . $locale); putenv('LANGUAGE=' . $locale); setlocale(LC_ALL, $locale); bindtextdomain($lang, DOCROOT . 'assets/locale'); bind_textdomain_codeset($lang, 'UTF-8'); textdomain($lang); } $this->profiler->log('Stop language setup'); $this->profiler->log('Start routes setup'); // load the routes from the child contextes first Hook::forge('Foolz\\FoolFrame\\Model\\Context::handleWeb#obj.routing')->setObject($this)->setParam('route_collection', $this->route_collection)->execute(); foreach ($this->child_contextes as $context) { $context->handleWeb($request); $context->loadRoutes($this->route_collection); } $this->profiler->log('Stop routes setup'); $this->profiler->log('Start routes load'); $this->loadRoutes($this->route_collection); $this->profiler->log('Stop routes setup'); } // load the framework routes Hook::forge('Foolz\\FoolFrame\\Model\\Context::handleWeb#obj.context')->setObject($this)->execute(); // this is the first time we know we have a request for sure // hooks that need the request to function must run here Hook::forge('Foolz\\FoolFrame\\Model\\Context::handleWeb#obj.request')->setObject($this)->setParam('request', $request)->execute(); $this->container->register('notices', 'Foolz\\FoolFrame\\Model\\Notices')->addArgument($this)->addArgument($request); $this->profiler->log('Start HttpKernel loading'); $request_context = new RequestContext(); $request_context->fromRequest($request); $matcher = new UrlMatcher($this->route_collection, $request_context); $resolver = new ControllerResolver($this); $dispatcher = new EventDispatcher(); $dispatcher->addSubscriber(new RouterListener($matcher, null, $this->logger)); $dispatcher->addSubscriber(new ResponseListener('UTF-8')); $this->http_kernel = new HttpKernel($dispatcher, $resolver); $this->profiler->log('End HttpKernel loading'); // if this hook is used, it can override the entirety of the request handling $response = Hook::forge('Foolz\\FoolFrame\\Model\\Context::handleWeb#obj.response')->setObject($this)->setParam('request', $request)->execute()->get(false); if (!$response) { try { $this->profiler->log('Request handling start'); $response = $this->http_kernel->handle($request); $this->profiler->log('Request handling end'); } catch (NotFoundHttpException $e) { $controller_404 = $this->route_collection->get('404')->getDefault('_controller'); $request = new Request(); $request->attributes->add(['_controller' => $controller_404]); $response = $this->http_kernel->handle($request); } // stick the html of the profiler at the end if ($request->getRequestFormat() == 'html' && isset($auth) && $auth->hasAccess('maccess.admin')) { $content = explode('</body>', $response->getContent()); if (count($content) == 2) { $this->profiler->log('Execution end'); $response->setContent($content[0] . $this->profiler->getHtml() . '</body>' . $content[1]); } } } $this->getService('security')->updateCsrfToken($response); $response->send(); }
/** * 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; }
/** * Creates the tables for the board */ public function createTables() { $config = $this->config->get('foolz/foolframe', 'db', 'default'); $config['dbname'] = $this->preferences->get('foolfuuka.boards.db') ?: $config['dbname']; $config['prefix'] = $this->preferences->get("foolfuuka.boards.prefix"); $conn = new DoctrineConnection($this->getContext(), $config); $charset = 'utf8mb4'; $collate = 'utf8mb4_general_ci'; $sm = $conn->getConnection()->getSchemaManager(); $schema = $sm->createSchema(); // create the main table also in _deleted flavour foreach (['', '_deleted'] as $key) { if (!$schema->hasTable($this->getPrefix() . $this->shortname . $key)) { $table = $schema->createTable($this->getPrefix() . $this->shortname . $key); if ($conn->getConnection()->getDriver()->getName() == 'pdo_mysql') { $table->addOption('charset', $charset); $table->addOption('collate', $collate); } $table->addColumn('doc_id', 'integer', ['unsigned' => true, 'autoincrement' => true]); $table->addColumn('media_id', 'integer', ['unsigned' => true, 'default' => 0]); $table->addColumn('poster_ip', 'decimal', ['unsigned' => true, 'precision' => 39, 'scale' => 0, 'default' => 0]); $table->addColumn('num', 'integer', ['unsigned' => true]); $table->addColumn('subnum', 'integer', ['unsigned' => true]); $table->addColumn('thread_num', 'integer', ['unsigned' => true, 'default' => 0]); $table->addColumn('op', 'boolean', ['default' => 0]); $table->addColumn('timestamp', 'integer', ['unsigned' => true]); $table->addColumn('timestamp_expired', 'integer', ['unsigned' => true]); $table->addColumn('preview_orig', 'string', ['length' => 20, 'notnull' => false]); $table->addColumn('preview_w', 'smallint', ['unsigned' => true, 'default' => 0]); $table->addColumn('preview_h', 'smallint', ['unsigned' => true, 'default' => 0]); $table->addColumn('media_filename', 'text', ['length' => 65532, 'notnull' => false]); $table->addColumn('media_w', 'smallint', ['unsigned' => true, 'default' => 0]); $table->addColumn('media_h', 'smallint', ['unsigned' => true, 'default' => 0]); $table->addColumn('media_size', 'integer', ['unsigned' => true, 'default' => 0]); $table->addColumn('media_hash', 'string', ['length' => 25, 'notnull' => false]); $table->addColumn('media_orig', 'string', ['length' => 20, 'notnull' => false]); $table->addColumn('spoiler', 'boolean', ['default' => 0]); $table->addColumn('deleted', 'boolean', ['default' => 0]); $table->addColumn('capcode', 'string', ['length' => 1, 'default' => 'N']); $table->addColumn('email', 'string', ['length' => 100, 'notnull' => false]); $table->addColumn('name', 'string', ['length' => 100, 'notnull' => false]); $table->addColumn('trip', 'string', ['length' => 25, 'notnull' => false]); $table->addColumn('title', 'string', ['length' => 100, 'notnull' => false]); $table->addColumn('comment', 'text', ['length' => 65532, 'notnull' => false]); $table->addColumn('delpass', 'text', ['length' => 255, 'notnull' => false]); $table->addColumn('sticky', 'boolean', ['default' => 0]); $table->addColumn('locked', 'boolean', ['default' => 0]); $table->addColumn('poster_hash', 'string', ['length' => 8, 'notnull' => false]); $table->addColumn('poster_country', 'string', ['length' => 2, 'notnull' => false]); $table->addColumn('exif', 'text', ['length' => 65532, 'notnull' => false]); $table->setPrimaryKey(['doc_id']); $table->addUniqueIndex(['num', 'subnum'], $this->getIndex($key, 'num_subnum_index')); $table->addIndex(['thread_num', 'num', 'subnum'], $this->getIndex($key, 'thread_num_subnum_index')); $table->addIndex(['subnum'], $this->getIndex($key, 'subnum_index')); $table->addIndex(['op'], $this->getIndex($key, 'op_index')); $table->addIndex(['media_id'], $this->getIndex($key, 'media_id_index')); $table->addIndex(['media_hash'], $this->getIndex($key, 'media_hash_index')); $table->addIndex(['media_orig'], $this->getIndex($key, 'media_orig_index')); $table->addIndex(['name', 'trip'], $this->getIndex($key, 'name_trip_index')); $table->addIndex(['trip'], $this->getIndex($key, 'trip_index')); $table->addIndex(['email'], $this->getIndex($key, 'email_index')); $table->addIndex(['poster_ip'], $this->getIndex($key, 'poster_ip_index')); $table->addIndex(['timestamp'], $this->getIndex($key, 'timestamp_index')); } } if (!$schema->hasTable($this->getPrefix() . $this->shortname . '_threads')) { $table_threads = $schema->createTable($this->getPrefix() . $this->shortname . '_threads'); if ($conn->getConnection()->getDriver()->getName() == 'pdo_mysql') { $table_threads->addOption('charset', $charset); $table_threads->addOption('collate', $collate); } $table_threads->addColumn('thread_num', 'integer', ['unsigned' => true]); $table_threads->addColumn('time_op', 'integer', ['unsigned' => true]); $table_threads->addColumn('time_last', 'integer', ['unsigned' => true]); $table_threads->addColumn('time_bump', 'integer', ['unsigned' => true]); $table_threads->addColumn('time_ghost', 'integer', ['unsigned' => true, 'notnull' => false, 'default' => 'null']); $table_threads->addColumn('time_ghost_bump', 'integer', ['unsigned' => true, 'notnull' => false, 'default' => 'null']); $table_threads->addColumn('time_last_modified', 'integer', ['unsigned' => true]); $table_threads->addColumn('nreplies', 'integer', ['unsigned' => true, 'default' => 0]); $table_threads->addColumn('nimages', 'integer', ['unsigned' => true, 'default' => 0]); $table_threads->addColumn('sticky', 'boolean', ['default' => 0]); $table_threads->addColumn('locked', 'boolean', ['default' => 0]); $table_threads->setPrimaryKey(['thread_num']); $table_threads->addIndex(['locked'], $this->getIndex('_threads', 'locked_index')); $table_threads->addIndex(['time_op'], $this->getIndex('_threads', 'time_op_index')); $table_threads->addIndex(['time_bump'], $this->getIndex('_threads', 'time_bump_index')); $table_threads->addIndex(['time_ghost_bump'], $this->getIndex('_threads', 'time_ghost_bump_index')); $table_threads->addIndex(['sticky'], $this->getIndex('_threads', 'sticky_index')); $table_threads->addIndex(['sticky', 'time_bump'], $this->getIndex('_threads', 'sticky_time_bump_index')); $table_threads->addIndex(['sticky', 'time_ghost_bump'], $this->getIndex('_threads', 'sticky_time_ghost_bump_index')); $table_threads->addIndex(['sticky', 'thread_num'], $this->getIndex('_threads', 'sticky_thread_num_index')); } if (!$schema->hasTable($this->getPrefix() . $this->shortname . '_users')) { $table_users = $schema->createTable($this->getPrefix() . $this->shortname . '_users'); if ($conn->getConnection()->getDriver()->getName() == 'pdo_mysql') { $table_users->addOption('charset', $charset); $table_users->addOption('collate', $collate); } $table_users->addColumn('user_id', 'integer', ['unsigned' => true, 'autoincrement' => true]); $table_users->addColumn('name', 'string', ['length' => 100, 'default' => '']); $table_users->addColumn('trip', 'string', ['length' => 25, 'default' => '']); $table_users->addColumn('firstseen', 'integer', ['unsigned' => true]); $table_users->addColumn('postcount', 'integer', ['unsigned' => true]); $table_users->setPrimaryKey(['user_id']); $table_users->addUniqueIndex(['name', 'trip'], $this->getIndex('_users', 'name_trip_index')); $table_users->addIndex(['firstseen'], $this->getIndex('_users', 'firstseen_index')); $table_users->addIndex(['postcount'], $this->getIndex('_users', 'postcount_index')); } if (!$schema->hasTable($this->getPrefix() . $this->shortname . '_images')) { $table_images = $schema->createTable($this->getPrefix() . $this->shortname . '_images'); if ($conn->getConnection()->getDriver()->getName() == 'pdo_mysql') { $table_images->addOption('charset', 'utf8'); $table_images->addOption('collate', 'utf8_general_ci'); } $table_images->addColumn('media_id', 'integer', ['unsigned' => true, 'autoincrement' => true]); $table_images->addColumn('media_hash', 'string', ['length' => 25]); $table_images->addColumn('media', 'string', ['length' => 20, 'notnull' => false]); $table_images->addColumn('preview_op', 'string', ['length' => 20, 'notnull' => false]); $table_images->addColumn('preview_reply', 'string', ['length' => 20, 'notnull' => false]); $table_images->addColumn('total', 'integer', ['unsigned' => true, 'default' => 0]); $table_images->addColumn('banned', 'smallint', ['unsigned' => true, 'default' => 0]); $table_images->setPrimaryKey(['media_id']); $table_images->addUniqueIndex(['media_hash'], $this->getIndex('_images', 'media_hash_index')); $table_images->addIndex(['total'], $this->getIndex('_images', 'total_index')); $table_images->addIndex(['banned'], $this->getIndex('_images', 'banned_index')); } if (!$schema->hasTable($this->getPrefix() . $this->shortname . '_daily')) { $table_daily = $schema->createTable($this->getPrefix() . $this->shortname . '_daily'); if ($conn->getConnection()->getDriver()->getName() == 'pdo_mysql') { $table_daily->addOption('charset', 'utf8'); $table_daily->addOption('collate', 'utf8_general_ci'); } $table_daily->addColumn('day', 'integer', ['unsigned' => true]); $table_daily->addColumn('posts', 'integer', ['unsigned' => true]); $table_daily->addColumn('images', 'integer', ['unsigned' => true]); $table_daily->addColumn('sage', 'integer', ['unsigned' => true]); $table_daily->addColumn('anons', 'integer', ['unsigned' => true]); $table_daily->addColumn('trips', 'integer', ['unsigned' => true]); $table_daily->addColumn('names', 'integer', ['unsigned' => true]); $table_daily->setPrimaryKey(['day']); } $conn->getConnection()->beginTransaction(); foreach ($schema->getMigrateFromSql($sm->createSchema(), $sm->getDatabasePlatform()) as $query) { $conn->getConnection()->query($query); } $md5_array = $this->dc->qb()->select('md5')->from($this->dc->p('banned_md5'), 'm')->execute()->fetchAll(); // in a transaction multiple inserts are almost like a single one foreach ($md5_array as $item) { $conn->getConnection()->insert($this->getTable('_images'), ['md5' => $item['md5'], 'banned' => 1]); } $conn->getConnection()->commit(); }