Visman 7 年 前
コミット
58b807cb87

+ 1 - 5
.gitignore

@@ -2,8 +2,4 @@
 /app/config/main.php
 /app/cache/**/*.php
 /app/cache/**/*.lock
-/img/avatars/*
-!/img/avatars/index.html
-/img/members/*
-!/img/members/.htaccess
-!/img/members/nofile.gif
+/public/avatar/*

+ 4 - 2
app/Controllers/Routing.php

@@ -82,8 +82,10 @@ class Routing
             $r->add('GET', '/forum/{id:[1-9]\d*}[/{name}][/page/{page:[1-9]\d*}]', 'Forum:view', 'Forum');
             // темы
             $r->add('GET', '/topic/{id:[1-9]\d*}[/{name}][/page/{page:[1-9]\d*}]', 'Topic:view', 'Topic');
-            $r->add('GET', '/topic/{id:[1-9]\d*}/go_to/new', 'Topic:goToNew', 'TopicGoToNew');
-            $r->add('GET', '/topic/{id:[1-9]\d*}/go_to/unread', 'Topic:goToUnread', 'TopicGoToUnread');
+            $r->add('GET', '/topic/{id:[1-9]\d*}/goto/new', 'Topic:goToNew', 'TopicGoToNew');
+            $r->add('GET', '/topic/{id:[1-9]\d*}/goto/unread', 'Topic:goToUnread', 'TopicGoToUnread');
+            $r->add('GET', '/topic/{id:[1-9]\d*}/goto/last', 'Topic:goToLast', 'TopicGoToLast');
+            $r->add('GET', '/topic/{id:[1-9]\d*}/new/post', 'Post:new', 'NewPost');
             // сообщения
             $r->add('GET', '/post/{id:[1-9]\d*}#p{id}', 'Topic:viewPost', 'ViewPost'); //????
 

+ 1 - 1
app/Core/Container.php

@@ -67,7 +67,7 @@ class Container
             if (is_array($service)) {
                 return $this->fromArray($service, $tree);
             } elseif (is_object($service)) {
-                return $service->$tree[0];
+                return $service->{$tree[0]};
             } else {
                 return null;
             }

+ 0 - 2
app/Core/Func.php

@@ -99,8 +99,6 @@ class Func
                 }
                 if ($i === $cur) {
                     $pages[] = [null, $i, true];
-                } elseif ($i === 1) {
-                    $pages[] = [$this->c->Router->link($marker, $args), $i, null];
                 } else {
                     $pages[] = [$this->c->Router->link($marker, ['page' => $i] + $args), $i, null];
                 }

+ 3 - 1
app/Core/Router.php

@@ -97,13 +97,15 @@ class Router
      */
     public function link($marker = null, array $args = [])
     {
-        $result = $this->baseUrl; //???? http и https
+        $result = $this->baseUrl;
         if (is_string($marker) && isset($this->links[$marker])) {
             $s = $this->links[$marker];
             foreach ($args as $key => $val) {
                 if ($key == '#') {
                     $s .= '#' . rawurlencode($val); //????
                     continue;
+                } elseif ($key == 'page' && $val === 1) {
+                    continue;
                 }
                 $s = preg_replace(
                     '%\{' . preg_quote($key, '%') . '(?::[^{}]+)?\}%',

+ 7 - 9
app/Models/Pages/Forum.php

@@ -79,7 +79,7 @@ class Forum extends Page
                 return $this->c->Message->message('Bad request');
             }
 
-            $offset = $user->dispTopics * ($page - 1);
+            $offset = ($page - 1) * $user->dispTopics;
 
             switch ($curForum['sort_by']) {
                 case 1:
@@ -132,10 +132,7 @@ class Forum extends Page
             $topics = $this->c->DB->query($sql, $vars)->fetchAll();
 
             foreach ($topics as &$cur) {
-                // цензура
-                if ($this->config['o_censoring'] == '1') {
-                    $cur['subject'] = preg_replace($this->c->censoring[0], $this->c->censoring[1], $cur['subject']);
-                }
+                $cur['subject'] = $this->censor($cur['subject']);
                 // перенос темы
                 if ($cur['moved_to']) {
                     $cur['link'] = $this->c->Router->link('Topic', ['id' => $cur['moved_to'], 'name' => $cur['subject']]);
@@ -155,8 +152,8 @@ class Forum extends Page
 
                 $cur['link'] = $this->c->Router->link('Topic', ['id' => $cur['id'], 'name' => $cur['subject']]);
                 $cur['link_last'] = $this->c->Router->link('ViewPost', ['id' => $cur['last_post_id']]);
-                $cur['num_views'] = $this->config['o_topic_views'] == '1' ? $this->number($cur['num_views']) : null;
-                $cur['num_replies'] = $this->number($cur['num_replies']);
+                $cur['views'] = $this->config['o_topic_views'] == '1' ? $this->number($cur['num_views']) : null;
+                $cur['replies'] = $this->number($cur['num_replies']);
                 $time = $cur['last_post'];
                 $cur['last_post'] = $this->time($cur['last_post']);
                 // для гостя пусто
@@ -191,6 +188,7 @@ class Forum extends Page
             || ($user->isAdmMod && isset($moders[$user->id]));
 
         $this->onlinePos = 'forum-' . $args['id'];
+
         $crumbs = [];
         $id = $args['id'];
         $activ = true;
@@ -223,8 +221,8 @@ class Forum extends Page
             'pages' => $this->c->Func->paginate($pages, $page, 'Forum', ['id' => $args['id'], 'name' => $fDesc[$args['id']]['forum_name']]),
         ];
 
-        $this->canonical = $page > 1 ? $this->c->Router->link('Forum', ['id' => $args['id'], 'name' => $fDesc[$args['id']]['forum_name'], 'page' => $page])
-            : $this->c->Router->link('Forum', ['id' => $args['id'], 'name' => $fDesc[$args['id']]['forum_name']]);
+        $this->canonical = $this->c->Router->link('Forum', ['id' => $args['id'], 'name' => $fDesc[$args['id']]['forum_name'], 'page' => $page]);
+
         return $this;
     }
 }

+ 14 - 0
app/Models/Pages/Page.php

@@ -407,6 +407,20 @@ abstract class Page
         }
     }
 
+    /**
+     * Выполняет цензуру при необходимости
+     * @param string $str
+     * @return string
+     */
+    protected function censor($str)
+    {
+        if ($this->config['o_censoring'] == '1') {
+            return (string) preg_replace($this->c->censoring[0], $this->c->censoring[1],  $str);
+        } else {
+            return $str;
+        }
+    }
+
     /**
      * Заглушка
      * @param string $name

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

@@ -115,9 +115,7 @@ class Register extends Page
         if (preg_match('%^(guest|' . preg_quote(__('Guest'), '%') . ')$%iu', $username)) {
             $error = __('Username guest');
         // цензура
-        } elseif ($this->config['o_censoring'] == '1'
-            && preg_replace($this->c->censoring[0], $this->c->censoring[1], $username) !== $username
-        ) {
+        } elseif ($this->censor($username) !== $username) {
             $error = __('Username censor');
         // username забанен
         } elseif ($this->c->CheckBans->isBanned($username) > 0) {

+ 338 - 0
app/Models/Pages/Topic.php

@@ -0,0 +1,338 @@
+<?php
+
+namespace ForkBB\Models\Pages;
+
+class Topic extends Page
+{
+    use UsersTrait;
+
+    /**
+     * Имя шаблона
+     * @var string
+     */
+    protected $nameTpl = 'topic';
+
+    /**
+     * Позиция для таблицы онлайн текущего пользователя
+     * @var null|string
+     */
+    protected $onlinePos = 'topic';
+
+    /**
+     * Подготовка данных для шаблона
+     * @param array $args
+     * @return Page
+     */
+     public function goToNew(array $args)
+     {
+
+     }
+
+    /**
+     * Подготовка данных для шаблона
+     * @param array $args
+     * @return Page
+     */
+     public function goToUnread(array $args)
+     {
+         
+     }
+
+    /**
+     * Переход к последнему сообщению темы
+     * @param array $args
+     * @return Page
+     */
+     public function goToLast(array $args)
+     {
+        $vars = [
+            ':tid' => $args['id'],
+        ];
+        $sql = 'SELECT MAX(id) FROM ::posts WHERE topic_id=?i:tid';
+
+        $pid = $this->c->DB->query($sql, $vars)->fetchColumn();
+        // нет ни одного сообщения в теме
+        if (empty($pid)) {
+            return $this->c->Message->message('Bad request');
+        }
+
+        return $this->c->Redirect->setPage('ViewPost', ['id' => $pid]);
+     }
+
+     /**
+     * Просмотр темы по номеру сообщения
+     * @param array $args
+     * @return Page
+     */
+     public function viewPost(array $args)
+     {
+        $vars = [
+            ':pid' => $args['id'],
+        ];
+        $sql = 'SELECT topic_id FROM ::posts WHERE id=?i:pid';
+
+        $tid = $this->c->DB->query($sql, $vars)->fetchColumn();
+        // сообшение не найдено в базе
+        if (empty($tid)) {
+            return $this->c->Message->message('Bad request');
+        }
+
+        $vars = [
+            ':pid' => $args['id'],
+            ':tid' => $tid,
+        ];
+        $sql = 'SELECT COUNT(id) FROM ::posts WHERE topic_id=?i:tid AND id<?i:pid';
+
+        $num = 1 + $this->c->DB->query($sql, $vars)->fetchColumn();
+
+        return $this->view([
+            'id' => $tid,
+            'page' => ceil($num / $this->c->user->dispPosts),
+        ]);
+    }
+
+    /**
+     * Подготовка данных для шаблона
+     * @param array $args
+     * @return Page
+     */
+    public function view(array $args)
+    {
+        $this->c->Lang->load('topic');
+
+        $user = $this->c->user;
+        $vars = [
+            ':tid' => $args['id'],
+            ':uid' => $user->id,
+        ];
+
+        if ($user->isGuest) {
+            $sql = 'SELECT t.*, f.moderators, 0 AS is_subscribed 
+                    FROM ::topics AS t 
+                    INNER JOIN ::forums AS f ON f.id=t.forum_id 
+                    WHERE t.id=?i:tid AND t.moved_to IS NULL';
+        } else {
+            $sql = 'SELECT t.*, f.moderators, s.user_id AS is_subscribed 
+                    FROM ::topics AS t 
+                    INNER JOIN ::forums AS f ON f.id=t.forum_id 
+                    LEFT JOIN ::topic_subscriptions AS s ON (t.id=s.topic_id AND s.user_id=?i:uid) 
+                    WHERE t.id=?i:tid AND t.moved_to IS NULL';
+        }
+        $topic = $this->c->DB->query($sql, $vars)->fetch();
+
+        // тема отсутствует или недоступна
+        if (empty($topic)) {
+            return $this->c->Message->message('Bad request');
+        }
+
+        list($fTree, $fDesc, $fAsc) = $this->c->forums;
+
+        // раздел отсутствует в доступных
+        if (empty($fDesc[$topic['forum_id']])) {
+            return $this->c->Message->message('Bad request');
+        }
+
+        $page = isset($args['page']) ? (int) $args['page'] : 1;
+        $pages = ceil(( $topic['num_replies'] + 1) / $user->dispPosts);
+
+        // попытка открыть страницу которой нет
+        if ($page < 1 || $page > $pages) {
+            return $this->c->Message->message('Bad request');
+        }
+
+        $offset = ($page - 1) * $user->dispPosts;
+        
+        $vars = [
+            ':tid' => $args['id'],
+            ':offset' => $offset,
+            ':rows' => $user->dispPosts,
+        ];
+        $sql = 'SELECT id 
+                FROM ::posts 
+                WHERE topic_id=?i:tid 
+                ORDER BY id LIMIT ?i:offset, ?i:rows';
+        $ids = $this->c->DB->query($sql, $vars)->fetchAll(\PDO::FETCH_COLUMN);
+
+        // нарушена синхронизация количества сообщений в темах
+        if (empty($ids)) {
+            return $this->goToLast($args); //????
+        }
+
+        $moders = empty($topic['moderators']) ? [] : array_flip(unserialize($topic['moderators']));
+
+        $parent = isset($fDesc[$topic['forum_id']][0]) ? $fDesc[$topic['forum_id']][0] : 0;
+        $perm = $fTree[$parent][$topic['forum_id']];
+
+        if ($user->isAdmin) {
+            $newPost = $this->c->Router->link('NewPost', ['id' => $args['id']]);
+        } elseif ($topic['closed'] == '1') {
+            $newPost = false;
+        } elseif ($perm['post_replies'] === 1 
+            || (null === $perm['post_replies'] && $user->gPostReplies == '1')
+            || ($user->isAdmMod && isset($moders[$user->id]))
+        ) {
+            $newPost = $this->c->Router->link('NewPost', ['id' => $args['id']]);
+        } else {
+            $newPost = null;
+        }
+
+        // приклейка первого сообщения темы
+        $stickFP = (! empty($topic['stick_fp']) || ! empty($topic['poll_type']));
+        if ($stickFP) {
+            $ids[] = $topic['first_post_id'];
+        }
+
+        $vars = [
+            ':ids' => $ids,
+        ];
+        $sql = 'SELECT id, message, poster, posted 
+                FROM ::warnings 
+                WHERE id IN (?ai:ids)';
+        $warnings = $this->c->DB->query($sql, $vars)->fetchAll(\PDO::FETCH_GROUP);
+        
+        $vars = [
+            ':ids' => $ids,
+        ];
+        $sql = 'SELECT u.warning_all, u.gender, u.email, u.title, u.url, u.location, u.signature, 
+                       u.email_setting, u.num_posts, u.registered, u.admin_note, u.messages_enable, 
+                       p.id, p.poster as username, p.poster_id, p.poster_ip, p.poster_email, p.message, 
+                       p.hide_smilies, p.posted, p.edited, p.edited_by, p.edit_post, p.user_agent, 
+                       g.g_id, g.g_user_title, g.g_promote_next_group, g.g_pm 
+                FROM ::posts AS p 
+                INNER JOIN ::users AS u ON u.id=p.poster_id 
+                INNER JOIN ::groups AS g ON g.g_id=u.group_id 
+                WHERE p.id IN (?ai:ids) ORDER BY p.id';
+        $stmt = $this->c->DB->query($sql, $vars);
+
+        $genders = [1 => ' f-user-male', 2 => ' f-user-female'];
+        $postCount = 0;
+        $posts = [];
+        $posters = [];
+        while ($cur = $stmt->fetch()) {
+            // данные по автору сообшения
+            if (isset($posters[$cur['poster_id']])) {
+                $post = $posters[$cur['poster_id']];
+            } else {
+                $post = [
+                    'poster'            => $cur['username'],
+                    'poster_title'      => $this->censor($this->userGetTitle($cur)),
+                    'poster_avatar'     => null,
+                    'poster_registered' => null,
+                    'poster_location'   => null,
+                    'poster_info_add'   => false,
+                    'poster_link'       => null,
+                    'poster_posts'      => null,
+                    'poster_gender'     => '',
+                    'poster_online'     => '',
+   
+                ];
+                if ($cur['poster_id'] > 1) {
+                    if ($user->gViewUsers == '1') {
+                        $post['poster_link'] = $this->c->Router->link('User', ['id' => $cur['poster_id'], 'name' => $cur['username']]);
+                    }
+                    if ($this->config['o_avatars'] == '1' && $user->showAvatars == '1') {
+                        $post['poster_avatar'] = $this->userGetAvatarLink($cur['poster_id']);
+                    }
+                    if ($this->config['o_show_user_info'] == '1') {
+                        $post['poster_info_add'] = true;
+                        
+                        $post['poster_registered'] = $this->time($cur['registered'], true);
+                        
+                        $post['poster_posts']     = $this->number($cur['num_posts']);
+                        $post['poster_num_posts'] = $cur['num_posts'];
+                        
+                        if ($cur['location'] != '') {
+                            $post['poster_location'] = $this->censor($cur['location']);
+                        }
+                        if (isset($genders[$cur['gender']])) {
+                            $post['poster_gender'] = $genders[$cur['gender']];
+                        }
+                    }
+                    $post['poster_online'] = ' f-user-online'; //????
+
+                    $posters[$cur['poster_id']] = $post;
+                }
+            }
+
+            // данные по сообщению
+            $post['id']         = $cur['id'];
+            $post['link']       = $this->c->Router->link('ViewPost', ['id' => $cur['id']]);
+            $post['posted']     = $this->time($cur['posted']);
+            $post['posted_utc'] = gmdate('Y-m-d\TH:i:s\Z', $cur['posted']);
+
+            // номер сообшения в теме
+            if ($stickFP && $offset > 0 && $cur['id'] == $topic['first_post_id']) {
+                $post['post_number'] = 1;
+            } else {
+                ++$postCount;
+                $post['post_number'] = $offset + $postCount;
+            }
+
+            // данные по элементам управления
+            $controls = [];
+            if (! $user->isAdmin && ! $user->isGuest) {
+                $controls['report'] = ['#', 'Report'];
+            }
+            if ($user->isAdmin 
+                || ($user->isAdmMod && isset($moders[$user->id]))
+                || ($cur['poster_id'] == $user->id) //????
+            ) {
+                $controls['edit'] = ['#', 'Edit'];
+            }
+            if ($newPost) {
+                $controls['quote'] = ['#', 'Reply'];
+            }
+
+            $post['controls'] = $controls;
+            
+            $posts[] = $post;
+        }
+
+        $topic['subject'] = $this->censor($topic['subject']);
+        
+        $crumbs = [];
+        $crumbs[] = [
+            $this->c->Router->link('Topic', ['id' => $args['id'], 'name' => $topic['subject']]),
+            $topic['subject'],
+            true,
+        ];
+        $this->titles[] = $topic['subject'];
+
+        $id = $topic['forum_id'];
+        $activ = null;
+        while (true) {
+            $name = $fDesc[$id]['forum_name'];
+            $this->titles[] = $name;
+            $crumbs[] = [
+                $this->c->Router->link('Forum', ['id' => $id, 'name' => $name]),
+                $name, 
+                $activ,
+            ];
+            $activ = null;
+            if (! isset($fDesc[$id][0])) {
+                break;
+            }
+            $id = $fDesc[$id][0];
+        }
+        $crumbs[] = [
+            $this->c->Router->link('Index'),
+            __('Index'),
+            null,
+        ];
+
+        $this->data = [
+            'topic' => $topic,
+            'posts' => $posts,
+            'warnings' => $warnings,
+            'crumbs' => array_reverse($crumbs),
+            'topicName' => $topic['subject'],
+            'newPost' => $newPost,
+            'stickFP' => $stickFP,
+            'pages' => $this->c->Func->paginate($pages, $page, 'Topic', ['id' => $args['id'], 'name' => $topic['subject']]),
+        ];
+
+        $this->canonical = $this->c->Router->link('Topic', ['id' => $args['id'], 'name' => $topic['subject'], 'page' => $page]);
+
+        return $this;
+    }
+}

