Example #1
0
 public function __construct(Context $context)
 {
     parent::__construct($context);
     $this->config = $context->getService('config');
     $config = $this->config->get('foolz/foolframe', 'swiftmailer');
     switch ($config['transport']) {
         case 'smtp':
             $transport = Swift_SmtpTransport::newInstance()->setHost($config['host'])->setPort($config['port'])->setUsername($config['username'])->setPassword($config['password'])->setEncryption($config['encryption'])->setAuthMode('login');
             break;
         default:
             $transport = Swift_SendmailTransport::newInstance();
     }
     $this->mailer = Swift_Mailer::newInstance($transport);
 }
Example #2
0
 /**
  * Non-dynamic sidebar array.
  * Permissions are set inside
  *
  * @return array sidebar array
  */
 private function getSidebarValues()
 {
     $sidebar = [];
     // load sidebars from modules and leave FoolFrame sidebar on bottom
     foreach ($this->config->get('foolz/foolframe', 'config', 'modules.installed') as $module) {
         $module_sidebar = $this->config->get($module['namespace'], 'sidebar');
         if (is_array($module_sidebar)) {
             $sidebar = array_merge($module_sidebar['sidebar'], $sidebar);
         }
     }
     return $sidebar;
 }
Example #3
0
 public function instantiate()
 {
     $this->loader = new Loader();
     // store all the relevant data from the modules
     foreach ($this->config->get('foolz/foolframe', 'config', 'modules.installed') as $module) {
         $dir = $this->config->get($module['namespace'], 'package', 'directories.plugins');
         $this->loader->addDir($dir);
         $dir = VAPPPATH . $module['namespace'] . '/plugins';
         $this->loader->addDir($dir);
     }
     // public dir for plugins
     $this->loader->addDir(VAPPPATH . 'foolz/foolframe/plugins/');
     foreach ($this->getEnabled() as $enabled) {
         try {
             $plugin = $this->loader->get($enabled['slug']);
             $plugin->bootstrap();
             // we could use execute() but we want to inject more in the call
             \Foolz\Plugin\Hook::forge('Foolz\\Plugin\\Plugin::execute#' . $plugin->getConfig('name'))->setObject($plugin)->setParam('context', $this->getContext())->execute();
             $this->loader->get($enabled['slug'])->enabled = true;
         } catch (\OutOfBoundsException $e) {
         }
     }
 }
Example #4
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('foolfuuka.radix_collection');
     $this->media_factory = $this->getContext()->getService('foolfuuka.media_factory');
     $this->comment_factory = $this->getContext()->getService('foolfuuka.comment_factory');
     // this has already been forged in the foolfuuka bootstrap
     $theme_instance = \Foolz\Theme\Loader::forge('foolfuuka');
     try {
         $theme_name = $this->getQuery('theme', $this->getCookie('theme')) ?: $this->preferences->get('foolfuuka.theme.default');
         $theme_name_exploded = explode('/', $theme_name);
         if (count($theme_name_exploded) >= 2) {
             $theme_name = $theme_name_exploded[0] . '/' . $theme_name_exploded[1];
         }
         $theme = $theme_instance->get($theme_name);
         if (!isset($theme->enabled) || !$theme->enabled) {
             throw new \OutOfBoundsException();
         }
         $this->theme = $theme;
     } catch (\OutOfBoundsException $e) {
         $theme_name = 'foolz/foolfuuka-theme-foolfuuka';
         $this->theme = $theme_instance->get('foolz/foolfuuka-theme-foolfuuka');
     }
     // TODO this is currently bootstrapped in the foolfuuka bootstrap because we need it running before the router.
     //$this->theme->bootstrap();
     $this->builder = $this->theme->createBuilder();
     $this->param_manager = $this->builder->getParamManager();
     $this->builder->createLayout('chan');
     if (count($theme_name_exploded) == 3) {
         try {
             $this->builder->setStyle($theme_name_exploded[2]);
         } catch (\OutOfBoundsException $e) {
             // just let it go with default on getStyle()
         }
     }
     $pass = $this->getCookie('reply_password', '');
     $name = $this->getCookie('reply_name');
     $email = $this->getCookie('reply_email');
     // KEEP THIS IN SYNC WITH THE ONE IN THE POSTS ADMIN PANEL
     $to_bind = ['context' => $this->getContext(), 'request' => $this->getRequest(), 'user_name' => $name, 'user_email' => $email, 'user_pass' => $pass, 'disable_headers' => false, 'is_page' => false, 'is_thread' => false, 'is_last50' => false, 'order' => false, 'modifiers' => [], 'backend_vars' => ['user_name' => $name, 'user_email' => $email, 'user_pass' => $pass, 'site_url' => $this->uri->base(), 'default_url' => $this->uri->base(), 'archive_url' => $this->uri->base(), 'system_url' => $this->uri->base(), 'api_url' => $this->uri->base(), 'cookie_domain' => $this->config->get('foolz/foolframe', 'config', 'config.cookie_domain'), 'cookie_prefix' => $this->config->get('foolz/foolframe', 'config', 'config.cookie_prefix'), 'selected_theme' => $theme_name, 'csrf_token_key' => 'csrf_token', 'images' => ['banned_image' => $this->theme->getAssetManager()->getAssetLink('images/banned-image.png'), 'banned_image_width' => 150, 'banned_image_height' => 150, 'missing_image' => $this->theme->getAssetManager()->getAssetLink('images/missing-image.jpg'), 'missing_image_width' => 150, 'missing_image_height' => 150], 'gettext' => ['submit_state' => _i('Submitting'), 'thread_is_real_time' => _i('This thread is being displayed in real time.'), 'update_now' => _i('Update now'), 'ghost_mode' => _i('This thread has entered ghost mode. Your reply will be marked as a ghost post and will only affect the ghost index.')]]];
     $this->param_manager->setParams($to_bind);
     $this->builder->createPartial('tools_modal', 'tools_modal');
     $this->builder->createPartial('tools_search', 'tools_search');
     $this->builder->createPartial('tools_advanced_search', 'advanced_search');
 }
