Visman %!s(int64=8) %!d(string=hai) anos
pai
achega
2a8c75f613

+ 62 - 0
app/Controllers/Install.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace ForkBB\Controllers;
+
+use ForkBB\Core\Container;
+use ForkBB\Models\User;
+
+class Install
+{
+    /**
+     * Контейнер
+     * @var Container
+     */
+    protected $c;
+
+    /**
+     * Конструктор
+     * @param Container $container
+     */
+    public function __construct(Container $container)
+    {
+        $this->c = $container;
+    }
+
+    /**
+     * Маршрутиризация
+     * @return Page
+     */
+    public function routing()
+    {
+        $uri = $_SERVER['REQUEST_URI'];
+        if (($pos = strpos($uri, '?')) !== false) {
+            $uri = substr($uri, 0, $pos);
+        }
+        $uri = rawurldecode($uri);
+
+        $this->c->BASE_URL = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://'
+            . preg_replace('%:(80|443)$%', '', $_SERVER['HTTP_HOST'])
+            . substr($uri, 0, (int) strrpos($uri, '/'));
+
+        $this->c->Lang->load('common', $this->c->config['o_default_lang']);
+        $this->c->user = new User(['id' => 2, 'group_id' => $this->c->GROUP_ADMIN], $this->c);
+
+        $r = $this->c->Router;
+        $r->add('GET', '/install', 'Install:install', 'Install');
+        $r->add('POST', '/install', 'Install:installPost');
+
+        $route = $r->route($_SERVER['REQUEST_METHOD'], $uri);
+        $page = null;
+        switch ($route[0]) {
+            case $r::OK:
+                // ... 200 OK
+                list($page, $action) = explode(':', $route[1], 2);
+                $page = $this->c->$page->$action($route[2]);
+                break;
+            default:
+                $page = $this->c->Redirect->setPage('Install')->setMessage('Redirect to install');
+                break;
+        }
+        return $page;
+    }
+}

+ 7 - 8
app/Controllers/Primary.php

@@ -29,10 +29,7 @@ class Primary
      */
      */
     public function check()
     public function check()
     {
     {
-        $config = $this->c->config;
-
-        // Проверяем режим обслуживания форума
-        if ($config['o_maintenance'] && ! defined('PUN_TURN_OFF_MAINT')) { //????
+        if ($this->c->config['o_maintenance'] && ! $this->c->MAINTENANCE_OFF) {
            if (! in_array($this->c->UserCookie->id(), $this->c->admins)
            if (! in_array($this->c->UserCookie->id(), $this->c->admins)
                || ! in_array($this->c->user['id'], $this->c->admins)
                || ! in_array($this->c->user['id'], $this->c->admins)
            ) {
            ) {
@@ -40,14 +37,16 @@ class Primary
            }
            }
         }
         }
 
 
-        // Обновляем форум, если нужно
-        if (empty($config['i_fork_revision']) || $config['i_fork_revision'] < FORK_REVISION) {
+        if (empty($this->c->config['i_fork_revision'])
+            || $this->c->config['i_fork_revision'] < $this->c->FORK_REVISION
+        ) {
             header('Location: db_update.php'); //????
             header('Location: db_update.php'); //????
             exit;
             exit;
         }
         }
 
 
-        if (($banned = $this->c->CheckBans->check()) !== null) {
-            return $this->c->Ban->ban($banned);
+        $ban = $this->c->CheckBans->check();
+        if (is_array($ban)) {
+            return $this->c->Ban->ban($ban);
         }
         }
     }
     }
 }
 }

+ 1 - 1
app/Core/DB.php

@@ -132,7 +132,7 @@ class DB extends PDO
                     continue 2;
                     continue 2;
             }
             }
 
 
-            if (! isset($params[$key])) {
+            if (! isset($params[$key]) && ! array_key_exists($key, $params)) {
                 throw new PDOException("'$key': No parameter for (?$type) placeholder");
                 throw new PDOException("'$key': No parameter for (?$type) placeholder");
             }
             }
 
 

+ 12 - 9
app/Core/DB/mysql.php

@@ -228,6 +228,11 @@ class Mysql
             $this->testStr($field);
             $this->testStr($field);
             // имя и тип
             // имя и тип
             $query .= "`{$field}` " . $this->replType($data[0]);
             $query .= "`{$field}` " . $this->replType($data[0]);
+            // сравнение
+            if (isset($data[3]) && is_string($data[3])) {
+                $this->testStr($data[3]);
+                $query .= " CHARACTER SET {$charSet} COLLATE {$charSet}_{$data[3]}";
+            }
             // не NULL
             // не NULL
             if (empty($data[1])) {
             if (empty($data[1])) {
                 $query .= ' NOT NULL';
                 $query .= ' NOT NULL';
@@ -236,11 +241,6 @@ class Mysql
             if (isset($data[2])) {
             if (isset($data[2])) {
                 $query .= ' DEFAULT ' . $this->convToStr($data[2]);
                 $query .= ' DEFAULT ' . $this->convToStr($data[2]);
             }
             }
-            // сравнение
-            if (isset($data[3]) && is_string($data[3])) {
-                $this->testStr($data[3]);
-                $query .= " CHARACTER SET {$charSet} COLLATE {$charSet}_{$data[3]}";
-            }
             $query .= ', ';
             $query .= ', ';
         }
         }
         if (isset($schema['PRIMARY KEY'])) {
         if (isset($schema['PRIMARY KEY'])) {
@@ -262,7 +262,8 @@ class Mysql
             $engine = $schema['ENGINE'];
             $engine = $schema['ENGINE'];
         } else {
         } else {
             // при отсутствии типа таблицы он определяется на основании типов других таблиц в базе
             // при отсутствии типа таблицы он определяется на основании типов других таблиц в базе
-            $stmt = $this->db->query("SHOW TABLE STATUS LIKE '{$this->dbPrefix}%'");
+            $prefix = str_replace('_', '\\_', $this->dbPrefix);
+            $stmt = $this->db->query("SHOW TABLE STATUS LIKE '{$prefix}%'");
             $engine = [];
             $engine = [];
             while ($row = $stmt->fetch()) {
             while ($row = $stmt->fetch()) {
                 if (isset($engine[$row['Engine']])) {
                 if (isset($engine[$row['Engine']])) {
@@ -277,11 +278,12 @@ class Mysql
             } else {
             } else {
                 arsort($engine);
                 arsort($engine);
                 // берем тип наиболее часто встречаемый у имеющихся таблиц
                 // берем тип наиболее часто встречаемый у имеющихся таблиц
-                $engine = array_shift(array_keys($engine));
+                $engine = array_keys($engine);
+                $engine = array_shift($engine);
             }
             }
         }
         }
         $this->testStr($engine);
         $this->testStr($engine);
-		$query = rtrim($query, ', ') . ") ENGINE = {$engine} CHARACTER SET {$charSet}";
+		$query = rtrim($query, ', ') . ") ENGINE={$engine} CHARACTER SET {$charSet}";
         return $this->db->exec($query) !== false;
         return $this->db->exec($query) !== false;
     }
     }
 
 
