Example #1
0
 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)]);
 }
Example #2
0
 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;
 }
Example #3
0
 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());
 }
Example #4
0
 /**
  * @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;
 }
Example #5
0
 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;
 }
Example #7
0
 /**
  * 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;
 }
Example #8
0
 /**
  * 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 : '');
     }
 }
Example #9
0
 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;
 }
Example #10
0
 /**
  * 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;
 }
Example #11
0
 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();
 }
Example #12
0
 /**
  * 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;
 }
Example #13
0
 /**
  * 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();
 }