Example #5
0
 public function get($setting, $fallback = null, $show_empty_string = false)
 {
     if (!$this->loaded) {
         $this->load();
     }
     if (isset($this->preferences[$setting]) && ($show_empty_string || $this->preferences[$setting] !== '')) {
         return $this->preferences[$setting];
     }
     if ($fallback !== null) {
         return $fallback;
     }
     $segments = explode('.', $setting);
     $identifier = array_shift($segments);
     $query = implode('.', $segments);
     return $this->config->get($this->modules[$identifier]['namespace'], 'package', 'preferences.' . $query);
 }
Example #6
0
 /**
  * @param string $username
  * @param string $password
  * @param bool $ip_decimal
  *
  * @throws WrongUsernameOrPasswordException
  * @throws LimitExceededException
  */
 public function authenticateWithUsernameAndPassword($username, $password, $ip_decimal = false)
 {
     if ($this->countAttempts($username) >= $this->config->get('foolz/foolframe', 'foolauth', 'attempts_to_lock')) {
         throw new LimitExceededException();
     }
     $user = $this->dc->qb()->select('*')->from($this->dc->p('users'), 'l')->where('l.username = :username')->setParameters([':username' => $username])->execute()->fetch();
     if (!$user) {
         throw new WrongUsernameOrPasswordException();
     }
     if (!password_verify($password, $user['password'])) {
         $this->dc->getConnection()->insert($this->dc->p('user_login_attempts'), ['username' => $username, 'ip' => $ip_decimal ?: 0, 'time' => time()]);
         throw new WrongUsernameOrPasswordException();
     }
     $this->resetAttempts($username);
     $this->user = new User($this->getContext(), $user);
 }
Example #7
0
 public function install_modules()
 {
     $this->config->addPackage('unknown', ASSETSPATH);
     $class_name = $this->config->get('unknown', 'package', 'main.class_name');
     $name_lowercase = strtolower($class_name);
     $modules = ['foolframe' => ['context' => '\\Foolz\\FoolFrame\\Model\\Context', 'namespace' => 'foolz/foolframe'], $name_lowercase => ['context' => $this->config->get('unknown', 'package', 'main.class_context'), 'namespace' => 'foolz/' . $name_lowercase]];
     $dc = new DoctrineConnection($this->getContext(), $this->config);
     $sm = SchemaManager::forge($dc->getConnection(), $dc->getPrefix());
     Schema::load($this->getContext(), $sm);
     $schema_class = '\\Foolz\\' . $class_name . '\\Model\\Schema';
     $schema_class::load($this->getContext(), $sm);
     $sm->commit();
     $this->config->set('foolz/foolframe', 'config', 'modules.installed', $modules);
     $this->config->set('foolz/foolframe', 'config', 'install.installed', true);
     $this->config->save('foolz/foolframe', 'config');
 }