@@ -490,7 +492,8 @@ class Mysql
     public function statistics()
     public function statistics()
     {
     {
         $this->testStr($this->dbPrefix);
         $this->testStr($this->dbPrefix);
-        $stmt = $this->db->query("SHOW TABLE STATUS LIKE '{$this->dbPrefix}%'");
+        $prefix = str_replace('_', '\\_', $this->dbPrefix);
+        $stmt = $this->db->query("SHOW TABLE STATUS LIKE '{$prefix}%'");
         $records = $size = 0;
         $records = $size = 0;
         $engine = [];
         $engine = [];
         while ($row = $stmt->fetch()) {
         while ($row = $stmt->fetch()) {

+ 0 - 65
app/Core/DBLoader.php

@@ -1,65 +0,0 @@
-<?php
-
-namespace ForkBB\Core;
-
-class DBLoader
-{
-    protected $host;
-    protected $username;
-    protected $password;
-    protected $name;
-    protected $prefix;
-    protected $connect;
-
-    public function __construct($host, $username, $password, $name, $prefix, $connect)
-    {
-        $this->host = $host;
-        $this->username = $username;
-        $this->password = $password;
-        $this->name = $name;
-        $this->prefix = $prefix;
-        $this->connect = $connect;
-    }
-
-    /**
-     * @param string $type
-     *
-     * @return \ForkBB\Core\DB\DBLayer
-     */
-    public function load($type)
-    {
-        switch ($type)
-        {
-            case 'mysql':
-                require_once __DIR__ . '/DB/mysql.php';
-                break;
-
-            case 'mysql_innodb':
-                require_once __DIR__ . '/DB/mysql_innodb.php';
-                break;
-
-            case 'mysqli':
-                require_once __DIR__ . '/DB/mysqli.php';
-                break;
-
-            case 'mysqli_innodb':
-                require_once __DIR__ . '/DB/mysqli_innodb.php';
-                break;
-
-            case 'pgsql':
-                require_once __DIR__ . '/DB/pgsql.php';
-                break;
-
-            case 'sqlite':
-                require_once __DIR__ . '/DB/sqlite.php';
-                break;
-
-            default:
-                error('\''.$type.'\' is not a valid database type. Please check settings', __FILE__, __LINE__);
-                break;
-        }
-
-        // Create the database adapter object (and open/connect to/select db)
-        return new \ForkBB\Core\DB\DBLayer($this->host, $this->username, $this->password, $this->name, $this->prefix, $this->connect);
-    }
-}

+ 62 - 0
app/Core/Func.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace ForkBB\Core;
+
+class Func
+{
+    /**
+     * Контейнер
+     * @var Container
+     */
+    protected $c;
+
+    /**
+     * Список доступных стилей
+     * @var array
+     */
+    protected $styles;
+
+    /**
+     * Список доступных языков
+     * @var array
+     */
+    protected $langs;
+
+    /**
+     * Конструктор
+     */
+    public function __construct(Container $container)
+    {
+        $this->c = $container;
+    }
+
+    /**
+     * Список доступных стилей
+     *
+     * @return array
+     */
+    public function getStyles()
+    {
+        if (empty($this->styles)) {
+            $this->styles = array_map(function($style) {
+                return str_replace([$this->c->DIR_PUBLIC . '/style/', '/style.css'], '', $style);
+            }, glob($this->c->DIR_PUBLIC . '/style/*/style.css'));
+        }
+        return $this->styles;
+    }
+
+    /**
+     * Список доступных языков
+     *
+     * @return array
+     */
+    public function getLangs()
+    {
+        if (empty($this->langs)) {
+            $this->langs = array_map(function($lang) {
+                return str_replace([$this->c->DIR_LANG . '/', '/common.po'], '', $lang);
+            }, glob($this->c->DIR_LANG . '/*/common.po'));
+        }
+        return $this->langs;
+    }
+}

+ 0 - 133
app/Core/Model.php

@@ -1,133 +0,0 @@
-<?php
-
-namespace ForkBB\Core;
-
-use \ArrayObject;
-use \ArrayIterator;
-use \InvalidArgumentException;
-
-class Model extends ArrayObject
-{
-    /**
-     * @var array
-     */
-    protected $master;
-
-    /**
-     * @var array
-     */
-    protected $current;
-
-    /**
-     * @param array $data
-     */
-    public function __construct(array $data = [])
-    {
-        $this->master = $data;
-        $this->current = $data;
-    }
-
-    /**
-     * @param int|string $key
-     *
-     * @return mixed
-     */
-    public function offsetGet($key)
-    {
-        $this->verifyKey($key);
-        if (isset($this->current[$key])) {
-            return $this->current[$key];
-        }
-    }
-
-    /**
-     * @param int|string $key
-     * @param mixed @value
-     */
-    public function offsetSet($key, $value)
-    {
-        $this->verifyKey($key, true);
-        if (null === $key) {
-            $this->current[] = $value;
-        } else {
-            $this->current[$key] = $value;
-        }
-    }
-
-    /**
-     * @param int|string $key
-     */
-    public function offsetUnset($key)
-    {
-        $this->verifyKey($key);
-        unset($this->current[$key]);
-    }
-
-    /**
-     * @param int|string $key
-     *
-     * @return bool
-     */
-    public function offsetExists($key)
-    {
-        $this->verifyKey($key);
-        return isset($this->current[$key]) || array_key_exists($key, $this->current);
-    }
-
-    /**
-     * @return ArrayIterator
-     */
-    public function getIterator()
-    {
-        return new ArrayIterator($this->current);
-    }
-
-    /**
-     * @return int
-     */
-    public function count()
-    {
-        return count($this->current);
-    }
-
-    /**
-     * @param mixed $value
-     */
-    public function append($value)
-    {
-        $this->current[] = $value;
-    }
-
-    /**
-     * @return array
-     */
-    public function export()
-    {
-        return $this->current;
-    }
-
-    /**
-     * @return bool
-     */
-    public function isModify()
-    {
-        return $this->master !== $this->current;
-    }
-
-    /**
-     * @param mixed $key
-     * @param bool $allowedNull
-     *
-     * @throw InvalidArgumentException
-     */
-    protected function verifyKey($key, $allowedNull = false)
-    {
-        if (is_string($key)
-            || is_int($key)
-            || ($allowedNull && null === $key)
-        ) {
-            return;
-        }
-        throw new InvalidArgumentException('Key should be string or integer');
-    }
-}

+ 5 - 5
app/Core/Router.php

@@ -28,7 +28,7 @@ class Router
     protected $methods = [];
     protected $methods = [];
 
 
     /**
     /**
-     * Массив для построения реальных ссылок
+     * Массив для построения ссылок
      * @var array
      * @var array
      */
      */
     protected $links = [];
     protected $links = [];
@@ -61,7 +61,7 @@ class Router
      * Конструктор
      * Конструктор
      * @param string $base
      * @param string $base
      */
      */
-    public function __construct($base = '')
+    public function __construct($base)
     {
     {
         $this->baseUrl = $base;
         $this->baseUrl = $base;
         $this->host = parse_url($base, PHP_URL_HOST);
         $this->host = parse_url($base, PHP_URL_HOST);
@@ -90,15 +90,15 @@ class Router
     }
     }
 
 
     /**
     /**
-     * Возвращает реальный url
+     * Возвращает ссылку на основании маркера
      * @param string $marker
      * @param string $marker
      * @param array $args
      * @param array $args
      * @return string
      * @return string
      */
      */
-    public function link($marker, array $args = [])
+    public function link($marker = null, array $args = [])
     {
     {
         $result = $this->baseUrl; //???? http и https
         $result = $this->baseUrl; //???? http и https
-        if (isset($this->links[$marker])) {
+        if (is_string($marker) && isset($this->links[$marker])) {
             $s = $this->links[$marker];
             $s = $this->links[$marker];
             foreach ($args as $key => $val) {
             foreach ($args as $key => $val) {
                 if ($key == '#') {
                 if ($key == '#') {

+ 3 - 3
app/Models/Actions/CacheGenerator.php

@@ -63,8 +63,8 @@ class CacheGenerator
     public function usersInfo()
     public function usersInfo()
     {
     {
         $stats = [];
         $stats = [];
-        $stats['total_users'] = $this->c->DB->query('SELECT COUNT(id)-1 FROM ::users WHERE group_id!='.PUN_UNVERIFIED)->fetchColumn();
-        $stats['last_user'] = $this->c->DB->query('SELECT id, username FROM ::users WHERE group_id!='.PUN_UNVERIFIED.' ORDER BY registered DESC LIMIT 1')->fetch();
+        $stats['total_users'] = $this->c->DB->query('SELECT COUNT(id)-1 FROM ::users WHERE group_id!=?i', [$this->c->GROUP_UNVERIFIED])->fetchColumn();
+        $stats['last_user'] = $this->c->DB->query('SELECT id, username FROM ::users WHERE group_id!=?i ORDER BY registered DESC LIMIT 1', [$this->c->GROUP_UNVERIFIED])->fetch();
         return $stats;
         return $stats;
     }
     }
 
 
@@ -74,7 +74,7 @@ class CacheGenerator
      */
      */
     public function admins()
     public function admins()
     {
     {
-        return $this->c->DB->query('SELECT id FROM ::users WHERE group_id='.PUN_ADMIN)->fetchAll(\PDO::FETCH_COLUMN);
+        return $this->c->DB->query('SELECT id FROM ::users WHERE group_id=?i', [$this->c->GROUP_ADMIN])->fetchAll(\PDO::FETCH_COLUMN);
     }
     }
 
 
     /**
     /**

+ 2 - 2
app/Models/Pages/Debug.php

@@ -23,13 +23,13 @@ class Debug extends Page
     public function debug()
     public function debug()
     {
     {
         $this->data = [
         $this->data = [
-            'time' => $this->number(microtime(true) - (empty($_SERVER['REQUEST_TIME_FLOAT']) ? $this->c->START : $_SERVER['REQUEST_TIME_FLOAT']), 3),
+            'time' => $this->number(microtime(true) - $this->c->START, 3),
             'numQueries' => 0, //$this->c->DB->get_num_queries(),
             'numQueries' => 0, //$this->c->DB->get_num_queries(),
             'memory' => $this->size(memory_get_usage()),
             'memory' => $this->size(memory_get_usage()),
             'peak' => $this->size(memory_get_peak_usage()),
             'peak' => $this->size(memory_get_peak_usage()),
         ];
         ];
 
 
-        if (defined('PUN_SHOW_QUERIES') && 0) {
+        if ($this->c->DEBUG > 1) {
             $this->data['queries'] = $this->c->DB->get_saved_queries();
             $this->data['queries'] = $this->c->DB->get_saved_queries();
         } else {
         } else {
             $this->data['queries'] = null;
             $this->data['queries'] = null;

+ 939 - 0
app/Models/Pages/Install.php

@@ -0,0 +1,939 @@
+<?php
+
+namespace ForkBB\Models\Pages;
+
+use ForkBB\Core\Container;
+use ForkBB\Models\Validator;
+use PDO;
+use PDOException;
+
+class Install extends Page
+{
+    /**
+     * Имя шаблона
+     * @var string
+     */
+    protected $nameTpl = 'layouts/install';
+
+    /**
+     * Позиция для таблицы онлайн текущего пользователя
+     * @var null|string
+     */
+    protected $onlinePos = null;
+
+    /**
+     * Для MySQL
+     * @var string
+     */
+    protected $DBEngine = '';
+
+    /**
+     * Конструктор
+     * @param Container $container
+     */
+    public function __construct(Container $container)
+    {
+        $this->c = $container;
+        $this->config = $container->config;
+        $container->Lang->load('common', $this->config['o_default_lang']);
+    }
+
+    /**
+     * Возвращает данные для шаблона
+     * @return array
+     */
+    public function getData()
+    {
+        return $this->data + [
+            'pageHeads' => $this->pageHeads(),
+            'fLang' => __('lang_identifier'),
+            'fDirection' => __('lang_direction'),
+            'fIswev' => $this->getIswev(),
+        ];
+    }
+
+    /**
+     * Возращает типы БД поддерживаемые PDO
+     * @param string $curType
+     * @return array
+     */
+    protected function getDBTypes($curType = null)
+    {
+        $dbTypes = [];
+        $pdoDrivers = PDO::getAvailableDrivers();
+        foreach ($pdoDrivers as $type) {
+            if (file_exists($this->c->DIR_APP . '/Core/DB/' . ucfirst($type) . '.php')) {
+                $slctd = $type == $curType ? true : null;
+                switch ($type) {
+                    case 'mysql':
+                        $dbTypes[$type] = ['MySQL (PDO)', $slctd];
+
+                        $type = 'mysql_innodb';
+                        $slctd = $type == $curType ? true : null;
+                        $dbTypes[$type] = ['MySQL (PDO) InnoDB', $slctd];
+                        break;
+                    case 'sqlite':
+                        $dbTypes[$type] = ['SQLite (PDO)', $slctd];
+                        break;
+                    case 'pgsql':
+                        $dbTypes[$type] = ['PostgreSQL (PDO)', $slctd];
+                        break;
+                    default:
+                        $dbTypes[$type] = [ucfirst($type) . ' (PDO)', $slctd];
+                }
+            }
+        }
+        return $dbTypes;
+    }
+
+    /**
+     * Подготовка данных для страницы установки форума
+     * @param array $args
+     * @return Page
+     */
+    public function install(array $args)
+    {
+        // язык
+        $langs = $this->c->Func->getLangs();
+        if (empty($langs)) {
+            $this->iswev['e'][] = 'No language pack.';
+            $installLang = $installLangs = $defaultLangs = 'English';
+        } else {
+            if (isset($args['installlang'])) {
+                $this->c->user->language = $args['installlang'];
+            }
+
+            $this->c->Lang->load('install');
+
+            if (count($langs) > 1) {
+                $installLang = $this->c->user->language;
+                $defLang = isset($args['defaultlang']) ? $args['defaultlang'] : $installLang;
+                $installLangs = $defaultLangs = [];
+                foreach ($langs as $lang) {
+                    $installLangs[] = $lang == $installLang ? [$lang, 1] : [$lang];
+                    $defaultLangs[] = $lang == $defLang ? [$lang, 1] : [$lang];
+                }
+            } else {
+                $installLang = $installLangs = $defaultLangs = $langs[0];
+            }
+
+        }
+        unset($args['installlang']);
+        // версия PHP
+        $phpMin = '5.6.0';
+        if (version_compare(PHP_VERSION, $phpMin, '<')) {
+            $this->iswev['e'][] = __('You are running error', 'PHP', PHP_VERSION, $this->c->FORK_REVISION, $phpMin);
+        }
+        // доступность папок на запись
+        $folders = [
+            $this->c->DIR_CONFIG,
+            $this->c->DIR_CACHE,
+            $this->c->DIR_PUBLIC . '/avatar',
+        ];
+        foreach ($folders as $folder) {
+            if (! is_writable($folder)) {
+                $this->iswev['e'][] = __('Alert folder', $folder);
+            }
+        }
+        // стиль
+        $styles = $this->c->Func->getStyles();
+        if (empty($styles)) {
+            $this->iswev['e'][] = __('No styles');
+            $defaultStyles = ['ForkBB'];
+        } else {
+            $defaultStyles = [];
+            $defStyle = isset($args['defaultstyle']) ? $args['defaultstyle'] : $this->c->user->style;
+            foreach ($styles as $style) {
+                $defaultStyles[] = $style == $defStyle ? [$style, 1] : [$style];
+            }
+        }
+        unset($args['defaultstyle']);
+        // типы БД
+        $dbTypes = $this->getDBTypes(isset($args['dbtype']) ? $args['dbtype'] : null);
+        if (empty($dbTypes)) {
+            $this->iswev['e'][] = __('No DB extensions');
+        }
+        unset($args['dbtype']);
+
+        $this->data = $args + [
+            'rev' => $this->c->FORK_REVISION,
+            'formAction' => $this->c->Router->link('Install'),
+            'installLangs' => $installLangs,
+            'installLang' => $installLang,
+            'dbTypes' => $dbTypes,
+            'dbhost' => 'localhost',
+            'dbname' => '',
+            'dbuser' => '',
+            'dbprefix' => '',
+            'username' => '',
+            'email' => '',
+            'title' => __('My ForkBB Forum'),
+            'descr' => __('Description'),
+            'baseurl' => $this->c->BASE_URL,
+            'defaultLangs' => $defaultLangs,
+            'defaultStyles' => $defaultStyles,
+        ];
+        return $this;
+    }
+
+    /**
+     * Начальная стадия установки
+     * @return Page
+     */
+    public function installPost()
+    {
+        $v = $this->c->Validator->setRules([
+            'installlang' => 'string:trim',
+            'changelang' => 'string',
+        ]);
+        $v->validation($_POST);
+
+        $installLang = $v->installlang;
+
+        if (isset($v->changelang)) {
+            return $this->install(['installlang' => $installLang]);
+        }
+
+        $this->c->user->language = $installLang;
+        $this->c->Lang->load('install');
+
+        $v = $this->c->Validator->addValidators([
+            'check_prefix' => [$this, 'vCheckPrefix'],
+            'check_host'   => [$this, 'vCheckHost'],
+            'rtrim_url'    => [$this, 'vRtrimURL']
+        ])->setRules([
+            'installlang' => 'string:trim',
+            'dbtype' => ['required|string:trim|in:' . implode(',', array_keys($this->getDBTypes())), __('Database type')],
+            'dbhost' => ['required|string:trim|check_host', __('Database server hostname')],
+            'dbname' => ['required|string:trim', __('Database name')],
+            'dbuser' => ['string:trim', __('Database username')],
+            'dbpass' => ['string:trim', __('Database password')],
+            'dbprefix' => ['string:trim|max:40|check_prefix', __('Table prefix')],
+            'username' => ['required|string:trim|min:2|max:25', __('Administrator username')],
+            'password' => ['required|string|min:8|password', __('Administrator password')],
+            'email' => 'required|string:trim,lower|email',
+            'title' => ['required|string:trim', __('Board title')],
+            'descr' => ['required|string:trim', __('Board description')],
+            'baseurl' => ['required|string:trim|rtrim_url', __('Base URL')],
+            'defaultlang' => ['required|string:trim|in:' . implode(',', $this->c->Func->getLangs()), __('Default language')],
+            'defaultstyle' => ['required|string:trim|in:' . implode(',', $this->c->Func->getStyles()), __('Default style')],
+        ])->setMessages([
+            'email' => __('Wrong email'),
+        ]);
+
+        if ($v->validation($_POST)) {
+            return $this->installEnd($v);
+        } else {
+            $this->iswev = $v->getErrors();
+            return $this->install($v->getData());
+        }
+    }
+
+    /**
+     * Обработка base URL
+     * @param Validator $v
+     * @param string $url
+     * @param int $type
+     * @return array
+     */
+    public function vRtrimURL(Validator $v, $url, $type)
+    {
+        return [rtrim($url, '/'), $type, false];
+    }
+
+    /**
+     * Дополнительная проверка префикса
+     * @param Validator $v
+     * @param string $prefix
+     * @param int $type
+     * @return array
+     */
+    public function vCheckPrefix(Validator $v, $prefix, $type)
+    {
+        $error = false;
+        if (strlen($prefix) == 0) {
+        } elseif (! preg_match('%^[a-z][a-z\d_]*$%i', $prefix)) {
+            $error = __('Table prefix error', $prefix);
+        } elseif ($v->dbtype == 'sqlite' && strtolower($prefix) == 'sqlite_') {
+            $error = __('Prefix reserved');
+        }
+        return [$prefix, $type, $error];
+    }
+
+    /**
+     * Полная проверка подключения к БД
+     * @param Validator $v
+     * @param string $dbhost
+     * @param int $type
+     * @return array
+     */
+    public function vCheckHost(Validator $v, $dbhost, $type)
+    {
+        $this->c->DB_USERNAME = $v->dbuser;
+        $this->c->DB_PASSWORD = $v->dbpass;
+        $this->c->DB_PREFIX   = $v->dbprefix;
+        $dbtype = $v->dbtype;
+        $dbname = $v->dbname;
+        // есть ошибки, ни чего не проверяем
+        if (! empty($v->getErrors())) {
+            return [$dbhost, $type, false];
+        }
+        // настройки подключения БД
+        $DBEngine = 'MyISAM';
+        switch ($dbtype) {
+            case 'mysql_innodb':
+                $DBEngine = 'InnoDB';
+            case 'mysql':
+                $this->DBEngine = $DBEngine;
+                if (preg_match('%^([^:]+):(\d+)$%', $dbhost, $matches)) {
+                    $this->c->DB_DSN = "mysql:host={$matches[1]};port={$matches[2]};dbname={$dbname};charset=utf8mb4";
+                } else {
+                    $this->c->DB_DSN = "mysql:host={$dbhost};dbname={$dbname};charset=utf8mb4";
+                }
+                break;
+            case 'sqlite':
+                break;
+            case 'pgsql':
+                break;
+            default:
+                //????
+        }
+        $this->c->DB_OPTIONS  = [];
+        // подключение к БД
+        try {
+            $stat = $this->c->DB->statistics();
+        } catch (PDOException $e) {
+            return [$dbhost, $type, $e->getMessage()];
+        }
+        // проверка наличия таблицы пользователей в БД
+        try {
+            $stmt = $this->c->DB->query('SELECT 1 FROM ::users WHERE id=1 LIMIT 1');
+            if (! empty($stmt->fetch())) {
+                return [$dbhost, $type, __('Existing table error', $v->dbprefix, $v->dbname)];
+            }
+        } catch (PDOException $e) {
+            // все отлично, таблица пользователей не найдена
+        }
+        // база MySQL, кодировка базы UTF-8 (3 байта)
+        if (isset($stat['character_set_database']) && $stat['character_set_database'] == 'utf8') {
+            $this->c->DB_DSN = str_replace('charset=utf8mb4', 'charset=utf8', $this->c->DB_DSN);
+        }
+        return [$dbhost, $type, false];
+    }
+
+    /**
+     * Завершение установки форума
+     * @param Validator $v
+     * @return Page
+     */
+    protected function installEnd(Validator $v)
+    {
+        $this->c->DB->beginTransaction();
+
+        $schema = [
+            'FIELDS' => [
+                'id'          => ['SERIAL', false],
+                'username'    => ['VARCHAR(200)', true],
+                'ip'          => ['VARCHAR(255)', true],
+                'email'       => ['VARCHAR(80)', true],
+                'message'     => ['VARCHAR(255)', true],
+                'expire'      => ['INT(10) UNSIGNED', true],
+                'ban_creator' => ['INT(10) UNSIGNED', false, 0],
+            ],
+            'PRIMARY KEY' => ['id'],
+            'INDEXES' => [
+                'username_idx' => ['username(25)'],
+            ],
+            'ENGINE' => $this->DBEngine,
+        ];
+        $this->c->DB->createTable('bans', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'id'            => ['SERIAL', false],
+                'cat_name'      => ['VARCHAR(80)', false, 'New Category'],
+                'disp_position' => ['INT(10)', false, 0],
+            ],
+            'PRIMARY KEY' => ['id'],
+        ];
+        $this->c->DB->createTable('categories', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'id'           => ['SERIAL', false],
+                'search_for'   => ['VARCHAR(60)', false, ''],
+                'replace_with' => ['VARCHAR(60)', false, ''],
+            ],
+            'PRIMARY KEY' => ['id'],
+        ];
+        $this->c->DB->createTable('censoring', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'conf_name'  => ['VARCHAR(255)', false, ''],
+                'conf_value' => ['TEXT', true],
+            ],
+            'PRIMARY KEY' => ['conf_name'],
+        ];
+        $this->c->DB->createTable('config', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'group_id'     => ['INT(10)', false, 0],
+                'forum_id'     => ['INT(10)', false, 0],
+                'read_forum'   => ['TINYINT(1)', false, 1],
+                'post_replies' => ['TINYINT(1)', false, 1],
+                'post_topics'  => ['TINYINT(1)', false, 1],
+            ],
+            'PRIMARY KEY' => array('group_id', 'forum_id'),
+        ];
+        $this->c->DB->createTable('forum_perms', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'id'              => ['SERIAL', false],
+                'forum_name'      => ['VARCHAR(80)', false, 'New forum'],
+                'forum_desc'      => ['TEXT', true],
+                'redirect_url'    => ['VARCHAR(100)', true],
+                'moderators'      => ['TEXT', true],
+                'num_topics'      => ['MEDIUMINT(8) UNSIGNED', false, 0],
+                'num_posts'       => ['MEDIUMINT(8) UNSIGNED', false, 0],
+                'last_post'       => ['INT(10) UNSIGNED', true],
+                'last_post_id'    => ['INT(10) UNSIGNED', true],
+                'last_poster'     => ['VARCHAR(200)', true],
+                'last_topic'      => ['VARCHAR(255)', true],
+                'sort_by'         => ['TINYINT(1)', false, 0],
+                'disp_position'   => ['INT(10)', false, 0],
+                'cat_id'          => ['INT(10) UNSIGNED', false, 0],
+                'no_sum_mess'     => ['TINYINT(1)', false, 0],
+                'parent_forum_id' => ['INT(10) UNSIGNED', false, 0],
+            ],
+            'PRIMARY KEY' => ['id'],
+        ];
+        $this->c->DB->createTable('forums', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'g_id'                   => ['SERIAL', false],
+                'g_title'                => ['VARCHAR(50)', false, ''],
+                'g_user_title'           => ['VARCHAR(50)', true],
+                'g_promote_min_posts'    => ['INT(10) UNSIGNED', false, 0],
+                'g_promote_next_group'   => ['INT(10) UNSIGNED', false, 0],
+                'g_moderator'            => ['TINYINT(1)', false, 0],
+                'g_mod_edit_users'       => ['TINYINT(1)', false, 0],
+                'g_mod_rename_users'     => ['TINYINT(1)', false, 0],
+                'g_mod_change_passwords' => ['TINYINT(1)', false, 0],
+                'g_mod_ban_users'        => ['TINYINT(1)', false, 0],
+                'g_mod_promote_users'    => ['TINYINT(1)', false, 0],
+                'g_read_board'           => ['TINYINT(1)', false, 1],
+                'g_view_users'           => ['TINYINT(1)', false, 1],
+                'g_post_replies'         => ['TINYINT(1)', false, 1],
+                'g_post_topics'          => ['TINYINT(1)', false, 1],
+                'g_edit_posts'           => ['TINYINT(1)', false, 1],
+                'g_delete_posts'         => ['TINYINT(1)', false, 1],
+                'g_delete_topics'        => ['TINYINT(1)', false, 1],
+                'g_post_links'           => ['TINYINT(1)', false, 1],
+                'g_set_title'            => ['TINYINT(1)', false, 1],
+                'g_search'               => ['TINYINT(1)', false, 1],
+                'g_search_users'         => ['TINYINT(1)', false, 1],
+                'g_send_email'           => ['TINYINT(1)', false, 1],
+                'g_post_flood'           => ['SMALLINT(6)', false, 30],
+                'g_search_flood'         => ['SMALLINT(6)', false, 30],
+                'g_email_flood'          => ['SMALLINT(6)', false, 60],
+                'g_report_flood'         => ['SMALLINT(6)', false, 60],
+                'g_deledit_interval'     => ['INT(10)', false, 0],
+                'g_pm'                   => ['TINYINT(1)', false, 1],
+                'g_pm_limit'             => ['INT(10) UNSIGNED', false, 100],
+            ],
+            'PRIMARY KEY' => ['g_id'],
+        ];
+        $this->c->DB->createTable('groups', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'user_id'     => ['INT(10) UNSIGNED', false, 1],
+                'ident'       => ['VARCHAR(200)', false, ''],
+                'logged'      => ['INT(10) UNSIGNED', false, 0],
+                'idle'        => ['TINYINT(1)', false, 0],
+                'last_post'   => ['INT(10) UNSIGNED', true],
+                'last_search' => ['INT(10) UNSIGNED', true],
+                'witt_data'   => ['VARCHAR(255)', false, ''],  //????
+                'o_position'  => ['VARCHAR(100)', false, ''],
+                'o_name'      => ['VARCHAR(200)', false, ''],
+            ],
+            'UNIQUE KEYS' => [
+                'user_id_ident_idx' => ['user_id', 'ident(25)'],
+            ],
+            'INDEXES' => [
+                'ident_idx'      => ['ident'],
+                'logged_idx'     => ['logged'],
+                'o_position_idx' => ['o_position'],
+            ],
+        ];
+        $this->c->DB->createTable('online', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'id'           => ['SERIAL', false],
+                'poster'       => ['VARCHAR(200)', false, ''],
+                'poster_id'    => ['INT(10) UNSIGNED', false, 1],
+                'poster_ip'    => ['VARCHAR(39)', true],
+                'poster_email' => ['VARCHAR(80)', true],
+                'message'      => ['MEDIUMTEXT', true],
+                'hide_smilies' => ['TINYINT(1)', false, 0],
+                'edit_post'    => ['TINYINT(1)', false, 0],
+                'posted'       => ['INT(10) UNSIGNED', false, 0],
+                'edited'       => ['INT(10) UNSIGNED', true],
+                'edited_by'    => ['VARCHAR(200)', true],
+                'user_agent'   => ['VARCHAR(255)', true],
+                'topic_id'     => ['INT(10) UNSIGNED', false, 0],
+            ],
+            'PRIMARY KEY' => ['id'],
+            'INDEXES' => [
+                'topic_id_idx' => ['topic_id'],
+                'multi_idx'    => ['poster_id', 'topic_id'],
+            ],
+        ];
+        $this->c->DB->createTable('posts', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'id'          => ['SERIAL', false],
+                'post_id'     => ['INT(10) UNSIGNED', false, 0],
+                'topic_id'    => ['INT(10) UNSIGNED', false, 0],
+                'forum_id'    => ['INT(10) UNSIGNED', false, 0],
+                'reported_by' => ['INT(10) UNSIGNED', false, 0],
+                'created'     => ['INT(10) UNSIGNED', false, 0],
+                'message'     => ['TEXT', true],
+                'zapped'      => ['INT(10) UNSIGNED', true],
+                'zapped_by'   => ['INT(10) UNSIGNED', true],
+            ],
+            'PRIMARY KEY' => ['id'],
+            'INDEXES' => [
+                'zapped_idx' => ['zapped'],
+            ],
+        ];
+        $this->c->DB->createTable('reports', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'id'          => ['INT(10) UNSIGNED', false, 0],
+                'ident'       => ['VARCHAR(200)', false, ''],
+                'search_data' => ['MEDIUMTEXT', true],
+            ],
+            'PRIMARY KEY' => ['id'],
+            'INDEXES' => [
+                'ident_idx' => ['ident(8)'], //????
+            ],
+        ];
+        $this->c->DB->createTable('search_cache', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'post_id'       => ['INT(10) UNSIGNED', false, 0],
+                'word_id'       => ['INT(10) UNSIGNED', false, 0],
+                'subject_match' => ['TINYINT(1)', false, 0],
+            ],
+            'INDEXES' => [
+                'word_id_idx' => ['word_id'],
+                'post_id_idx' => ['post_id'],
+            ],
+        ];
+        $this->c->DB->createTable('search_matches', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'id'   => ['SERIAL', false],
+                'word' => ['VARCHAR(20)', false, '' , 'bin'],
+            ],
+            'PRIMARY KEY' => ['word'],
+            'INDEXES' => [
+                'id_idx' => ['id'],
+            ],
+        ];
+        if ($v->dbtype == 'sqlite') { //????
+            $schema['PRIMARY KEY'] = ['id'];
+            $schema['UNIQUE KEYS'] = ['word_idx' => ['word']];
+        }
+        $this->c->DB->createTable('search_words', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'user_id'  => ['INT(10) UNSIGNED', false, 0],
+                'topic_id' => ['INT(10) UNSIGNED', false, 0],
+            ],
+            'PRIMARY KEY' => ['user_id', 'topic_id'],
+        ];
+        $this->c->DB->createTable('topic_subscriptions', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'user_id'  => ['INT(10) UNSIGNED', false, 0],
+                'forum_id' => ['INT(10) UNSIGNED', false, 0],
+            ],
+            'PRIMARY KEY' => ['user_id', 'forum_id'],
+        ];
+        $this->c->DB->createTable('forum_subscriptions', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'id'            => ['SERIAL', false],
+                'poster'        => ['VARCHAR(200)', false, ''],
+                'subject'       => ['VARCHAR(255)', false, ''],
+                'posted'        => ['INT(10) UNSIGNED', false, 0],
+                'first_post_id' => ['INT(10) UNSIGNED', false, 0],
+                'last_post'     => ['INT(10) UNSIGNED', false, 0],
+                'last_post_id'  => ['INT(10) UNSIGNED', false, 0],
+                'last_poster'   => ['VARCHAR(200)', true],
+                'num_views'     => ['MEDIUMINT(8) UNSIGNED', false, 0],
+                'num_replies'   => ['MEDIUMINT(8) UNSIGNED', false, 0],
+                'closed'        => ['TINYINT(1)', false, 0],
+                'sticky'        => ['TINYINT(1)', false, 0],
+                'stick_fp'      => ['TINYINT(1)', false, 0],
+                'moved_to'      => ['INT(10) UNSIGNED', true],
+                'forum_id'      => ['INT(10) UNSIGNED', false, 0],
+                'poll_type'     => ['TINYINT(4)', false, 0],
+                'poll_time'     => ['INT(10) UNSIGNED', false, 0],
+                'poll_term'     => ['TINYINT(4)', false, 0],
+                'poll_kol'      => ['INT(10) UNSIGNED', false, 0],
+            ],
+            'PRIMARY KEY' => ['id'],
+            'INDEXES' => [
+                'forum_id_idx'      => ['forum_id'],
+                'moved_to_idx'      => ['moved_to'],
+                'last_post_idx'     => ['last_post'],
+                'first_post_id_idx' => ['first_post_id'],
+            ],
+        ];
+        $this->c->DB->createTable('topics', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'bl_id'      => ['INT(10) UNSIGNED', false, 0],
+                'bl_user_id' => ['INT(10) UNSIGNED', false, 0],
+            ],
+            'INDEXES' => [
+                'bl_id_idx'      => ['bl_id'],
+                'bl_user_id_idx' => ['bl_user_id']
+            ],
+        ];
+        $this->c->DB->createTable('pms_new_block', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'id'           => ['SERIAL', false],
+                'poster'       => ['VARCHAR(200)', false, ''],
+                'poster_id'    => ['INT(10) UNSIGNED', false, 1],
+                'poster_ip'    => ['VARCHAR(39)', true],
+                'message'      => ['TEXT', true],
+                'hide_smilies' => ['TINYINT(1)', false, 0],
+                'posted'       => ['INT(10) UNSIGNED', false, 0],
+                'edited'       => ['INT(10) UNSIGNED', true],
+                'edited_by'    => ['VARCHAR(200)', true],
+                'post_new'     => ['TINYINT(1)', false, 1],
+                'topic_id'     => ['INT(10) UNSIGNED', false, 0],
+            ],
+            'PRIMARY KEY' => ['id'],
+            'INDEXES' => [
+                'topic_id_idx' => ['topic_id'],
+                'multi_idx'    => ['poster_id', 'topic_id'],
+            ],
+        ];
+        $this->c->DB->createTable('pms_new_posts', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'id'          => ['SERIAL', false],
+                'topic'       => ['VARCHAR(255)', false, ''],
+                'starter'     => ['VARCHAR(200)', false, ''],
+                'starter_id'  => ['INT(10) UNSIGNED', false, 0],
+                'to_user'     => ['VARCHAR(200)', false, ''],
+                'to_id'       => ['INT(10) UNSIGNED', false, 0],
+                'replies'     => ['MEDIUMINT(8) UNSIGNED', false, 0],
+                'last_posted' => ['INT(10) UNSIGNED', false, 0],
+                'last_poster' => ['TINYINT(1)', false, 0],
+                'see_st'      => ['INT(10) UNSIGNED', false, 0],
+                'see_to'      => ['INT(10) UNSIGNED', false, 0],
+                'topic_st'    => ['TINYINT(4)', false, 0],
+                'topic_to'    => ['TINYINT(4)', false, 0],
+            ],
+            'PRIMARY KEY' => ['id'],
+            'INDEXES' => [
+                'multi_idx_st' => ['starter_id', 'topic_st'],
+                'multi_idx_to' => ['to_id', 'topic_to'],
+            ],
+        ];
+        $this->c->DB->createTable('pms_new_topics', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'id'               => ['SERIAL', false],
+                'group_id'         => ['INT(10) UNSIGNED', false, 3], //????
+                'username'         => ['VARCHAR(200)', false, ''],
+                'password'         => ['VARCHAR(255)', false, ''],
+                'email'            => ['VARCHAR(80)', false, ''],
+                'email_confirmed'  => ['TINYINT(1)', false, 0],
+                'title'            => ['VARCHAR(50)', true],
+                'realname'         => ['VARCHAR(40)', true],
+                'url'              => ['VARCHAR(100)', true],
+                'jabber'           => ['VARCHAR(80)', true],
+                'icq'              => ['VARCHAR(12)', true],
+                'msn'              => ['VARCHAR(80)', true],
+                'aim'              => ['VARCHAR(30)', true],
+                'yahoo'            => ['VARCHAR(30)', true],
+                'location'         => ['VARCHAR(30)', true],
+                'signature'        => ['TEXT', true],
+                'disp_topics'      => ['TINYINT(3) UNSIGNED', true],
+                'disp_posts'       => ['TINYINT(3) UNSIGNED', true],
+                'email_setting'    => ['TINYINT(1)', false, 1],
+                'notify_with_post' => ['TINYINT(1)', false, 0],
+                'auto_notify'      => ['TINYINT(1)', false, 0],
+                'show_smilies'     => ['TINYINT(1)', false, 1],
+                'show_img'         => ['TINYINT(1)', false, 1],
+                'show_img_sig'     => ['TINYINT(1)', false, 1],
+                'show_avatars'     => ['TINYINT(1)', false, 1],
+                'show_sig'         => ['TINYINT(1)', false, 1],
+                'timezone'         => ['FLOAT', false, 0],
+                'dst'              => ['TINYINT(1)', false, 0],
+                'time_format'      => ['TINYINT(1)', false, 0],
+                'date_format'      => ['TINYINT(1)', false, 0],
+                'language'         => ['VARCHAR(25)', false, $v->defaultlang],
+                'style'            => ['VARCHAR(25)', false, $v->defaultstyle],
+                'num_posts'        => ['INT(10) UNSIGNED', false, 0],
+                'last_post'        => ['INT(10) UNSIGNED', true],
+                'last_search'      => ['INT(10) UNSIGNED', true],
+                'last_email_sent'  => ['INT(10) UNSIGNED', true],
+                'last_report_sent' => ['INT(10) UNSIGNED', true],
+                'registered'       => ['INT(10) UNSIGNED', false, 0],
+                'registration_ip'  => ['VARCHAR(39)', false, ''],
+                'last_visit'       => ['INT(10) UNSIGNED', false, 0],
+                'admin_note'       => ['VARCHAR(30)', true],
+                'activate_string'  => ['VARCHAR(80)', true],
+                'activate_key'     => ['VARCHAR(8)', true],
+                'messages_enable'  => ['TINYINT(1)', false, 1],
+                'messages_email'   => ['TINYINT(1)', false, 0],
+                'messages_flag'    => ['TINYINT(1)', false, 0],
+                'messages_new'     => ['INT(10) UNSIGNED', false, 0],
+                'messages_all'     => ['INT(10) UNSIGNED', false, 0],
+                'pmsn_last_post'   => ['INT(10) UNSIGNED', true],
+                'warning_flag'     => ['TINYINT(1)', false, 0],
+                'warning_all'      => ['INT(10) UNSIGNED', false, 0],
+                'gender'           => ['TINYINT(4) UNSIGNED', false, 0],
+                'u_mark_all_read'  => ['INT(10) UNSIGNED', true],
+            ],
+            'PRIMARY KEY' => ['id'],
+            'UNIQUE KEYS' => [
+                'username_idx' => ['username(25)'],
+            ],
+            'INDEXES' => [
+                'registered_idx' => ['registered'],
+            ],
+        ];
+        $this->c->DB->createTable('users', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'id'            => ['SERIAL', false],
+                'image'         => ['VARCHAR(40)', false, ''],
+                'text'          => ['VARCHAR(20)', false, ''],
+                'disp_position' => ['TINYINT(4) UNSIGNED', false, 0],
+            ],
+            'PRIMARY KEY' => ['id'],
+        ];
+        $this->c->DB->createTable('smilies', $schema);
+
+        $smilies = [
+            ':)'         => 'smile.png',
+            '=)'         => 'smile.png',
+            ':|'         => 'neutral.png',
+            '=|'         => 'neutral.png',
+            ':('         => 'sad.png',
+            '=('         => 'sad.png',
+            ':D'         => 'big_smile.png',
+            '=D'         => 'big_smile.png',
+            ':o'         => 'yikes.png',
+            ':O'         => 'yikes.png',
+            ';)'         => 'wink.png',
+            ':/'         => 'hmm.png',
+            ':P'         => 'tongue.png',
+            ':p'         => 'tongue.png',
+            ':lol:'      => 'lol.png',
+            ':mad:'      => 'mad.png',
+            ':rolleyes:' => 'roll.png',
+            ':cool:'     => 'cool.png',
+        ];
+        $i = 0;
+        foreach ($smilies as $text => $img) {
+            $this->c->DB->exec('INSERT INTO ::smilies (image, text, disp_position) VALUES(?s, ?s, ?i)', [$img, $text, $i++]); //????
+        }
+
+        $schema = [
+            'FIELDS' => [
+                'id'        => ['SERIAL', false],
+                'poster'    => ['VARCHAR(200)', false, ''],
+                'poster_id' => ['INT(10) UNSIGNED', false, 0],
+                'posted'    => ['INT(10) UNSIGNED', false, 0],
+                'message'   => ['TEXT', true],
+            ],
+            'PRIMARY KEY' => ['id'],
+        ];
+        $this->c->DB->createTable('warnings', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'tid'      => ['INT(10) UNSIGNED', false, 0],
+                'question' => ['TINYINT(4)', false, 0],
+                'field'    => ['TINYINT(4)', false, 0],
+                'choice'   => ['VARCHAR(255)', false, ''],
+                'votes'    => ['INT(10) UNSIGNED', false, 0],
+            ],
+            'PRIMARY KEY' => ['tid', 'question', 'field'],
+        ];
+        $this->c->DB->createTable('poll', $schema);
+
+        $schema = [
+            'FIELDS' => [
+                'tid' => ['INT(10) UNSIGNED', false],
+                'uid' => ['INT(10) UNSIGNED', false],
+                'rez' => ['TEXT', true],
+            ],
+            'PRIMARY KEY' => ['tid', 'uid'],
+        ];
+        $this->c->DB->createTable('poll_voted', $schema) ;
+
+        $now = time();
+
+        $groups = [
+        // g_id, g_title,             g_user_title,        g_moderator, g_mod_edit_users, g_mod_rename_users, g_mod_change_passwords, g_mod_ban_users, g_mod_promote_users, g_read_board, g_view_users, g_post_replies, g_post_topics, g_edit_posts, g_delete_posts, g_delete_topics, g_set_title, g_search, g_search_users, g_send_email, g_post_flood, g_search_flood, g_email_flood, g_report_flood
+            [1, __('Administrators'), __('Administrator'), 0,           0,                0,                  0,                      0,               1,                   1,            1,            1,              1,             1,            1,              1,               1,           1,        1,              1,            0,            0,              0,             0],
+            [2, __('Moderators'),     __('Moderator'),     1,           1,                1,                  1,                      1,               1,                   1,            1,            1,              1,             1,            1,              1,               1,           1,        1,              1,            0,            0,              0,             0],
+            [3, __('Guests'),         NULL,                0,           0,                0,                  0,                      0,               0,                   1,            1,            0,              0,             0,            0,              0,               0,           1,        1,              0,            120,          60,             0,             0],
+            [4, __('Members'),        NULL,                0,           0,                0,                  0,                      0,               0,                   1,            1,            1,              1,             1,            1,              1,               0,           1,        1,              1,            30,           30,             60,            60],
+        ];
+        foreach ($groups as $group) { //???? $db_type != 'pgsql'
+            $this->c->DB->exec('INSERT INTO ::groups (g_id, g_title, g_user_title, g_moderator, g_mod_edit_users, g_mod_rename_users, g_mod_change_passwords, g_mod_ban_users, g_mod_promote_users, g_read_board, g_view_users, g_post_replies, g_post_topics, g_edit_posts, g_delete_posts, g_delete_topics, g_set_title, g_search, g_search_users, g_send_email, g_post_flood, g_search_flood, g_email_flood, g_report_flood) VALUES (?i, ?s, ?s, ?i, ?i, ?i, ?i, ?i, ?i, ?i, ?i, ?i, ?i, ?i, ?i, ?i, ?i, ?i, ?i, ?i, ?i, ?i, ?i, ?i)', $group) ;
+        }
+        $this->c->DB->exec('UPDATE ::groups SET g_pm_limit=0 WHERE g_id=1') ;
+
+        $ip = filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP) ?: 'unknow';
+        $this->c->DB->exec('INSERT INTO ::users (group_id, username, password, email) VALUES (?i, ?s, ?s, ?s)', [3, __('Guest'), __('Guest'), __('Guest')]);
+        $this->c->DB->exec('INSERT INTO ::users (group_id, username, password, email, language, style, num_posts, last_post, registered, registration_ip, last_visit) VALUES (?i, ?s, ?s, ?s, ?s, ?s, ?i, ?i, ?i, ?s, ?i)', [1, $v->username, password_hash($v->password, PASSWORD_DEFAULT), $v->email, $v->defaultlang, $v->defaultstyle, 1, $now, $now, $ip, $now]);
+
+        $pun_config = [
+            'i_fork_revision' => $this->c->FORK_REVISION,
+            'o_board_title' => $v->title,
+            'o_board_desc' => $v->descr,
+            'o_default_timezone' => 0,
+            'o_time_format' => 'H:i:s',
+            'o_date_format' => 'Y-m-d',
+            'o_timeout_visit' => 1800,
+            'o_timeout_online' => 300,
+            'o_redirect_delay' => 1,
+            'o_show_version' => 0,
+            'o_show_user_info' => 1,
+            'o_show_post_count' => 1,
+            'o_signatures' => 1,
+            'o_smilies' => 1,
+            'o_smilies_sig' => 1,
+            'o_make_links' => 1,
+            'o_default_lang' => $v->defaultlang,
+            'o_default_style' => $v->defaultstyle,
+            'o_default_user_group' => 4,
+            'o_topic_review' => 15,
+            'o_disp_topics_default' => 30,
+            'o_disp_posts_default' => 25,
+            'o_indent_num_spaces' => 4,
+            'o_quote_depth' => 3,
+            'o_quickpost' => 1,
+            'o_users_online' => 1,
+            'o_censoring' => 0,
+            'o_show_dot' => 0,
+            'o_topic_views' => 1,
+            'o_quickjump' => 1,
+            'o_gzip' => 0,
+            'o_additional_navlinks' => '',
+            'o_report_method' => 0,
+            'o_regs_report' => 0,
+            'o_default_email_setting' => 1,
+            'o_mailing_list' => $v->email,
+            'o_avatars' => in_array(strtolower(@ini_get('file_uploads')), ['on', 'true', '1']) ? 1 : 0,
+            'o_avatars_dir' => 'img/avatars',
+            'o_avatars_width' => 60,
+            'o_avatars_height' => 60,
+            'o_avatars_size' => 10240,
+            'o_search_all_forums' => 1,
+            'o_admin_email' => $v->email,
+            'o_webmaster_email' => $v->email,
+            'o_forum_subscriptions' => 1,
+            'o_topic_subscriptions' => 1,
+            'o_smtp_host' => NULL,
+            'o_smtp_user' => NULL,
+            'o_smtp_pass' => NULL,
+            'o_smtp_ssl' => 0,
+            'o_regs_allow' => 1,
+            'o_regs_verify' => 0,
+            'o_announcement' => 0,
+            'o_announcement_message' => __('Announcement'),
+            'o_rules' => 0,
+            'o_rules_message' => __('Rules'),
+            'o_maintenance' => 0,
+            'o_maintenance_message' => __('Maintenance message'),
+            'o_default_dst' => 0,
+            'o_feed_type' => 2,
+            'o_feed_ttl' => 0,
+            'p_message_bbcode' => 1,
+            'p_message_img_tag' => 1,
+            'p_message_all_caps' => 1,
+            'p_subject_all_caps' => 1,
+            'p_sig_all_caps' => 1,
+            'p_sig_bbcode' => 1,
+            'p_sig_img_tag' => 0,
+            'p_sig_length' => 400,
+            'p_sig_lines' => 4,
+            'p_allow_banned_email' => 1,
+            'p_allow_dupe_email' => 0,
+            'p_force_guest_email' => 1,
+            'o_pms_enabled' => 1,                    // New PMS - Visman
+            'o_pms_min_kolvo' => 0,
+            'o_merge_timeout' => 86400,        // merge post - Visman
+            'o_board_redirect' => '',    // для редиректа - Visman
+            'o_board_redirectg' => 0,
+            'o_poll_enabled' => 0,    // опросы - Visman
+            'o_poll_max_ques' => 3,
+            'o_poll_max_field' => 20,
+            'o_poll_time' => 60,
+            'o_poll_term' => 3,
+            'o_poll_guest' => 0,
+            'o_fbox_guest' => 0,    // Fancybox - Visman
+            'o_fbox_files' => 'viewtopic.php,search.php,pmsnew.php',
+            'o_coding_forms' => 1,    // кодирование форм - Visman
+            'o_check_ip' => 0,    // проверка ip администрации - Visman
+            'o_crypto_enable' => 1,    // случайные имена полей форм - Visman
+            'o_crypto_pas' => $this->c->Secury->randomPass(25),
+            'o_crypto_salt' => $this->c->Secury->randomPass(13),
+            'o_enable_acaptcha' => 1, // математическая каптча
+            'st_max_users' => 1,    // статистика по максимуму юзеров - Visman
+            'st_max_users_time' => time(),
+        ];
+
+        foreach ($pun_config as $conf_name => $conf_value) {
+            $this->c->DB->exec('INSERT INTO ::config (conf_name, conf_value) VALUES (?s, ?s)', [$conf_name, $conf_value]);
+        }
+
+        // Insert some other default data
+        $subject = __('Test post');
+        $message = __('Test message');
+
+        $this->c->DB->exec('INSERT INTO ::categories (cat_name, disp_position) VALUES (?s, ?i)', [__('Test category'), 1]);
+
+        $this->c->DB->exec('INSERT INTO ::forums (forum_name, forum_desc, num_topics, num_posts, last_post, last_post_id, last_poster, last_topic, disp_position, cat_id) VALUES (?s, ?s, ?i, ?i, ?i, ?i, ?s, ?s, ?i, ?i)', [__('Test forum'), __('This is just a test forum'), 1, 1, $now, 1, $v->username, $subject, 1, 1]);
+
+        $this->c->DB->exec('INSERT INTO ::topics (poster, subject, posted, first_post_id, last_post, last_post_id, last_poster, forum_id) VALUES(?s, ?s, ?i, ?i, ?i, ?i, ?s, ?i)', [$v->username, $subject, $now, 1, $now, 1, $v->username, 1]);
+
+        $this->c->DB->exec('INSERT INTO ::posts (poster, poster_id, poster_ip, message, posted, topic_id) VALUES(?s, ?i, ?s, ?s, ?i, ?i)', [$v->username, 2, $ip, $message, $now, 1]);
+
+        $this->c->DB->commit();
+
+        exit();
+    }
+}