+ 59 - 0
app/Models/Pages/UsersTrait.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace ForkBB\Models\Pages;
+
+trait UsersTrait 
+{
+    /**
+     * Имена забаненных пользователей
+     * @var array
+     */
+    protected $userBanNames;
+
+    /**
+     * Определение титула для пользователя
+     * @param array $data
+     * @return string
+     */
+    protected function userGetTitle(array $data) 
+    {
+        if (! isset($this->userBanNames)) {
+            $this->userBanNames = [];
+            foreach($this->c->bans as $cur) {
+                $this->userBanNames[mb_strtolower($cur['username'])] = true;
+            }
+        }
+
+        if(isset($this->userBanNames[$data['username']])) {
+            return __('Banned');
+        } elseif ($data['title'] != '') {
+            return $data['title'];
+        } elseif ($data['g_user_title'] != '') {
+            return $data['g_user_title'];
+        } elseif ($data['g_id'] == $this->c->GROUP_GUEST) {
+            return __('Guest');
+        } else {
+            return __('Member');
+        }
+    }
+
+    /**
+     * Определение ссылки на аватарку
+     * @param int $id
+     * @return string|null
+     */
+    protected function userGetAvatarLink($id)
+    {
+        $filetypes = array('jpg', 'gif', 'png');
+    
+        foreach ($filetypes as $type) {
+            $path = $this->c->DIR_PUBLIC . "/avatar/{$id}.{$type}";
+
+            if (file_exists($path) && getimagesize($path)) {
+                return $this->c->PUBLIC_URL . "/avatar/{$id}.{$type}";
+            }
+        }
+
+        return null;
+    }
+}