Example #8
0
 /**
  * Process the secure tripcode
  *
  * @param string $plain the word to generate the secure tripcode from
  * @return string the processed secure tripcode
  */
 protected function p_processSecureTripcode($plain)
 {
     return substr(base64_encode(sha1($plain . base64_decode($this->config->get('foolz/foolfuuka', 'config', 'comment.secure_tripcode_salt')), true)), 0, 11);
 }
Example #9
0
 /**
  * Get the full URL to the media, and in case switch between multiple CDNs
  *
  * @param  boolean  $thumbnail  True if looking for a thumbnail, false for full media
  *
  * @return  null|string  Null if not available, string of the url if available
  */
 public function getLink(Request $request, $thumbnail = false, $download = false)
 {
     $before = \Foolz\Plugin\Hook::forge('Foolz\\Foolfuuka\\Model\\Media::getLink.call.before.method.body')->setObject($this)->setParams(['thumbnail' => $thumbnail])->execute()->get();
     if (!$before instanceof \Foolz\Plugin\Void) {
         return $before;
     }
     $image = null;
     if ($this->config->get('foolz/foolfuuka', 'config', 'media.filecheck') === true) {
         // locate the image
         if ($thumbnail && file_exists($this->getDir($thumbnail)) !== false) {
             if ($this->op == 1) {
                 $image = $this->media->preview_op ?: $this->media->preview_reply;
             } else {
                 $image = $this->media->preview_reply ?: $this->media->preview_op;
             }
         }
         // full image
         if (!$thumbnail && file_exists($this->getDir(false)) !== false) {
             $image = $this->media->media;
         }
         // fallback if we have the full image but not the thumbnail
         if ($thumbnail && $image === null && file_exists($this->getDir(false))) {
             $thumbnail = false;
             $image = $this->media->media;
         }
     } else {
         // locate the image
         if ($thumbnail) {
             if ($this->op == 1) {
                 $image = $this->media->preview_op ?: $this->media->preview_reply;
             } else {
                 $image = $this->media->preview_reply ?: $this->media->preview_op;
             }
         } else {
             if ($this->radix->archive && !$this->radix->getValue('archive_full_images')) {
                 return null;
             } else {
                 $image = $this->media->media;
             }
         }
     }
     if ($image !== null) {
         if ($download === true && $this->preferences->get('foolfuuka.boards.media_download_url')) {
             return $this->preferences->get('foolfuuka.boards.media_download_url') . '/' . $this->radix->shortname . '/' . ($thumbnail ? 'thumb' : 'image') . '/' . substr($image, 0, 4) . '/' . substr($image, 4, 2) . '/' . $image;
         }
         if ($request->isSecure() && $this->preferences->get('foolfuuka.boards.media_balancers_https')) {
             $balancers = $this->preferences->get('foolfuuka.boards.media_balancers_https');
         }
         if (!isset($balancers) && $this->preferences->get('foolfuuka.boards.media_balancers')) {
             $balancers = $this->preferences->get('foolfuuka.boards.media_balancers');
         }
         $media_cdn = [];
         if (isset($balancers)) {
             $media_cdn = array_filter(preg_split('/\\r\\n|\\r|\\n/', $balancers));
         }
         if (!empty($media_cdn) && $this->media->media_id > 0) {
             return $media_cdn[$this->media->media_id % count($media_cdn)] . '/' . $this->radix->shortname . '/' . ($thumbnail ? 'thumb' : 'image') . '/' . substr($image, 0, 4) . '/' . substr($image, 4, 2) . '/' . $image;
         }
         return $this->preferences->get('foolfuuka.boards.url') . '/' . $this->radix->shortname . '/' . ($thumbnail ? 'thumb' : 'image') . '/' . substr($image, 0, 4) . '/' . substr($image, 4, 2) . '/' . $image;
     }
     if ($thumbnail && $this->media->media_status === 'normal') {
         $this->media->media_status = 'not-available';
     }
     return null;
 }