+ 7 - 5
app/Models/Pages/Page.php

@@ -219,7 +219,7 @@ abstract class Page
      */
      */
     protected function pageHeads()
     protected function pageHeads()
     {
     {
-        return []; //????
+        return ['link rel="stylesheet" type="text/css" href="' . $this->c->Router->link() . 'style/' . $this->c->user->style . '/style.css' . '"'];
     }
     }
 
 
     /**
     /**
@@ -310,7 +310,7 @@ abstract class Page
      */
      */
     public function __call($name, array $arguments)
     public function __call($name, array $arguments)
     {
     {
-        throw new RuntimeException("'{$name}' method is not");
+        throw new RuntimeException("'{$name}' method not found.");
     }
     }
 
 
     /**
     /**
@@ -349,7 +349,9 @@ abstract class Page
      */
      */
     protected function number($number, $decimals = 0)
     protected function number($number, $decimals = 0)
     {
     {
-        return is_numeric($number) ? number_format($number, $decimals, __('lang_decimal_point'), __('lang_thousands_sep')) : 'not a number';
+        return is_numeric($number)
+            ? number_format($number, $decimals, __('lang_decimal_point'), __('lang_thousands_sep'))
+            : 'not a number';
     }
     }
 
 
 
 
@@ -375,10 +377,10 @@ abstract class Page
         $timestamp += $diff;
         $timestamp += $diff;
 
 
         if (null === $dateFormat) {
         if (null === $dateFormat) {
-            $dateFormat = $this->c->date_formats[$user->dateFormat];
+            $dateFormat = $this->c->DATE_FORMATS[$user->dateFormat];
         }
         }
         if(null === $timeFormat) {
         if(null === $timeFormat) {
-            $timeFormat = $this->c->time_formats[$user->timeFormat];
+            $timeFormat = $this->c->TIME_FORMATS[$user->timeFormat];
         }
         }
 
 
         $date = gmdate($dateFormat, $timestamp);
         $date = gmdate($dateFormat, $timestamp);