+ 9 - 1
app/bootstrap.php

@@ -31,6 +31,14 @@ if (file_exists(__DIR__ . '/config/main.php')) {
 
 require __DIR__ . '/functions.php';
 
+// https or http?
+if (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off') {
+    $c->BASE_URL = str_replace('http://', 'https://', $c->BASE_URL);
+} else {
+    $c->BASE_URL = str_replace('https://', 'http://', $c->BASE_URL);
+}
+$c->PUBLIC_URL = $c->BASE_URL . $forkPublicPrefix;
+
 $c->FORK_REVISION = 1;
 $c->START = $forkStart;
 $c->DIR_APP    = __DIR__;
@@ -52,7 +60,7 @@ if ($page->getDataForOnline(true)) {
     $c->Online->handle($page);
 }
 $tpl = $c->View->rendering($page);
-if ($c->DEBUG > 0) {
+if ($tpl !== null && $c->DEBUG > 0) {
     $debug = $c->Debug->debug();
     $debug = $c->View->rendering($debug);
     $tpl = str_replace('<!-- debuginfo -->', $debug, $tpl);

+ 1 - 1
app/functions.php

@@ -12,7 +12,7 @@ function __($data, ...$args)
     $tr = $lang->get($data);
 
     if (is_array($tr)) {
-        if (isset($args[0]) && is_numeric($args[0])) {
+        if (isset($args[0]) && is_int($args[0])) {
             $n = array_shift($args);
             eval('$n = (int) ' . $tr['plural']);
             $tr = $tr[$n];

+ 102 - 0
app/lang/English/topic.po

@@ -0,0 +1,102 @@
+#
+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 "Post reply"
+msgstr "Post reply"
+
+msgid "Topic closed"
+msgstr "Topic closed"
+
+msgid "From"
+msgstr "From:"
+
+msgid "Promote user"
+msgstr "Promote user"
+
+msgid "IP address logged"
+msgstr "IP address logged"
+
+msgid "Note"
+msgstr "Note:"
+
+msgid "%s post"
+msgid_plural "%s posts"
+msgstr[0] "%s post"
+msgstr[1] "%s posts"
+
+msgid "Registered:"
+msgstr "Registered:"
+
+msgid "Replies"
+msgstr "Replies:"
+
+msgid "Website"
+msgstr "Website"
+
+msgid "Online"
+msgstr "Online"
+
+msgid "Offline"
+msgstr "Offline"
+
+msgid "Last edit"
+msgstr "Last edited by"
+
+msgid "Report"
+msgstr "Report"
+
+msgid "Delete"
+msgstr "Delete"
+
+msgid "Edit"
+msgstr "Edit"
+
+msgid "Quote"
+msgstr "Quote"
+
+msgid "Is subscribed"
+msgstr "You are currently subscribed to this topic"
+
+msgid "Unsubscribe"
+msgstr "Unsubscribe"
+
+msgid "Subscribe"
+msgstr "Subscribe to this topic"
+
+msgid "Quick post"
+msgstr "Quick reply"
+
+msgid "Mod controls"
+msgstr "Moderator controls"
+
+msgid "New icon"
+msgstr "New post"
+
+msgid "Re"
+msgstr "Re:"
+
+msgid "Preview"
+msgstr "Preview"
+
+msgid "Warnings"
+msgstr "Warnings:"
+
+msgid "Reply"
+msgstr "Reply"
+
+msgid "Users online"
+msgstr "Registered users online in this topic: %s"
+
+msgid "Guests online"
+msgstr "guests: %s"

+ 103 - 0
app/lang/Russian/topic.po

@@ -0,0 +1,103 @@
+#
+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 "Post reply"
+msgstr "Ответить"
+
+msgid "Topic closed"
+msgstr "Тема закрыта"
+
+msgid "From"
+msgstr "Откуда:"
+
+msgid "Promote user"
+msgstr "Переместить пользователя"
+
+msgid "IP address logged"
+msgstr "IP адрес"
+
+msgid "Note"
+msgstr "Замечание"
+
+msgid "%s post"
+msgid_plural "%s posts"
+msgstr[0] "%s сообщение"
+msgstr[1] "%s сообщения"
+msgstr[2] "%s сообщений"
+
+msgid "Registered:"
+msgstr "Здесь с"
+
+msgid "Replies"
+msgstr "Ответов:"
+
+msgid "Website"
+msgstr "Сайт"
+
+msgid "Online"
+msgstr "Активен"
+
+msgid "Offline"
+msgstr "Вне форума"
+
+msgid "Last edit"
+msgstr "Отредактировано"
+
+msgid "Report"
+msgstr "Просигналить"
+
+msgid "Delete"
+msgstr "Удалить"
+
+msgid "Edit"
+msgstr "Редактировать"
+
+msgid "Quote"
+msgstr "Цитировать"
+
+msgid "Is subscribed"
+msgstr "Вы подписаны на эту тему"
+
+msgid "Unsubscribe"
+msgstr "Отказаться от подписки"
+
+msgid "Subscribe"
+msgstr "Подписаться на тему"
+
+msgid "Quick post"
+msgstr "Быстрый ответ"
+
+msgid "Mod controls"
+msgstr "Операции модерирования"
+
+msgid "New icon"
+msgstr "New post"
+
+msgid "Re"
+msgstr "Re:"
+
+msgid "Preview"
+msgstr "Предпросмотр"
+
+msgid "Warnings"
+msgstr "Предупреждений:"
+
+msgid "Reply"
+msgstr "Ответить"
+
+msgid "Users online"
+msgstr "Сейчас в этой теме пользователей: %s"
+
+msgid "Guests online"
+msgstr "гостей: %s"

+ 3 - 3
app/templates/forum.tpl

@@ -141,9 +141,9 @@
             </div>
             <div class="f-cell f-cstats">
               <ul>
-                <li>{!! __('%s Reply', $topic['num_replies'], $topic['num_replies']) !!}</li>
-@if(null !== $topic['num_views'])
-                <li>{!! __('%s View', $topic['num_views'], $topic['num_views'])!!}</li>
+                <li>{!! __('%s Reply', $topic['num_replies'], $topic['replies']) !!}</li>
+@if($topic['views'])
+                <li>{!! __('%s View', $topic['num_views'], $topic['views']) !!}</li>
 @endif
               </ul>
             </div>

+ 114 - 0
app/templates/topic.tpl

@@ -0,0 +1,114 @@
+@section('crumbs')
+      <ul class="f-crumbs">
+@foreach($crumbs as $cur)
+@if($cur[2])
+        <li class="f-crumb"><a href="{!! $cur[0] !!}" class="active">{{ $cur[1] }}</a></li>
+@else
+        <li class="f-crumb"><a href="{!! $cur[0] !!}">{{ $cur[1] }}</a></li>
+@endif
+@endforeach
+      </ul>
+@endsection
+@section('linkpost')
+@if($newPost !== null)
+        <div class="f-link-post">
+@if($newPost === false)
+          __('Topic closed')
+@else
+          <a class="f-btn" href="{!! $newPost !!}">{!! __('Post reply') !!}</a>
+@endif
+        </div>
+@endif
+@endsection
+@section('pages')
+        <nav class="f-pages">
+@foreach($pages as $cur)
+@if($cur[2])
+          <span class="f-page active">{{ $cur[1] }}</span>
+@elseif($cur[1] === 'space')
+          <span class="f-page f-pspacer">{!! __('Spacer') !!}</span>
+@elseif($cur[1] === 'prev')
+          <a rel="prev" class="f-page f-pprev" href="{!! $cur[0] !!}">{!! __('Previous') !!}</a>
+@elseif($cur[1] === 'next')
+          <a rel="next" class="f-page f-pnext" href="{!! $cur[0] !!}">{!! __('Next') !!}</a>
+@else
+          <a class="f-page" href="{!! $cur[0] !!}">{{ $cur[1] }}</a>
+@endif
+@endforeach
+        </nav>
+@endsection
+@extends('layouts/main')
+    <div class="f-nav-links">
+@yield('crumbs')
+@if($newPost || $pages)
+      <div class="f-links-b clearfix">
+@yield('pages')
+@yield('linkpost')
+      </div>
+@endif
+    </div>
+    <section class="f-main f-topic">
+      <h2>{{ $topicName }}</h2>
+@foreach($posts as $post)
+      <article id="p{!! $post['id'] !!}" class="f-post{!! $post['poster_gender'].$post['poster_online'] !!} clearfix">
+        <div class="f-post-header clearfix">
+          <h3>{{ $topicName }} - #{!! $post['post_number'] !!}</h3>
+          <span class="left"><time datetime="{{ $post['posted_utc'] }}">{{ $post['posted'] }}</time></span>
+          <span class="right"><a href="{!! $post['link'] !!}" rel="bookmark">#{!! $post['post_number'] !!}</a></span>
+        </div>
+        <div class="f-post-body clearfix">
+          <address class="f-post-left clearfix">
+            <ul class="f-user-info">
+@if($post['poster_link'])
+              <li class="f-username"><a href="{!! $post['poster_link'] !!}">{{ $post['poster'] }}</a></li>
+@else
+              <li class="f-username">{{ $post['poster'] }}</li>
+@endif
+@if($post['poster_avatar'])
+              <li class="f-avatar">
+                <img alt="{{ $post['poster'] }}" src="{!! $post['poster_avatar'] !!}">
+              </li>
+@endif
+              <li class="f-usertitle"><span>{{$post['poster_title']}}</span></li>
+@if($post['poster_posts'])
+              <li class="f-postcount"><span>{!! __('%s post', $post['poster_num_posts'], $post['poster_posts']) !!}</span></li>
+@endif
+            </ul>
+@if($post['poster_info_add'])
+            <ul class="f-user-info-add">
+              <li><span>{!! __('Registered:') !!} {{ $post['poster_registered'] }}</span></li>
+@if($post['poster_location'])
+              <li><span>{!! __('From') !!} {{ $post['poster_location'] }}</span></li>
+@endif
+              <li><span></span></li>
+            </ul>
+@endif
+          </address>
+
+        </div>
+        <div class="f-post-footer clearfix">
+          <div class="f-post-left">
+            <span></span>
+          </div>
+@if($post['controls'])
+          <div class="f-post-right clearfix">
+            <ul>
+@foreach($post['controls'] as $key => $control)
+              <li class="f-post{!! $key !!}"><a class="f-btn" href="{!! $control[0] !!}">{!! __($control[1]) !!}</a></li>
+@endforeach
+            </ul>
+          </div>
+@endif
+        </div>
+      </article>
+@endforeach
+    </section>
+    <div class="f-nav-links">
+@if($newPost || $pages)
+      <div class="f-links-a clearfix">
+@yield('linkpost')
+@yield('pages')
+      </div>
+@endif
+@yield('crumbs')
+    </div>

+ 1 - 0
public/index.php

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

+ 130 - 123
public/style/ForkBB/style.css

@@ -696,129 +696,6 @@ select {
   padding: 0.625rem;
 }
 
-/********************/
-/* Сообщение в теме */
-/********************/
-
-.f-post {
-  border-top: 0.0625rem solid #AA7939;
-  margin-bottom: 0.625rem;
-  position: relative;
-  background-color: #FFFFFF;
-  overflow: hidden;
-}
-
-.f-post-header > h3 {
-  display: none;
-}
-
-.f-post-header {
-  padding: 0.625rem;
-  border-bottom: 0.0625rem solid #AA7939;
-  background-color: #D3B58D;
-}
-
-.f-post-left {
-  padding: 0.625rem;
-  background-color: #F8F4E3;
-}
-
-.f-post-right {
-  padding: 0.625rem;
-}
-
-.f-post-footer .f-post-right {
-  text-align: right;
-}
-
-.f-post-footer .f-post-left {
-  display: none;
-}
-
-.f-post-footer li {
-  display: inline;
-}
-
-li + li .f-btn {
-  margin-left: 0.1875rem;
-}
-
-.f-user-info {
-  font-size: 0.875rem;
-}
-
-.f-user-info-custom {
-  display: none;
-}
-
-.f-username {
-  font-size: 1.25rem;
-  float: left;
-}
-
-.f-username a {
-  border: 0;
-}
-
-.f-avatar {
-  float: right;
-}
-
-.f-avatar img {
-  max-width: 4.6875rem;
-  height: auto;
-}
-
-.f-usertitle {
-  clear: left;
-}
-
-@media screen and (min-width: 50rem) {
-  .f-post {
-    background-color: #F8F4E3;
-  }
-
-  .f-post-body, .f-post-footer {
-    width: 100%;
-    float: right;
-    margin-right: -13rem;
-    position: relative;
-    border-left: 0.0625rem solid #AA7939;
-    background-color: #FFFFFF;
-  }
-
-  .f-post-left {
-    width: 11.75rem;
-    margin-left: -13rem;
-    float: left;
-    position: relative;
-    background-color: transparent;
-  }
-
-  .f-post-right {
-    padding-right: 13.625rem;
-    position: relative;
-  }
-
-  .f-post-footer .f-post-left {
-    display: block;
-  }
-
-  .f-user-info-custom {
-    display: block;
-    font-size: 0.875rem;
-  }
-
-  .f-username, .f-avatar {
-    float: none;
-  }
-
-  .f-avatar img {
-    max-width: 100%;
-    height: auto;
-  }
-} /* @media screen and (min-width: 50rem) */
-
 /******************/
 /* Админка - меню */
 /******************/
@@ -1253,3 +1130,133 @@ li + li .f-btn {
     border: 0;
     padding: 0;
 }
+
+/*****************/
+/* Страница темы */
+/*****************/
+.f-topic > h2 {
+  display: none;
+}
+
+/********************/
+/* Сообщение в теме */
+/********************/
+
+.f-post {
+  border-top: 0.0625rem solid #AA7939;
+  margin-bottom: 0.625rem;
+  position: relative;
+  background-color: #FFFFFF;
+  overflow: hidden;
+}
+
+.f-post-header > h3 {
+  display: none;
+}
+
+.f-post-header {
+  padding: 0.625rem;
+  border-bottom: 0.0625rem solid #AA7939;
+  background-color: #D3B58D;
+}
+
+.f-post-left {
+  padding: 0.625rem;
+  background-color: #F8F4E3;
+}
+
+.f-post-right {
+  padding: 0.625rem;
+}
+
+.f-post-footer .f-post-right {
+  text-align: right;
+}
+
+.f-post-footer .f-post-left {
+  display: none;
+}
+
+.f-post-footer li {
+  display: inline;
+}
+
+li + li .f-btn {
+/*  margin-left: 0.1875rem; */
+}
+
+.f-user-info {
+  font-size: 0.875rem;
+}
+
+.f-user-info-add {
+  display: none;
+}
+
+.f-username {
+  font-size: 1.25rem;
+  float: left;
+}
+
+.f-username a {
+  border: 0;
+}
+
+.f-avatar {
+  float: right;
+}
+
+.f-avatar img {
+  max-width: 4.6875rem;
+  height: auto;
+}
+
+.f-usertitle {
+  clear: left;
+}
+
+@media screen and (min-width: 50rem) {
+  .f-post {
+    background-color: #F8F4E3;
+  }
+
+  .f-post-body, .f-post-footer {
+    width: 100%;
+    float: right;
+    margin-right: -13rem;
+    position: relative;
+    border-left: 0.0625rem solid #AA7939;
+    background-color: #FFFFFF;
+  }
+
+  .f-post-left {
+    width: 11.75rem;
+    margin-left: -13rem;
+    float: left;
+    position: relative;
+    background-color: transparent;
+  }
+
+  .f-post-right {
+    padding-right: 13.625rem;
+    position: relative;
+  }
+
+  .f-post-footer .f-post-left {
+    display: block;
+  }
+
+  .f-user-info-add {
+    display: block;
+    font-size: 0.875rem;
+  }
+
+  .f-username, .f-avatar {
+    float: none;
+  }
+
+  .f-avatar img {
+    max-width: 100%;
+    height: auto;
+  }
+} /* @media screen and (min-width: 50rem) */