Example #10
0
 /**
  * Puts the table in readily available variables
  */
 public function preload()
 {
     $this->profiler->log('Radix::preload Start');
     try {
         $result = Cache::item('foolfuuka.model.radix.preload')->get();
     } catch (\OutOfBoundsException $e) {
         $result = $this->dc->qb()->select('*')->from($this->dc->p('boards'), 'b')->orderBy('shortname', 'ASC')->execute()->fetchAll();
         Cache::item('foolfuuka.model.radix.preload')->set($result, 900);
     }
     if (!is_array($result) || empty($result)) {
         $this->preloaded_radixes = [];
         return false;
     }
     $result_object = [];
     foreach ($result as $item) {
         // don't process hidden boards
         if (!$this->getAuth()->hasAccess('boards.see_hidden') && $item['hidden'] == 1) {
             continue;
         }
         $structure = $this->structure($item);
         $result_object[$item['id']] = new Radix($this->getContext(), $this);
         // set the plain database data as keys
         foreach ($item as $k => $i) {
             $result_object[$item['id']]->{$k} = $i;
             // we set it also in the values so we can just use it from there as commodity
             $result_object[$item['id']]->setValue($k, $i);
         }
         // url values for commodity
         $result_object[$item['id']]->setValue('formatted_title', $item['name'] ? '/' . $item['shortname'] . '/ - ' . $item['name'] : '/' . $item['shortname'] . '/');
         // load the basic value of the preferences
         foreach ($structure as $key => $arr) {
             if (!isset($result_object[$item['id']]->{$key}) && isset($arr['boards_preferences'])) {
                 $result_object[$item['id']]->setValue($key, $this->config->get('foolz/foolfuuka', 'package', 'preferences.radix.' . $key, false));
             }
             foreach (['sub', 'sub_inverse'] as $sub) {
                 if (isset($arr[$sub])) {
                     foreach ($arr[$sub] as $k => $a) {
                         if (!isset($result_object[$item['id']]->{$k}) && isset($a['boards_preferences'])) {
                             $result_object[$item['id']]->setValue($k, $this->config->get('foolz/foolfuuka', 'package', 'preferences.radix.' . $k, false));
                         }
                     }
                 }
             }
         }
     }
     // load the preferences from the board_preferences table
     $this->profiler->log('Radix::load_preferences Start');
     try {
         $preferences = Cache::item('foolfuuka.model.radix.load_preferences')->get();
     } catch (\OutOfBoundsException $e) {
         $preferences = $this->dc->qb()->select('*')->from($this->dc->p('boards_preferences'), 'p')->execute()->fetchAll();
         Cache::item('foolfuuka.model.radix.load_preferences')->set($preferences, 900);
     }
     foreach ($preferences as $value) {
         // in case of leftover values, it would try instantiating a new stdClass and that would trigger error
         if (isset($result_object[$value['board_id']])) {
             $result_object[$value['board_id']]->setValue($value['name'], $value['value']);
         }
     }
     $this->preloaded_radixes = $result_object;
     $this->profiler->logMem('Radix $this->preloaded_radixes', $this->preloaded_radixes);
     $this->profiler->log('Radix::load_preferences End');
     // take them all and then filter/do whatever (we use this to split the boards through various subdomains)
     // only public is affected! admins and mods will see all boards at all the time
     $this->preloaded_radixes = \Foolz\Plugin\Hook::forge('Foolz\\Foolfuuka\\Model\\Radix::preload.result.public')->setObject($this)->setParam('preloaded_radixes', $this->preloaded_radixes)->execute()->get($this->preloaded_radixes);
     $this->profiler->log('Radix::preload End');
     $this->profiler->logMem('Radix $this->preloaded_radixes w/ preferences', $this->preloaded_radixes);
 }
Example #11
0
 /**
  * Checks that the CSRF token cookie and POST match
  *
  * @param Request $request
  * @return bool
  */
 public function checkCsrfTokenGet(Request $request)
 {
     $cookie_name = $this->config->get('foolz/foolframe', 'config', 'config.cookie_prefix') . 'csrf_token';
     return $request->cookies->get($cookie_name) !== null && $request->query->get('csrf_token') !== null && $request->cookies->get($cookie_name) === $request->query->get('csrf_token');
 }
Example #12
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 #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();
 }