+ 1 - 1
app/Models/Pages/Register.php

@@ -133,7 +133,7 @@ class Register extends Page
     protected function regEnd(Validator $v)
     protected function regEnd(Validator $v)
     {
     {
         if ($this->config['o_regs_verify'] == '1') {
         if ($this->config['o_regs_verify'] == '1') {
-            $groupId = PUN_UNVERIFIED;
+            $groupId = $this->c->GROUP_UNVERIFIED;
             $key = 'w' . $this->c->Secury->randomPass(79);
             $key = 'w' . $this->c->Secury->randomPass(79);
         } else {
         } else {
             $groupId = $this->config['o_default_user_group'];
             $groupId = $this->config['o_default_user_group'];

+ 25 - 15
app/Models/User.php

@@ -4,7 +4,6 @@ namespace ForkBB\Models;
 
 
 use ForkBB\Core\AbstractModel;
 use ForkBB\Core\AbstractModel;
 use ForkBB\Core\Container;
 use ForkBB\Core\Container;
-use RuntimeException;
 
 
 class User extends AbstractModel
 class User extends AbstractModel
 {
 {
@@ -46,22 +45,25 @@ class User extends AbstractModel
 
 
     protected function getIsUnverified()
     protected function getIsUnverified()
     {
     {
-        return $this->groupId == PUN_UNVERIFIED;
+        return $this->groupId == $this->c->GROUP_UNVERIFIED;
     }
     }
 
 
     protected function getIsGuest()
     protected function getIsGuest()
     {
     {
-        return $this->id < 2 || empty($this->gId) || $this->gId == PUN_GUEST;
+        return $this->groupId == $this->c->GROUP_GUEST
+            || $this->id < 2
+            || $this->groupId == $this->c->GROUP_UNVERIFIED;
     }
     }
 
 
     protected function getIsAdmin()
     protected function getIsAdmin()
     {
     {
-        return $this->gId == PUN_ADMIN;
+        return $this->groupId == $this->c->GROUP_ADMIN;
     }
     }
 
 
     protected function getIsAdmMod()
     protected function getIsAdmMod()
     {
     {
-        return $this->gId == PUN_ADMIN || $this->gModerator == '1';
+        return $this->groupId == $this->c->GROUP_ADMIN
+            || $this->gModerator == '1';
     }
     }
 
 
     protected function getLogged()
     protected function getLogged()
@@ -76,23 +78,31 @@ class User extends AbstractModel
 
 
     protected function getLanguage()
     protected function getLanguage()
     {
     {
-        if ($this->isGuest
-            || ! file_exists($this->c->DIR_LANG . '/' . $this->data['language'] . '/common.po')
-        ) {
-            return $this->config['o_default_lang'];
+        $langs = $this->c->Func->getLangs();
+
+        $lang = $this->isGuest || empty($this->data['language']) || ! in_array($this->data['language'], $langs)
+            ? $this->config['o_default_lang']
+            : $this->data['language'];
+
+        if (in_array($lang, $langs)) {
+            return $lang;
         } else {
         } else {
-            return $this->data['language'];
+            return isset($langs[0]) ? $langs[0] : 'English';
         }
         }
     }
     }
 
 
     protected function getStyle()
     protected function getStyle()
     {
     {
-        if ($this->isGuest
-//???            || ! file_exists($this->c->DIR_LANG . '/' . $this->data['language'])
-        ) {
-            return $this->config['o_default_style'];
+        $styles = $this->c->Func->getStyles();
+
+        $style = $this->isGuest || empty($this->data['style']) || ! in_array($this->data['style'], $styles)
+            ? $this->config['o_default_style']
+            : $this->data['style'];
+
+        if (in_array($style, $styles)) {
+            return $style;
         } else {
         } else {
-            return $this->data['style'];
+            return isset($styles[0]) ? $styles[0] : 'ForkBB';
         }
         }
     }
     }
 }
 }

+ 21 - 1
app/Models/Validator.php

@@ -70,6 +70,7 @@ class Validator
             'array'         => [$this, 'vArray'],
             'array'         => [$this, 'vArray'],
             'checkbox'      => [$this, 'vCheckbox'],
             'checkbox'      => [$this, 'vCheckbox'],
             'email'         => [$this, 'vEmail'],
             'email'         => [$this, 'vEmail'],
+            'in'            => [$this, 'vIn'],
             'integer'       => [$this, 'vInteger'],
             'integer'       => [$this, 'vInteger'],
             'login'         => [$this, 'vLogin'],
             'login'         => [$this, 'vLogin'],
             'max'           => [$this, 'vMax'],
             'max'           => [$this, 'vMax'],
@@ -186,6 +187,16 @@ class Validator
         return empty($this->errors);
         return empty($this->errors);
     }
     }
 
 
+    /**
+     * Проверяет наличие поля
+     * @param string $field
+     * @return bool
+     */
+    public function __isset($field)
+    {
+        return isset($this->result[$field]); //????
+    }
+
     /**
     /**
      * Проверяет поле согласно заданным правилам
      * Проверяет поле согласно заданным правилам
      * Возвращает значение запрашиваемого поля
      * Возвращает значение запрашиваемого поля
@@ -357,7 +368,7 @@ class Validator
             foreach(explode(',', $attr) as $action) {
             foreach(explode(',', $attr) as $action) {
                 switch ($action) {
                 switch ($action) {
                     case 'trim':
                     case 'trim':
-                        $value = trim($value);
+                        $value = preg_replace('%^\s+|\s+$%u', '', $value); // trim($value);
                         break;
                         break;
                     case 'lower':
                     case 'lower':
                         $value = mb_strtolower($value, 'UTF-8');
                         $value = mb_strtolower($value, 'UTF-8');
@@ -529,4 +540,13 @@ class Validator
     {
     {
         return $this->vRegex($v, $value, $type, '%^\p{L}[\p{L}\p{N}\x20\._-]+$%uD');
         return $this->vRegex($v, $value, $type, '%^\p{L}[\p{L}\p{N}\x20\._-]+$%uD');
     }
     }
+
+    protected function vIn($v, $value, $type, $attr)
+    {
+        if (null === $value || in_array($value, explode(',', $attr))) {
+            return [$value, $type, false];
+        } else {
+            return [null, $type, 'The :alias contains an invalid value'];
+        }
+    }
 }
 }

+ 21 - 38
app/bootstrap.php

@@ -6,9 +6,6 @@ use ForkBB\Core\Container;
 use ForkBB\Models\Pages\Page;
 use ForkBB\Models\Pages\Page;
 use RuntimeException;
 use RuntimeException;
 
 
-if (! defined('PUN_ROOT'))
-	exit('The constant PUN_ROOT must be defined and point to a valid FluxBB installation root directory.');
-
 // боевой
 // боевой
 #error_reporting(E_ALL);
 #error_reporting(E_ALL);
 #ini_set('display_errors', 0);
 #ini_set('display_errors', 0);
@@ -22,56 +19,42 @@ mb_language('uni');
 mb_internal_encoding('UTF-8');
 mb_internal_encoding('UTF-8');
 mb_substitute_character(0xFFFD);
 mb_substitute_character(0xFFFD);
 
 
-// The maximum size of a post, in characters, not bytes
-if (!defined('PUN_MAX_POSTSIZE'))
-	define('PUN_MAX_POSTSIZE', 65000);
-
-if (!defined('PUN_SEARCH_MIN_WORD'))
-	define('PUN_SEARCH_MIN_WORD', 3);
-if (!defined('PUN_SEARCH_MAX_WORD'))
-	define('PUN_SEARCH_MAX_WORD', 20);
-
-if (!defined('FORUM_MAX_COOKIE_SIZE'))
-	define('FORUM_MAX_COOKIE_SIZE', 4048);
-
-//$loader =
 require __DIR__ . '/../vendor/autoload.php';
 require __DIR__ . '/../vendor/autoload.php';
-//$loader->setPsr4('ForkBB\\', __DIR__ . '/');
 
 
 if (file_exists(__DIR__ . '/config/main.php')) {
 if (file_exists(__DIR__ . '/config/main.php')) {
-    $container = new Container(include __DIR__ . '/config/main.php');
+    $c = new Container(include __DIR__ . '/config/main.php');
 } elseif (file_exists(__DIR__ . '/config/install.php')) {
 } elseif (file_exists(__DIR__ . '/config/install.php')) {
-    $container = new Container(include __DIR__ . '/config/install.php');
+    $c = new Container(include __DIR__ . '/config/install.php');
 } else {
 } else {
-    throw new RuntimeException('Application is not configured');
+    throw new RuntimeException('Application is not configured.');
 }
 }
 
 
-define('PUN', 1);
+require __DIR__ . '/functions.php';
 
 
-$container->DIR_CONFIG = __DIR__ . '/config';
-$container->DIR_CACHE = __DIR__ . '/cache';
-$container->DIR_VIEWS = __DIR__ . '/templates';
-$container->DIR_LANG = __DIR__ . '/lang';
-$container->START = $pun_start;
+$c->FORK_REVISION = 1;
+$c->START = $forkStart;
+$c->DIR_APP    = __DIR__;
+$c->DIR_PUBLIC = $forkPublic;
+$c->DIR_CONFIG = __DIR__ . '/config';
+$c->DIR_CACHE  = __DIR__ . '/cache';
+$c->DIR_VIEWS  = __DIR__ . '/templates';
+$c->DIR_LANG   = __DIR__ . '/lang';
+$c->DATE_FORMATS = [$c->config['o_date_format'], 'Y-m-d', 'Y-d-m', 'd-m-Y', 'm-d-Y', 'M j Y', 'jS M Y'];
+$c->TIME_FORMATS = [$c->config['o_time_format'], 'H:i:s', 'H:i', 'g:i:s a', 'g:i a'];
 
 
-$config = $container->config;
-$container->date_formats = [$config['o_date_format'], 'Y-m-d', 'Y-d-m', 'd-m-Y', 'm-d-Y', 'M j Y', 'jS M Y'];
-$container->time_formats = [$config['o_time_format'], 'H:i:s', 'H:i', 'g:i:s a', 'g:i a'];
-
-$page = null;
 $controllers = ['Routing', 'Primary'];
 $controllers = ['Routing', 'Primary'];
-
+$page = null;
 while (! $page instanceof Page && $cur = array_pop($controllers)) {
 while (! $page instanceof Page && $cur = array_pop($controllers)) {
-    $page = $container->$cur;
+    $page = $c->$cur;
 }
 }
 
 
 if ($page->getDataForOnline(true)) {
 if ($page->getDataForOnline(true)) {
-    $container->Online->handle($page);
+    $c->Online->handle($page);
 }
 }
-$tpl = $container->View->setPage($page)->outputPage();
-if (defined('PUN_DEBUG')) {
-    $debug = $container->Debug->debug();
-    $debug = $container->View->setPage($debug)->outputPage();
+$tpl = $c->View->setPage($page)->outputPage();
+if ($c->DEBUG > 0) {
+    $debug = $c->Debug->debug();
+    $debug = $c->View->setPage($debug)->outputPage();
     $tpl = str_replace('<!-- debuginfo -->', $debug, $tpl);
     $tpl = str_replace('<!-- debuginfo -->', $debug, $tpl);
 }
 }
 exit($tpl);
 exit($tpl);

+ 59 - 26
app/config/install.php

@@ -1,45 +1,78 @@
 <?php
 <?php
 
 
 return [
 return [
-    'DB_TYPE'     => '_DB_TYPE_',
-    'DB_HOST'     => '_DB_HOST_',
-    'DB_USERNAME' => '_DB_USERNAME_',
-    'DB_PASSWORD' => '_DB_PASSWORD_',
-    'DB_NAME'     => '_DB_NAME_',
-    'DB_PREFIX'   => '_DB_PREFIX_',
-    'P_CONNECT'   => false,
+    'BASE_URL'    => 'http://forkbb.local',
+    'DEBUG' => 1,
+    'GROUP_UNVERIFIED' => 0,
+    'GROUP_ADMIN'      => 1,
+    'GROUP_MOD'        => 2,
+    'GROUP_GUEST'      => 3,
+    'GROUP_MEMBER'     => 4,
+    'EOL' => PHP_EOL,
+
+
     'HMAC' => [
     'HMAC' => [
         'algo' => 'sha1',
         'algo' => 'sha1',
         'salt' => '_SALT_FOR_HMAC_',
         'salt' => '_SALT_FOR_HMAC_',
     ],
     ],
+
+    'config' => [
+        'o_default_lang' => 'English',
+        'o_default_style' => 'ForkBB',
+        'o_redirect_delay' => 0,
+        'o_date_format' => 'Y-m-d',
+        'o_time_format' => 'H:i:s',
+        'o_maintenance' => 0,
+    ],
+
     'shared' => [
     'shared' => [
-        'Request' => [
-            'class' => \ForkBB\Core\Request::class,
-            'Secury' => '@Secury',
+        'Lang' => \ForkBB\Core\Lang::class,
+        'Router' => [
+            'class' => \ForkBB\Core\Router::class,
+            'base_url' => '%BASE_URL%',
+        ],
+        'View' => [
+            'class' => \ForkBB\Core\View::class,
+            'cache_dir' => '%DIR_CACHE%',
+            'views_dir' => '%DIR_VIEWS%',
         ],
         ],
-        'DBLoader' => [
-            'class' => \ForkBB\Core\DBLoader::class,
-            'db_host'     => '%DB_HOST%',
-            'db_username' => '%DB_USERNAME%',
-            'db_password' => '%DB_PASSWORD%',
-            'db_name'     => '%DB_NAME%',
-            'db_prefix'   => '%DB_PREFIX%',
-            'p_connect'   => '%P_CONNECT%',
+        'Func' => \ForkBB\Core\Func::class,
+        'Validator' => \ForkBB\Models\Validator::class,
+        'Mail' => [
+            'class' => \ForkBB\Core\Mail::class,
+            'host' => '',
+            'user' => '',
+            'pass' => '',
+            'ssl' => '',
+            'eol' => '%EOL%',
         ],
         ],
         'DB' => [
         'DB' => [
-            'factory method' => '@DBLoader:load',
-            'type' => '%DB_TYPE%',
+            'class' => \ForkBB\Core\DB::class,
+            'dsn'      => '%DB_DSN%',
+            'username' => '%DB_USERNAME%',
+            'password' => '%DB_PASSWORD%',
+            'options'  => '%DB_OPTIONS%',
+            'prefix'   => '%DB_PREFIX%',
         ],
         ],
-        'Install' => [
-            'class' => \ForkBB\Core\Install::class,
-            'request' => '@Request',
-            'container' => '%CONTAINER%',
+
+
+
+
+        'Request' => [
+            'class' => \ForkBB\Core\Request::class,
+            'Secury' => '@Secury',
         ],
         ],
         'Secury' => [
         'Secury' => [
             'class' => \ForkBB\Core\Secury::class,
             'class' => \ForkBB\Core\Secury::class,
             'hmac' => '%HMAC%',
             'hmac' => '%HMAC%',
         ],
         ],
-        'Primary' => '@Install:install',
     ],
     ],
-    'multiple'  => [],
+    'multiple'  => [
+        'PrimaryController' => \ForkBB\Controllers\Install::class,
+        'Primary' => '@PrimaryController:routing',
+
+        'Install' => \ForkBB\Models\Pages\Install::class,
+        'Redirect' => \ForkBB\Models\Pages\Redirect::class,
+        'Debug' => \ForkBB\Models\Pages\Debug::class,
+    ],
 ];
 ];

+ 31 - 0
app/functions.php

@@ -0,0 +1,31 @@
+<?php
+
+function __($data, ...$args)
+{
+    static $lang;
+
+    if (empty($lang)) {
+        $lang = $data;
+        return;
+    }
+
+    $tr = $lang->get($data);
+
+    if (is_array($tr)) {
+        if (isset($args[0]) && is_numeric($args[0])) {
+            $n = array_shift($args);
+            eval('$n = (int) ' . $tr['plural']);
+            $tr = $tr[$n];
+        } else {
+            $tr = $tr[0];
+        }
+    }
+
+    if (empty($args)) {
+        return $tr;
+    } elseif (is_array($args[0])) {
+        return strtr($tr, $args[0]);
+    } else {
+        return sprintf($tr, ...$args);
+    }
+}

+ 223 - 0
app/lang/English/install.po

@@ -0,0 +1,223 @@
+#
+msgid ""
+msgstr ""
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"Project-Id-Version: ForkBB\n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: ForkBB <mio.visman@yandex.ru>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en\n"
+
+msgid "ForkBB Installation"
+msgstr "ForkBB Installation"
+
+msgid "Welcome"
+msgstr "You are about to install ForkBB. In order to install ForkBB, you must complete the form set out below. If you encounter any difficulties with the installation, please refer to the documentation."
+
+msgid "Choose install language"
+msgstr "Choose the install script language"
+
+msgid "Install language"
+msgstr "Install language"
+
+msgid "Choose install language info"
+msgstr "The language used for this install script. The default language used for the board itself can be set below."
+
+msgid "Change language"
+msgstr "Change language"
+
+msgid "Install"
+msgstr "Install ForkBB rev.%s"
+
+msgid "Database setup"
+msgstr "Database setup"
+
+msgid "Info 1"
+msgstr "All information we need to create a connection with your database."
+
+msgid "Database type"
+msgstr "Database type"
+
+msgid "Info 2"
+msgstr "Select a database. We support SQLite, MySQL and PostgreSQL."
+
+msgid "Database server hostname"
+msgstr "Database server hostname"
+
+msgid "Info 3"
+msgstr "You should be able to get this info from your web host, if <code>localhost</code> does not work."
+
+msgid "Database name"
+msgstr "Database name"
+
+msgid "Info 4"
+msgstr "The name of the database you want to install ForkBB on."
+
+msgid "Database username"
+msgstr "Database username"
+
+msgid "Database password"
+msgstr "Database password"
+
+msgid "Info 5"
+msgstr "Your database username and password (ignore of SQLite)."
+
+msgid "Table prefix"
+msgstr "Table prefix"
+
+msgid "Info 6"
+msgstr "If you want to run multiple ForkBB installations in a single database, change this. The prefix may contain the letters a to z, any numbers and the underscore character. They must however start with a letter. The maximum length is 40 characters."
+
+msgid "Administration setup"
+msgstr "Administration setup"
+
+msgid "Info 7"
+msgstr "Create the very first account on your board."
+
+msgid "Administrator username"
+msgstr "Administrator's username"
+
+msgid "Info 8"
+msgstr "The username must begin with a letter. May contain letters, numbers, spaces, dots, dashes and underscores."
+
+msgid "Administrator password"
+msgstr "Administrator's password"
+
+msgid "Info 9"
+msgstr "Password must contain the digit, uppercase and lowercase letters, symbol different from the digits and letters. Passwords must be at least 8 characters long. Passwords are case sensitive."
+
+msgid "Administrator email"
+msgstr "Administrator's email"
+
+msgid "Info 10"
+msgstr "Please enter a valid email address."
+
+msgid "Board setup"
+msgstr "Board setup"
+
+msgid "Info 11"
+msgstr "Settings for your board. You can change this later."
+
+msgid "Board title"
+msgstr "Board title"
+
+msgid "Board description"
+msgstr "Board description (supports HTML)"
+
+msgid "Base URL"
+msgstr "The URL of your forum"
+
+msgid "Default language"
+msgstr "Default language"
+
+msgid "Default style"
+msgstr "Default style"
+
+msgid "Start install"
+msgstr "Start install"
+
+msgid "You are running error"
+msgstr "You are running %1$s version %2$s. ForkBB rev.%3$s requires at least %1$s %4$s to run properly. You must upgrade your %1$s installation before you can continue."
+
+msgid "Alert folder"
+msgstr "In order for ForkBB to function properly, the directory <em>%s</em> must be writable by PHP. Use chmod to set the appropriate directory permissions. If in doubt, chmod to 0777."
+
+msgid "No styles"
+msgstr "No styles."
+
+msgid "No DB extensions"
+msgstr "This PHP environment does not have support for any of the databases that ForkBB supports. PDO must be enabled."
+
+msgid "My ForkBB Forum"
+msgstr "My ForkBB Forum"
+
+msgid "Description"
+msgstr "Forum powered by open source software - ForkBB."
+
+msgid "Wrong email"
+msgstr "The administrator email address you entered is invalid."
+
+msgid "Table prefix error"
+msgstr "The table prefix '%s' does not match the format."
+
+msgid "Prefix reserved"
+msgstr "The table prefix 'sqlite_' is reserved for use by the SQLite engine."
+
+msgid "Existing table error"
+msgstr "A table called '%susers' is already present in the database '%s'. This could mean that ForkBB is already installed or that another piece of software is installed and is occupying one or more of the table names ForkBB requires. If you want to install multiple copies of ForkBB in the same database, you must choose a different table prefix."
+
+msgid "Administrators"
+msgstr "Administrators"
+
+msgid "Administrator"
+msgstr "Administrator"
+
+msgid "Moderators"
+msgstr "Moderators"
+
+msgid "Moderator"
+msgstr "Moderator"
+
+msgid "Guests"
+msgstr "Guests"
+
+msgid "Guest"
+msgstr "Guest"
+
+msgid "Members"
+msgstr "Members"
+
+msgid "Announcement"
+msgstr "Enter your announcement here."
+
+msgid "Rules"
+msgstr "Enter your rules here"
+
+msgid "Maintenance message"
+msgstr "The forums are temporarily down for maintenance. Come back later."
+
+msgid "Test post"
+msgstr "Test topic"
+
+msgid "Test message"
+msgstr "If you are looking at this (which I guess you are), the install of ForkBB appears to have worked! Now log in and head over to the administration control panel to configure your forum."
+
+msgid "Test category"
+msgstr "Test category"
+
+msgid "Test forum"
+msgstr "Test forum"
+
+msgid "This is just a test forum"
+msgstr "This is just a test forum"
+
+msgid "Alert cache"
+msgstr "<strong>The cache directory is currently not writable!</strong> In order for ForkBB to function properly, the directory <em>%s</em> must be writable by PHP. Use chmod to set the appropriate directory permissions. If in doubt, chmod to 0777."
+
+msgid "Alert avatar"
+msgstr "<strong>The avatar directory is currently not writable!</strong> If you want users to be able to upload their own avatar images you must see to it that the directory <em>%s</em> is writable by PHP. You can later choose to save avatar images in a different directory (see Admin/Options). Use chmod to set the appropriate directory permissions. If in doubt, chmod to 0777."
+
+msgid "Alert upload"
+msgstr "<strong>File uploads appear to be disallowed on this server!</strong> If you want users to be able to upload their own avatar images you must enable the file_uploads configuration setting in PHP. Once file uploads have been enabled, avatar uploads can be enabled in Administration/Options/Features."
+
+msgid "ForkBB has been installed"
+msgstr "ForkBB has been installed. To finalize the installation please follow the instructions below."
+
+msgid "Final instructions"
+msgstr "Final instructions"
+
+msgid "Info 17"
+msgstr "To finalize the installation, you need to click on the button below to download a file called main.php. You then need to upload this file to directory /app/config (near the main.dist.php file) of your ForkBB installation."
+
+msgid "Info 18"
+msgstr "Once you have uploaded main.php, ForkBB will be fully installed! At that point, you may <a href=\"index.php\">go to the forum index</a>."
+
+msgid "Download main.php file"
+msgstr "Download main.php file"
+
+msgid "ForkBB fully installed"
+msgstr "ForkBB has been fully installed! You may now <a href=\"index.php\">go to the forum index</a>."

+ 223 - 0
app/lang/Russian/install.po

@@ -0,0 +1,223 @@
+#
+msgid ""
+msgstr ""
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"Project-Id-Version: ForkBB\n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: ForkBB <mio.visman@yandex.ru>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: ru\n"
+
+msgid "ForkBB Installation"
+msgstr "Установка ForkBB"
+
+msgid "Welcome"
+msgstr "Вы собираетесь установить ForkBB. Для этого заполните ниже представленную форму. Если вы столкнетесь с трудностями в процессе установки, пожалуйста, обратитесь к документации."
+
+msgid "Choose install language"
+msgstr "Язык скрипта установки"
+
+msgid "Install language"
+msgstr "Язык"
+
+msgid "Choose install language info"
+msgstr "Язык, на котором будет отображаться информация во время установки форума. Для форума язык по умолчанию можно задать отдельно, ниже."
+
+msgid "Change language"
+msgstr "Изменить язык"
+
+msgid "Install"
+msgstr "Установка ForkBB rev.%s"
+
+msgid "Database setup"
+msgstr "Настройки базы данных"
+
+msgid "Info 1"
+msgstr "Полная информация о подключаемой базе данных."
+
+msgid "Database type"
+msgstr "Тип базы данных"
+
+msgid "Info 2"
+msgstr "Выберите тип базы данных. Форум поддерживает SQLite, MySQL и PostgreSQL."
+
+msgid "Database server hostname"
+msgstr "Адрес сервера базы данных"
+
+msgid "Info 3"
+msgstr "Вы можете уточнить адрес у хостера, если <code>localhost</code> не работает."
+
+msgid "Database name"
+msgstr "Имя базы данных"
+
+msgid "Info 4"
+msgstr "Имя базы данных, в которую будет установлен ForkBB."
+
+msgid "Database username"
+msgstr "Имя пользователя базы данных"
+
+msgid "Database password"
+msgstr "Пароль к базе данных"
+
+msgid "Info 5"
+msgstr "Введите имя пользователя и пароль для подключения к базе данных (для SQLite оставьте пустыми)."
+
+msgid "Table prefix"
+msgstr "Префикс таблиц"
+
+msgid "Info 6"
+msgstr "Если требуется установить несколько форумов ForkBB в одну базу данных, измените это поле. Префикс может содержать буквы от a до z (латиница), цифры и знак подчеркивания. Первой должна идти буква. Максимальная длина 40 символов."
+
+msgid "Administration setup"
+msgstr "Настройки администратора"
+
+msgid "Info 7"
+msgstr "Создайте первый аккаунт на вашем форуме."
+
+msgid "Administrator username"
+msgstr "Имя администратора"
+
+msgid "Info 8"
+msgstr "Имя должно начинаться с буквы. Может содержать буквы, цифры, пробел, точку, дефис и знак подчеркивания."
+
+msgid "Administrator password"
+msgstr "Пароль администратора"
+
+msgid "Info 9"
+msgstr "Пароль должен содержать цифру, строчную и прописную буквы, символ отличающийся от цифр и букв. Пароль должен состоять минимум из 8 символов. Пароль чувствителен к регистру вводимых букв."
+
+msgid "Administrator email"
+msgstr "Почта администратора"
+
+msgid "Info 10"
+msgstr "Укажите действующий почтовый адрес."
+
+msgid "Board setup"
+msgstr "Настройки форума"
+
+msgid "Info 11"
+msgstr "Настройки вашего форума. Можете изменить их позже."
+
+msgid "Board title"
+msgstr "Заголовок форума"
+
+msgid "Board description"
+msgstr "Описание форума (поддерживает HTML)"
+
+msgid "Base URL"
+msgstr "URL форума"
+
+msgid "Default language"
+msgstr "Язык по умолчанию"
+
+msgid "Default style"
+msgstr "Стиль по умолчанию"
+
+msgid "Start install"
+msgstr "Начать установку"
+
+msgid "You are running error"
+msgstr "Вы используете %1$s версии %2$s. ForkBB rev.%3$s требует как минимум %1$s %4$s для своей работы. Вы должны обновить ваш %1$s, прежде чем продолжить."
+
+msgid "Alert folder"
+msgstr "Для правильного функционирования ForkBB директория <em>%s</em> должна быть открыта для записи из PHP. Используйте chmod для установки прав на директорию. Если сомневаетесь, то установите права 0777."
+
+msgid "No styles"
+msgstr "Нет стилей."
+
+msgid "No DB extensions"
+msgstr "Ваш PHP не поддерживает ни одного типа БД поддерживаемого ForkBB. PDO должен быть включен."
+
+msgid "My ForkBB Forum"
+msgstr "Мой ForkBB форум"
+
+msgid "Description"
+msgstr "Форум работает на движке с открытым исходным кодом - ForkBB."
+
+msgid "Wrong email"
+msgstr "Ошибка в email администратора."
+
+msgid "Table prefix error"
+msgstr "Префикс '%s' не соответсвует формату."
+
+msgid "Prefix reserved"
+msgstr "Префикс 'sqlite_' зарезрвирован для SQLite."
+
+msgid "Existing table error"
+msgstr "Таблица '%susers' уже существует в базе '%s'. Это может означать, что ForkBB был установлен в данную базу или другой программный пакет занимает таблицы, требуемые для работы ForkBB. Если вы хотите установить несколько копий форума в одну базу, выбирайте разные префиксы для них."
+
+msgid "Administrators"
+msgstr "Администраторы"
+
+msgid "Administrator"
+msgstr "Администратор"
+
+msgid "Moderators"
+msgstr "Модераторы"
+
+msgid "Moderator"
+msgstr "Модератор"
+
+msgid "Guests"
+msgstr "Гости"
+
+msgid "Guest"
+msgstr "Гость"
+
+msgid "Members"
+msgstr "Пользователи"
+
+msgid "Announcement"
+msgstr "Тут можно задать свое объявление"
+
+msgid "Rules"
+msgstr "Тут можно указать правила форума"
+
+msgid "Maintenance message"
+msgstr "Форум временно переведен в режим обслуживания. Зайдите позже."
+
+msgid "Test post"
+msgstr "Тестовая тема"
+
+msgid "Test message"
+msgstr "Если вы видите это сообщение, то установка ForkBB закончена и форум работает! Теперь перейдите в админку (войдите на форум под администратором) и сконфигурируйте форум."
+
+msgid "Test category"
+msgstr "Тестовая категория"
+
+msgid "Test forum"
+msgstr "Тестовый раздел"
+
+msgid "This is just a test forum"
+msgstr "Этот раздел создан при установке форума"
+
+msgid "Alert cache"
+msgstr "<strong>Папка кэша заблокирована для записи!</strong> Для правильного функционирования ForkBB директория <em>%s</em> должна быть открыта для записи из PHP. Используйте chmod для установки прав на директорию. Если сомневаетесь, то установите права 0777."
+
+msgid "Alert avatar"
+msgstr "<strong>Папка для аватар заблокирована для записи!</strong> Если вы хотите, чтобы пользователи форума использовали аватары, вы должны разрешить запись в директорию <em>%s</em> для PHP. Позже вы можете сменить директорию хранения аватар (смотрите Админка/Опции). Используйте chmod для установки прав на директорию. Если сомневаетесь, то установите права 0777."
+
+msgid "Alert upload"
+msgstr "<strong>Загрузка файлов, кажется, выключена на этом сервере!</strong> Если вы хотите, чтобы пользователи форума использовали аватары, вы должны разрешить file_uploads в настройках вашего PHP. После разрешения загрузки файлов на сервер, вы можете разрешить использования аватар для пользователей форума (смотрите Админка/Опции)."
+
+msgid "ForkBB has been installed"
+msgstr "ForkBB установлен. Для завершения следуйте ниже приведенной инструкции."
+
+msgid "Final instructions"
+msgstr "Заключительная инструкция"
+
+msgid "Info 17"
+msgstr "Чтобы завершить установку форума, нажмите кнопку ниже для скачивания файла main.php. После этого запишите этот файл в директорию /app/config (рядом с файлом main.dist.php) вашего форума."
+
+msgid "Info 18"
+msgstr "Как только вы загрузите main.php на форум, ForkBB будет полностью установлен! После этого <a href="index.php">перейти на главную страницу форума</a>."
+
+msgid "Download main.php file"
+msgstr "Скачать файл main.php"
+
+msgid "ForkBB fully installed"
+msgstr "ForkBB полностью установлен! Теперь вы можете <a href="index.php">перейти на главную страницу форума</a>."

+ 167 - 0
app/templates/layouts/install.tpl

@@ -0,0 +1,167 @@
+<!DOCTYPE html>
+<html lang="{!! $fLang !!}" dir="{!! $fDirection !!}">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>{!! __('ForkBB Installation') !!}</title>
+@foreach($pageHeads as $cur)
+  <{!! $cur !!}>
+@endforeach
+</head>
+<body>
+  <div class="f-wrap">
+    <header class="f-header">
+      <div class="f-title">
+        <h1>{!! __('ForkBB Installation') !!}</h1>
+        <p class="f-description">{!! __('Welcome') !!}</p>
+      </div>
+    </header>
+@if($fIswev)
+@include('layouts/iswev')
+@endif
+@if(is_array($installLangs))
+    <section class="f-install">
+      <div class="f-lrdiv">
+        <h2>{!! __('Choose install language') !!}</h2>
+        <form class="f-form" method="post" action="{!! $formAction !!}">
+          <div>
+            <label class="f-child1">{!! __('Install language') !!}</label>
+            <select class="f-ctrl" id="id-installlang" name="installlang">
+@foreach($installLangs as $cur)
+@if(isset($cur[1]))
+              <option value="{{ $cur[0] }}" selected>{{ $cur[0] }}</option>
+@else
+              <option value="{{ $cur[0] }}">{{ $cur[0] }}</option>
+@endif
+@endforeach
+            </select>
+            <label class="f-child4">{!! __('Choose install language info') !!}</label>
+          </div>
+          <div>
+            <input class="f-btn" type="submit" name="changelang" value="{!! __('Change language') !!}">
+          </div>
+        </form>
+      </div>
+    </section>
+@endif
+@if(empty($fIswev['e']))
+    <section class="f-main f-install">
+      <div class="f-lrdiv">
+        <h2>{!! __('Install', $rev) !!}</h2>
+        <form class="f-form" method="post" action="{!! $formAction !!}" autocomplete="off">
+          <input type="hidden" name="installlang" value="{!! $installLang !!}">
+          <div class="f-finfo">
+            <h3>{!! __('Database setup') !!}</h3>
+            <p>{!! __('Info 1') !!}</p>
+          </div>
+          <div>
+            <label class="f-child1 f-req">{!! __('Database type') !!}</label>
+            <select class="f-ctrl" id="id-dbtype" name="dbtype">
+@foreach($dbTypes as $key => $cur)
+@if(empty($cur[1]))
+              <option value="{{ $key }}">{{ $cur[0] }}</option>
+@else
+              <option value="{{ $key }}" selected>{{ $cur[0] }}</option>
+@endif
+@endforeach
+            </select>
+            <label class="f-child4">{!! __('Info 2') !!}</label>
+          </div>
+          <div>
+            <label class="f-child1 f-req" for="id-dbhost">{!! __('Database server hostname') !!}</label>
+            <input required class="f-ctrl" id="id-dbhost" type="text" name="dbhost" value="{{ $dbhost }}">
+            <label class="f-child4">{!! __('Info 3') !!}</label>
+          </div>
+          <div>
+            <label class="f-child1 f-req" for="id-dbname">{!! __('Database name') !!}</label>
+            <input required class="f-ctrl" id="id-dbname" type="text" name="dbname" value="{{ $dbname }}">
+            <label class="f-child4">{!! __('Info 4') !!}</label>
+          </div>
+          <div>
+            <label class="f-child1" for="id-dbuser">{!! __('Database username') !!}</label>
+            <input class="f-ctrl" id="id-dbuser" type="text" name="dbuser" value="{{ $dbuser }}">
+          </div>
+          <div>
+            <label class="f-child1" for="id-dbpass">{!! __('Database password') !!}</label>
+            <input class="f-ctrl" id="id-dbpass" type="password" name="dbpass">
+            <label class="f-child4">{!! __('Info 5') !!}</label>
+          </div>
+          <div>
+            <label class="f-child1" for="id-dbprefix">{!! __('Table prefix') !!}</label>
+            <input class="f-ctrl" id="id-dbprefix" type="text" name="dbprefix" value="{{ $dbprefix }}" maxlength="40" pattern="^[a-zA-Z][a-zA-Z\d_]*$">
+            <label class="f-child4">{!! __('Info 6') !!}</label>
+          </div>
+          <div class="f-finfo">
+            <h3>{!! __('Administration setup') !!}</h3>
+            <p>{!! __('Info 7') !!}</p>
+          </div>
+          <div>
+            <label class="f-child1 f-req" for="id-username">{!! __('Administrator username') !!}</label>
+            <input required class="f-ctrl" id="id-username" type="text" name="username" value="{{ $username }}" maxlength="25" pattern="^.{2,25}$">
+            <label class="f-child4">{!! __('Info 8') !!}</label>
+          </div>
+          <div>
+            <label class="f-child1 f-req" for="id-password">{!! __('Administrator password') !!}</label>
+            <input required class="f-ctrl" id="id-password" type="password" name="password" pattern=".{8,}">
+            <label class="f-child4">{!! __('Info 9') !!}</label>
+          </div>
+          <div>
+            <label class="f-child1 f-req" for="id-email">{!! __('Administrator email') !!}</label>
+            <input required class="f-ctrl" id="id-email" type="text" name="email" value="{{ $email }}" maxlength="80" pattern=".+@.+">
+            <label class="f-child4">{!! __('Info 10') !!}</label>
+          </div>
+          <div class="f-finfo">
+            <h3>{!! __('Board setup') !!}</h3>
+            <p>{!! __('Info 11') !!}</p>
+          </div>
+          <div>
+            <label class="f-child1 f-req" for="id-title">{!! __('Board title') !!}</label>
+            <input required class="f-ctrl" id="id-title" type="text" name="title" value="{{ $title }}">
+          </div>
+          <div>
+            <label class="f-child1 f-req" for="id-descr">{!! __('Board description') !!}</label>
+            <input required class="f-ctrl" id="id-descr" type="text" name="descr" value="{{ $descr }}">
+          </div>
+          <div>
+            <label class="f-child1 f-req" for="id-baseurl">{!! __('Base URL') !!}</label>
+            <input required class="f-ctrl" id="id-baseurl" type="text" name="baseurl" value="{{ $baseurl }}">
+          </div>
+@if(is_array($defaultLangs))
+          <div>
+            <label class="f-child1 f-req">{!! __('Default language') !!}</label>
+            <select class="f-ctrl" id="id-defaultlang" name="defaultlang">
+@foreach($defaultLangs as $cur)
+@if(isset($cur[1]))
+              <option value="{{ $cur[0] }}" selected>{{ $cur[0] }}</option>
+@else
+              <option value="{{ $cur[0] }}">{{ $cur[0] }}</option>
+@endif
+@endforeach
+            </select>
+          </div>
+@else
+          <input type="hidden" name="defaultlang" value="{!! $defaultLangs !!}">
+@endif
+          <div>
+            <label class="f-child1 f-req">{!! __('Default style') !!}</label>
+            <select class="f-ctrl" id="id-defaultstyle" name="defaultstyle">
+@foreach($defaultStyles as $cur)
+@if(isset($cur[1]))
+              <option value="{{ $cur[0] }}" selected>{{ $cur[0] }}</option>
+@else
+              <option value="{{ $cur[0] }}">{{ $cur[0] }}</option>
+@endif
+@endforeach
+            </select>
+          </div>
+          <div>
+            <input class="f-btn" type="submit" name="startinstall" value="{!! __('Start install') !!}">
+          </div>
+        </form>
+      </div>
+    </section>
+@endif
+<!-- debuginfo -->
+  </div>
+</body>
+</html>

+ 2 - 3
app/templates/layouts/main.tpl

@@ -4,9 +4,8 @@
   <meta charset="utf-8">
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>{{ $pageTitle }}</title>
   <title>{{ $pageTitle }}</title>
-  <link rel="stylesheet" type="text/css" href="http://forkbb.local/public/style/ForkBB/style.css">
-@foreach($pageHeads as $v)
-  {!! $v !!}
+@foreach($pageHeads as $cur)
+  <{!! $cur !!}>
 @endforeach
 @endforeach
 </head>
 </head>
 <body>
 <body>

+ 2 - 3
app/templates/layouts/redirect.tpl

@@ -5,9 +5,8 @@
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <meta http-equiv="refresh" content="{!! $Timeout !!};URL={{ $Link }}">
   <meta http-equiv="refresh" content="{!! $Timeout !!};URL={{ $Link }}">
   <title>{{ $pageTitle }}</title>
   <title>{{ $pageTitle }}</title>
-  <link rel="stylesheet" type="text/css" href="http://forkbb.local/public/style/ForkBB/style.css">
-@foreach($pageHeads as $v)
-  {!! $v !!}
+@foreach($pageHeads as $cur)
+  <{!! $cur !!}>
 @endforeach
 @endforeach
 </head>
 </head>
 <body>
 <body>

+ 1 - 1
app/templates/register.tpl

@@ -13,7 +13,7 @@
           </div>
           </div>
           <div>
           <div>
             <label class="f-child1 f-req" for="id-username">{!! __('Username') !!}</label>
             <label class="f-child1 f-req" for="id-username">{!! __('Username') !!}</label>
-            <input required class="f-ctrl" id="id-username" type="text" name="username" value="{{ $username }}" maxlength="25" pattern=".{2,25}" spellcheck="false" tabindex="2">
+            <input required class="f-ctrl" id="id-username" type="text" name="username" value="{{ $username }}" maxlength="25" pattern="^.{2,25}$" spellcheck="false" tabindex="2">
             <label class="f-child4 f-fhint">{!! __('Login format') !!}</label>
             <label class="f-child4 f-fhint">{!! __('Login format') !!}</label>
           </div>
           </div>
           <div>
           <div>

+ 1 - 1
.htaccess → public/.htaccess

@@ -5,7 +5,7 @@ AddDefaultCharset UTF-8
   #RewriteBase /
   #RewriteBase /
   RewriteCond %{REQUEST_FILENAME} !-f
   RewriteCond %{REQUEST_FILENAME} !-f
   RewriteCond %{REQUEST_FILENAME} !-d
   RewriteCond %{REQUEST_FILENAME} !-d
-  RewriteCond %{REQUEST_URI} !/public/.*\.\w+$
+  #RewriteCond %{REQUEST_URI} !/public/.*\.\w+$
   RewriteCond %{REQUEST_URI} !favicon\.ico
   RewriteCond %{REQUEST_URI} !favicon\.ico
   RewriteRule . index.php [L]
   RewriteRule . index.php [L]
 </IfModule>
 </IfModule>

+ 0 - 0
favicon.ico → public/favicon.ico


+ 6 - 0
public/index.php

@@ -0,0 +1,6 @@
+<?php
+
+$forkStart = empty($_SERVER['REQUEST_TIME_FLOAT']) ? microtime(true) : $_SERVER['REQUEST_TIME_FLOAT'];
+$forkPublic = __DIR__;
+
+require __DIR__ . '/../app/bootstrap.php';

+ 0 - 0
robots.txt → public/robots.txt


+ 21 - 0
public/style/ForkBB/style.css

@@ -1089,3 +1089,24 @@ li + li .f-btn {
   margin-bottom: 1rem;
   margin-bottom: 1rem;
   max-height: 1000rem;
   max-height: 1000rem;
 }
 }
+
+/*************/
+/* Установка */
+/*************/
+
+.f-install .f-lrdiv {
+  margin: 1rem 0;
+  max-width: 100%;
+}
+
+.f-lrdiv .f-finfo {
+  margin: 1rem -0.625rem;
+  background-color: #AA7939;
+  padding: 0.625rem;
+  color: #F8F4E3;
+}
+
+.f-install .f-ctrl + .f-child4 {
+  margin-top: -0.375rem;
+  margin-bottom: 1rem;
+}