Browse Source

2017-12-19

Visman 7 years ago
parent
commit
a255ac2a16
61 changed files with 1210 additions and 459 deletions
  1. 2 2
      app/Controllers/Install.php
  2. 1 0
      app/Controllers/Routing.php
  3. 1 1
      app/Core/Router.php
  4. 44 0
      app/Models/Action.php
  5. 2 2
      app/Models/AdminList/Load.php
  6. 3 3
      app/Models/BanList/Check.php
  7. 2 2
      app/Models/BanList/Delete.php
  8. 3 3
      app/Models/BanList/IsBanned.php
  9. 2 2
      app/Models/BanList/Load.php
  10. 2 2
      app/Models/CensorshipList/Load.php
  11. 2 2
      app/Models/Config/Load.php
  12. 2 2
      app/Models/Config/Save.php
  13. 1 1
      app/Models/Cookie.php
  14. 2 2
      app/Models/Forum/CalcStat.php
  15. 5 5
      app/Models/Forum/LoadTree.php
  16. 106 0
      app/Models/Forum/Manager.php
  17. 7 8
      app/Models/Forum/Model.php
  18. 3 3
      app/Models/Forum/Refresh.php
  19. 25 20
      app/Models/Forum/Save.php
  20. 0 58
      app/Models/ForumList.php
  21. 54 0
      app/Models/ManagerModel.php
  22. 15 4
      app/Models/Method.php
  23. 2 15
      app/Models/Model.php
  24. 1 1
      app/Models/Online.php
  25. 2 2
      app/Models/Online/Info.php
  26. 10 10
      app/Models/Pages/Auth.php
  27. 1 1
      app/Models/Pages/Ban.php
  28. 1 1
      app/Models/Pages/CrumbTrait.php
  29. 123 0
      app/Models/Pages/Delete.php
  30. 7 7
      app/Models/Pages/Edit.php
  31. 17 17
      app/Models/Pages/Post.php
  32. 2 2
      app/Models/Pages/PostValidatorTrait.php
  33. 6 6
      app/Models/Pages/Register.php
  34. 6 1
      app/Models/Pages/Topic.php
  35. 84 39
      app/Models/Post/Load.php
  36. 76 0
      app/Models/Post/Manager.php
  37. 36 6
      app/Models/Post/Model.php
  38. 25 20
      app/Models/Post/Save.php
  39. 2 2
      app/Models/SmileyList/Load.php
  40. 2 2
      app/Models/Stats/Load.php
  41. 2 2
      app/Models/Topic/CalcStat.php
  42. 23 53
      app/Models/Topic/Load.php
  43. 67 0
      app/Models/Topic/Manager.php
  44. 6 9
      app/Models/Topic/Model.php
  45. 25 20
      app/Models/Topic/Save.php
  46. 38 32
      app/Models/User/Current.php
  47. 9 9
      app/Models/User/IsUniqueName.php
  48. 21 14
      app/Models/User/Load.php
  49. 77 0
      app/Models/User/Manager.php
  50. 6 6
      app/Models/User/Model.php
  51. 26 21
      app/Models/User/Save.php
  52. 16 7
      app/Models/User/UpdateLastVisit.php
  53. 1 1
      app/functions.php
  54. 10 1
      app/lang/English/common.po
  55. 55 0
      app/lang/English/delete.po
  56. 10 1
      app/lang/Russian/common.po
  57. 55 0
      app/lang/Russian/delete.po
  58. 26 16
      app/templates/layouts/form.tpl
  59. 24 1
      app/templates/post.tpl
  60. 2 2
      app/templates/topic.tpl
  61. 24 10
      public/style/ForkBB/style.css

+ 2 - 2
app/Controllers/Install.php

@@ -3,7 +3,7 @@
 namespace ForkBB\Controllers;
 
 use ForkBB\Core\Container;
-use ForkBB\Models\User;
+use ForkBB\Models\User\Model as User;
 
 class Install
 {
@@ -41,7 +41,7 @@ class Install
             . substr($uri, 0, (int) strrpos($uri, '/'));
 
         $this->c->Lang->load('common', $this->c->config->o_default_lang);
-        $this->c->user = $this->ModelUser->setAttrs(['id' => 2, 'group_id' => $this->c->GROUP_ADMIN]);
+        $this->c->user = $this->users->create(['id' => 2, 'group_id' => $this->c->GROUP_ADMIN]);
 
         $r = $this->c->Router;
         $r->add('GET', '/install', 'Install:install', 'Install');

+ 1 - 0
app/Controllers/Routing.php

@@ -95,6 +95,7 @@ class Routing
             $r->add('GET',  '/post/{id:[1-9]\d*}/edit', 'Edit:edit', 'EditPost');
             $r->add('POST', '/post/{id:[1-9]\d*}/edit', 'Edit:editPost');
             $r->add('GET',  '/post/{id:[1-9]\d*}/delete', 'Delete:delete', 'DeletePost');
+            $r->add('POST', '/post/{id:[1-9]\d*}/delete', 'Delete:deletePost');
             $r->add('GET',  '/post/{id:[1-9]\d*}/report', 'Report:report', 'ReportPost');
 
         }

+ 1 - 1
app/Core/Router.php

@@ -129,7 +129,7 @@ class Router
             // значение есть
             if (isset($args[$name])) {
                 // кроме page = 1
-                if ($name != 'page' || $args[$name] !== 1) {
+                if ($name !== 'page' || $args[$name] !== 1) {
                     $data['{' . $name . '}'] = rawurlencode(preg_replace('%[\s\\\/]+%u', '-', $args[$name]));
                     continue;
                 }

+ 44 - 0
app/Models/Action.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace ForkBB\Models;
+
+use ForkBB\Core\Container;
+use ForkBB\Models\ManagerModel;
+
+class Action
+{
+    /**
+     * Контейнер
+     * @var Container
+     */
+    protected $c;
+
+    /**
+     * Модель
+     * @var ManagerModel
+     */
+    protected $manager;
+
+    /**
+     * Конструктор
+     *
+     * @param Container $container
+     */
+    public function __construct(Container $container)
+    {
+        $this->c = $container;
+    }
+
+    /**
+     * Объявление менеджера
+     * 
+     * @param ManagerModel $manager
+     * 
+     * @return Action
+     */
+    public function setManager(ManagerModel $manager)
+    {
+        $this->manager = $manager;
+        return $this;
+    }
+}

+ 2 - 2
app/Models/AdminList/Load.php

@@ -2,9 +2,9 @@
 
 namespace ForkBB\Models\AdminList;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Method;
 
-class Load extends MethodModel
+class Load extends Method
 {
     /**
      * Заполняет модель данными из БД

+ 3 - 3
app/Models/BanList/Check.php

@@ -2,10 +2,10 @@
 
 namespace ForkBB\Models\BanList;
 
-use ForkBB\Models\MethodModel;
-use ForkBB\Models\User;
+use ForkBB\Models\Method;
+use ForkBB\Models\User\Model as User;
 
-class Check extends MethodModel
+class Check extends Method
 {
     /**
      * Проверяет наличие бана (для текущего пользователя) на основании имени пользователя/ip

+ 2 - 2
app/Models/BanList/Delete.php

@@ -2,9 +2,9 @@
 
 namespace ForkBB\Models\BanList;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Method;
 
-class Delete extends MethodModel
+class Delete extends Method
 {
     /**
      * Удаляет из банов записи по списку номеров

+ 3 - 3
app/Models/BanList/IsBanned.php

@@ -2,10 +2,10 @@
 
 namespace ForkBB\Models\BanList;
 
-use ForkBB\Models\MethodModel;
-use ForkBB\Models\User;
+use ForkBB\Models\Method;
+use ForkBB\Models\User\Model as User;
 
-class IsBanned extends MethodModel
+class IsBanned extends Method
 {
     /**
      * Проверяет наличие бана на основании имени пользователя и(или) email

+ 2 - 2
app/Models/BanList/Load.php

@@ -2,9 +2,9 @@
 
 namespace ForkBB\Models\BanList;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Method;
 
-class Load extends MethodModel
+class Load extends Method
 {
     /**
      * Загружает список банов из БД

+ 2 - 2
app/Models/CensorshipList/Load.php

@@ -2,9 +2,9 @@
 
 namespace ForkBB\Models\CensorshipList;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Method;
 
-class Load extends MethodModel
+class Load extends Method
 {
     /**
      * Заполняет модель данными из БД

+ 2 - 2
app/Models/Config/Load.php

@@ -2,9 +2,9 @@
 
 namespace ForkBB\Models\Config;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Method;
 
-class Load extends MethodModel
+class Load extends Method
 {
     /**
      * Заполняет модель данными из БД

+ 2 - 2
app/Models/Config/Save.php

@@ -2,9 +2,9 @@
 
 namespace ForkBB\Models\Config;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Method;
 
-class Save extends MethodModel
+class Save extends Method
 {
     /**
      * Сохраняет изменения модели в БД

+ 1 - 1
app/Models/Cookie.php

@@ -3,7 +3,7 @@
 namespace ForkBB\Models;
 
 use ForkBB\Models\Model;
-use ForkBB\Models\User;
+use ForkBB\Models\User\Model as User;
 use ForkBB\Core\Container;
 use RuntimeException;
 

+ 2 - 2
app/Models/Forum/CalcStat.php

@@ -2,10 +2,10 @@
 
 namespace ForkBB\Models\Forum;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Method;
 use RuntimeException;
 
-class CalcStat extends MethodModel
+class CalcStat extends Method
 {
     /**
      * Пересчитывает статистику

+ 5 - 5
app/Models/ForumList/LoadTree.php → app/Models/Forum/LoadTree.php

@@ -1,10 +1,10 @@
 <?php
 
-namespace ForkBB\Models\ForumList;
+namespace ForkBB\Models\Forum;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Action;
 
-class LoadTree extends MethodModel
+class LoadTree extends Action
 {
     /**
      * Загружает данные в модели для указанного раздела и всех его потомков
@@ -15,7 +15,7 @@ class LoadTree extends MethodModel
      */
     public function loadTree($rootId)
     {
-        $root = $this->model->forum($rootId);
+        $root = $this->manager->get($rootId);
         if (null === $root) {
             return null;
         }
@@ -44,7 +44,7 @@ class LoadTree extends MethodModel
      * 
      * @param array $list
      */
-    public function loadData(array $list)
+    protected function loadData(array $list)
     {
         if (empty($list)) {
             return;

+ 106 - 0
app/Models/Forum/Manager.php

@@ -0,0 +1,106 @@
+<?php
+
+namespace ForkBB\Models\Forum;
+
+use ForkBB\Models\ManagerModel;
+use ForkBB\Models\Forum\Model as Forum;
+use RuntimeException;
+
+class Manager extends ManagerModel
+{
+    /**
+     * Закешированные данные по разделам
+     * @var array
+     */
+    protected $forumList = [];
+
+    /**
+     * Создает новую модель раздела
+     * 
+     * @param array $attrs
+     * 
+     * @return Topic
+     */
+    public function create(array $attrs = [])
+    {
+        return $this->c->ForumModel->setAttrs($attrs);
+    }
+
+    /**
+     * Инициализация списка разделов
+     * 
+     * @param int $gid
+     *
+     * @return Manager
+     */
+    public function init($gid = 0)
+    {
+        if (empty($gid)) {
+            $gid = $this->c->user->group_id;
+        }
+
+        $mark = $this->c->Cache->get('forums_mark');
+        if (empty($mark)) {
+            $this->c->Cache->set('forums_mark', time());
+            $list = $this->refresh($gid);
+        } else {
+            $result = $this->c->Cache->get('forums_' . $gid);
+            if (empty($result['time']) || $result['time'] < $mark) {
+                $list = $this->refresh($gid);
+            } else {
+                $list = $result['list'];
+            }
+        }
+
+        $this->forumList = $list;
+        return $this;
+    }
+
+    /**
+     * Получение модели раздела
+     * 
+     * @param int $id
+     * 
+     * @return null|Forum
+     */
+    public function get($id)
+    {
+        $forum = parent::get($id);
+
+        if (! $forum instanceof Forum) {
+            if (empty($this->forumList[$id])) {
+                return null;
+            }
+            $forum = $this->create($this->forumList[$id]);
+            $this->set($id, $forum);
+        }
+
+        return $forum;
+    }
+
+    /**
+     * Обновляет раздел в БД
+     *
+     * @param Forum $forum
+     * 
+     * @return Forum
+     */
+    public function update(Forum $forum)
+    {
+        return $this->Save->update($forum);
+    }
+
+    /**
+     * Добавляет новый раздел в БД
+     *
+     * @param Forum $forum
+     * 
+     * @return int
+     */
+    public function insert(Topic $forum)
+    {
+        $id = $this->Save->insert($forum);
+        $this->set($id, $forum);
+        return $id;
+    }
+}

+ 7 - 8
app/Models/Forum.php → app/Models/Forum/Model.php

@@ -1,13 +1,12 @@
 <?php
 
-namespace ForkBB\Models;
+namespace ForkBB\Models\Forum;
 
 use ForkBB\Models\DataModel;
-use ForkBB\Core\Container;
 use RuntimeException;
 use InvalidArgumentException;
 
-class Forum extends DataModel
+class Model extends DataModel
 {
     /**
      * Получение родительского раздела
@@ -22,7 +21,7 @@ class Forum extends DataModel
             throw new RuntimeException('Parent is not defined');
         }
 
-        return $this->c->forums->forum($this->parent_forum_id);
+        return $this->c->forums->get($this->parent_forum_id);
     }
 
     /**
@@ -49,7 +48,7 @@ class Forum extends DataModel
         $sub = [];
         if (! empty($this->a['subforums'])) {
             foreach ($this->a['subforums'] as $id) {
-                $sub[$id] = $this->c->forums->forum($id);
+                $sub[$id] = $this->c->forums->get($id);
             }
         }
         return $sub;
@@ -65,7 +64,7 @@ class Forum extends DataModel
         $all = [];
         if (! empty($this->a['descendants'])) {
             foreach ($this->a['descendants'] as $id) {
-                $all[$id] = $this->c->forums->forum($id);
+                $all[$id] = $this->c->forums->get($id);
             }
         }
         return $all;
@@ -160,7 +159,7 @@ class Forum extends DataModel
                     $topic  = $children->last_topic;
                 }
             }
-            $this->a['tree'] = $this->c->ModelForum->setAttrs([
+            $this->a['tree'] = $this->c->forums->create([
                 'num_topics'     => $numT,
                 'num_posts'      => $numP,
                 'last_post'      => $time,
@@ -284,7 +283,7 @@ class Forum extends DataModel
 
         foreach ($topics as &$cur) {
             $cur['dot'] = isset($dots[$cur['id']]);
-            $cur = $this->c->ModelTopic->setAttrs($cur);
+            $cur = $this->c->topics->create($cur);
         }
         unset($cur);
 

+ 3 - 3
app/Models/ForumList/Refresh.php → app/Models/Forum/Refresh.php

@@ -1,10 +1,10 @@
 <?php
 
-namespace ForkBB\Models\ForumList;
+namespace ForkBB\Models\Forum;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Action;
 
-class Refresh extends MethodModel
+class Refresh extends Action
 {
     /**
      * @var array

+ 25 - 20
app/Models/Forum/Save.php

@@ -2,28 +2,31 @@
 
 namespace ForkBB\Models\Forum;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Action;
+use ForkBB\Models\Forum\Model as Forum;
 use RuntimeException;
 
-class Save extends MethodModel
+class Save extends Action
 {
     /**
-     * Обновляет данные пользователя
+     * Обновляет раздел в БД
      *
+     * @param Forum $forum
+     * 
      * @throws RuntimeException
      * 
      * @return Forum
      */
-    public function update()
+    public function update(Forum $forum)
     {
-        if (empty($this->model->id)) {
+        if ($forum->id < 1) {
             throw new RuntimeException('The model does not have ID');
         }
-        $modified = $this->model->getModified();
+        $modified = $forum->getModified();
         if (empty($modified)) {
-            return $this->model;
+            return $forum;
         }
-        $values = $this->model->getAttrs();
+        $values = $forum->getAttrs();
         $fileds = $this->c->dbMap->forums;
         $set = $vars = [];
         foreach ($modified as $name) {
@@ -34,29 +37,31 @@ class Save extends MethodModel
             $set[] = $name . '=?' . $fileds[$name];
         }
         if (empty($set)) {
-            return $this->model;
+            return $forum;
         }
-        $vars[] = $this->model->id;
+        $vars[] = $forum->id;
         $this->c->DB->query('UPDATE ::forums SET ' . implode(', ', $set) . ' WHERE id=?i', $vars);
-        $this->model->resModified();
+        $forum->resModified();
 
-        return $this->model;
+        return $forum;
     }
 
     /**
-     * Добавляет новую запись в таблицу пользователей
+     * Добавляет новый раздел в БД
      *
+     * @param Forum $forum
+     * 
      * @throws RuntimeException
      * 
      * @return int
      */
-    public function insert()
+    public function insert(Forum $forum)
     {
-        $modified = $this->model->getModified();
-        if (null !== $this->model->id || in_array('id', $modified)) {
+        $modified = $forum->getModified();
+        if (null !== $forum->id || in_array('id', $modified)) {
             throw new RuntimeException('The model has ID');
         }
-        $values = $this->model->getAttrs();
+        $values = $forum->getAttrs();
         $fileds = $this->c->dbMap->forums;
         $set = $set2 = $vars = [];
         foreach ($modified as $name) {
@@ -71,9 +76,9 @@ class Save extends MethodModel
             throw new RuntimeException('The model is empty');
         }
         $this->c->DB->query('INSERT INTO ::forums (' . implode(', ', $set) . ') VALUES (' . implode(', ', $set2) . ')', $vars);
-        $this->model->id = $this->c->DB->lastInsertId();
-        $this->model->resModified();
+        $forum->id = $this->c->DB->lastInsertId();
+        $forum->resModified();
 
-        return $this->model->id;
+        return $forum->id;
     }
 }

+ 0 - 58
app/Models/ForumList.php

@@ -1,58 +0,0 @@
-<?php
-
-namespace ForkBB\Models;
-
-use ForkBB\Models\Model;
-use RuntimeException;
-
-class ForumList extends Model
-{
-    /**
-     * Заполняет модель данными
-     * 
-     * @param int $gid
-     *
-     * @return ForumList
-     */
-    public function init($gid = 0)
-    {
-        if (empty($gid)) {
-            $gid = $this->c->user->group_id;
-        }
-
-        $mark = $this->c->Cache->get('forums_mark');
-        if (empty($mark)) {
-            $this->c->Cache->set('forums_mark', time());
-            $list = $this->refresh($gid);
-        } else {
-            $result = $this->c->Cache->get('forums_' . $gid);
-            if (empty($result['time']) || $result['time'] < $mark) {
-                $list = $this->refresh($gid);
-            } else {
-                $list = $result['list'];
-            }
-        }
-
-        $this->list = $list; //????
-        return $this;
-    }
-
-    /**
-     * 
-     * @param int $id
-     * 
-     * @return null|Forum
-     */
-    public function forum($id)
-    {
-        if (isset($this->forums[$id])) {
-            return $this->forums[$id];
-        } elseif (isset($this->list[$id])) {
-            $forum = $this->c->ModelForum->setAttrs($this->list[$id]);
-            $this->a['forums'][$id] = $forum; //????
-            return $forum;
-        } else {
-            return null;
-        }
-    }
-}

+ 54 - 0
app/Models/ManagerModel.php

@@ -0,0 +1,54 @@
+<?php
+
+namespace ForkBB\Models;
+
+use ForkBB\Models\Model;
+
+class ManagerModel extends Model
+{
+    /**
+     * @var array
+     */
+    protected $repository = [];
+
+    public function get($key)
+    {
+        return isset($this->repository[$key]) ? $this->repository[$key] : null;
+    }
+
+    public function set($key, $value)
+    {
+        $this->repository[$key] = $value;
+
+        return $this;
+    }
+
+    /**
+     * Возвращает action по его имени
+     * 
+     * @param string $name
+     *
+     * @return mixed
+     */
+    public function __get($name)
+    {
+        $key = str_replace(['ForkBB\\Models\\', 'ForkBB\\', '\\'], '', get_class($this));
+
+        return $this->c->{$key . $name}->setManager($this);
+    }
+
+    /**
+     * Выполняет подгружаемый метод при его наличии
+     *
+     * @param string $name
+     * @param array $args
+     *
+     * @return mixed
+     */
+    public function __call($name, array $args)
+    {
+        $key = str_replace(['ForkBB\\Models\\', 'ForkBB\\', '\\'], '', get_class($this));
+
+        return $this->c->{$key . ucfirst($name)}->setManager($this)->$name(...$args);
+    }
+}

+ 15 - 4
app/Models/MethodModel.php → app/Models/Method.php

@@ -5,7 +5,7 @@ namespace ForkBB\Models;
 use ForkBB\Core\Container;
 use ForkBB\Models\Model;
 
-abstract class MethodModel
+class Method
 {
     /**
      * Контейнер
@@ -22,12 +22,23 @@ abstract class MethodModel
     /**
      * Конструктор
      *
-     * @param Model $model
      * @param Container $container
      */
-    public function __construct(Model $model, Container $container)
+    public function __construct(Container $container)
     {
-        $this->model = $model;
         $this->c = $container;
     }
+
+    /**
+     * Объявление модели
+     * 
+     * @param Model $model
+     * 
+     * @return Method
+     */
+    public function setModel(Model $model)
+    {
+        $this->model = $model;
+        return $this;
+    }
 }

+ 2 - 15
app/Models/Model.php

@@ -101,25 +101,12 @@ class Model
      * @param string $name
      * @param array $args
      *
-     * @throws RuntimeException
-     *
      * @return mixed
      */
     public function __call($name, array $args)
     {
-        $key = str_replace(['ForkBB\\', '\\'], '', get_class($this));
-
-        if (empty($this->c->METHODS[$key][$name])) {
-            throw new RuntimeException("The {$name} method was not found");
-        }
+        $key = str_replace(['ForkBB\\Models\\', 'ForkBB\\', '\\'], '', get_class($this));
 
-        $link = explode(':', $this->c->METHODS[$key][$name], 2);
-        $factory = new $link[0]($this, $this->c);
-
-        if (isset($link[1])) {
-            return $factory->{$link[1]}(...$args);
-        } else {
-            return $factory->$name(...$args);
-        }
+        return $this->c->{$key . ucfirst($name)}->setModel($this)->$name(...$args);
     }
 }

+ 1 - 1
app/Models/Online.php

@@ -3,7 +3,7 @@
 namespace ForkBB\Models;
 
 use ForkBB\Models\Model;
-use ForkBB\Models\User;
+use ForkBB\Models\User\Model as User;
 use ForkBB\Models\Page;
 
 class Online extends Model

+ 2 - 2
app/Models/Online/Info.php

@@ -2,9 +2,9 @@
 
 namespace ForkBB\Models\Online;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Method;
 
-class Info extends MethodModel
+class Info extends Method
 {
     /**
      * Получение информации об онлайн посетителях

+ 10 - 10
app/Models/Pages/Auth.php

@@ -5,7 +5,7 @@ namespace ForkBB\Models\Pages;
 use ForkBB\Core\Validator;
 use ForkBB\Core\Exceptions\MailException;
 use ForkBB\Models\Page;
-use ForkBB\Models\User;
+use ForkBB\Models\User\Model as User;
 
 class Auth extends Page
 {
@@ -30,7 +30,7 @@ class Auth extends Page
 
         $this->c->Cookie->deleteUser();
         $this->c->Online->delete($this->c->user);
-        $this->c->user->updateLastVisit();
+        $this->c->users->updateLastVisit($this->c->user);
 
         $this->c->Lang->load('auth');
         return $this->c->Redirect->page('Index')->message(\ForkBB\__('Logout redirect'));
@@ -113,7 +113,7 @@ class Auth extends Page
     public function vLoginProcess(Validator $v, $password)
     {
         if (! empty($v->getErrors())) {
-        } elseif (! ($user = $this->c->ModelUser->load($v->username, 'username')) instanceof User) {
+        } elseif (! ($user = $this->c->users->load($v->username, 'username')) instanceof User) {
             $v->addError('Wrong user/pass');
         } elseif ($user->isUnverified) {
             $v->addError('Account is not activated', 'w');
@@ -142,7 +142,7 @@ class Auth extends Page
                     $user->registration_ip = $this->c->user->ip;
                 }
                 // изменения юзера в базе
-                $user->update();
+                $this->c->users->update($user);
 
                 $this->c->Online->delete($this->c->user);
                 $this->c->Cookie->setUser($user, (bool) $v->save);
@@ -229,7 +229,7 @@ class Auth extends Page
         if ($isSent) {
             $this->tmpUser->activate_string = $key;
             $this->tmpUser->last_email_sent = time();
-            $this->tmpUser->update();
+            $this->c->users->update($this->tmpUser);
             return $this->c->Message->message(\ForkBB\__('Forget mail', $this->c->config->o_admin_email), false, 200);
         } else {
             return $this->c->Message->message(\ForkBB\__('Error mail', $this->c->config->o_admin_email), true, 200);
@@ -250,7 +250,7 @@ class Auth extends Page
             return $email;
         }
             
-        $user = $this->c->ModelUser;
+        $user = $this->c->users->create();
         $user->__email = $email;
 
         // email забанен
@@ -283,7 +283,7 @@ class Auth extends Page
         } else {
             // что-то пошло не так
             if (! hash_equals($args['hash'], $this->c->Secury->hash($args['email'] . $args['key']))
-                || ! ($user = $this->c->ModelUser->load($args['email'], 'email')) instanceof User
+                || ! ($user = $this->c->users->load($args['email'], 'email')) instanceof User
                 || empty($user->activate_string)
                 || $user->activate_string{0} !== 'p'
                 || ! hash_equals($user->activate_string, $args['key'])
@@ -297,7 +297,7 @@ class Auth extends Page
         if ($user->isUnverified) {
             $user->group_id = $this->c->config->o_default_user_group;
             $user->email_confirmed = 1;
-            $user->update();
+            $this->c->users->update($user);
             $this->c->{'users_info update'};
             $this->a['fIswev']['i'][] = \ForkBB\__('Account activated');
         }
@@ -324,7 +324,7 @@ class Auth extends Page
     {
         // что-то пошло не так
         if (! hash_equals($args['hash'], $this->c->Secury->hash($args['email'] . $args['key']))
-            || ! ($user = $this->c->ModelUser->load($args['email'], 'email')) instanceof User
+            || ! ($user = $this->c->users->load($args['email'], 'email')) instanceof User
             || empty($user->activate_string)
             || $user->activate_string{0} !== 'p'
             || ! hash_equals($user->activate_string, $args['key'])
@@ -356,7 +356,7 @@ class Auth extends Page
         $user->password = password_hash($data['password'], PASSWORD_DEFAULT);
         $user->email_confirmed = 1;
         $user->activate_string = null;
-        $user->update();
+        $this->c->users->update($user);
 
         $this->a['fIswev']['s'][] = \ForkBB\__('Pass updated');
         return $this->login(['_redirect' => $this->c->Router->link('Index')]);

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

@@ -3,7 +3,7 @@
 namespace ForkBB\Models\Pages;
 
 use ForkBB\Models\Page;
-use ForkBB\Models\User;
+use ForkBB\Models\User\Model as User;
 
 class Ban extends Page
 {

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

@@ -32,7 +32,7 @@ trait CrumbTrait
                     }
 
                     if ($arg->page > 1) {
-                        $this->titles = $name . ' ' . \ForkBB\__('Page', $arg->page);
+                        $this->titles = $name . ' ' . \ForkBB\__('Page %s', $arg->page);
                     } else {
                         $this->titles = $name;
                     }

+ 123 - 0
app/Models/Pages/Delete.php

@@ -0,0 +1,123 @@
+<?php
+
+namespace ForkBB\Models\Pages;
+
+use ForkBB\Models\Page;
+
+class Delete extends Page
+{
+    use CrumbTrait;
+
+    /**
+     * Подготовка данных для шаблона удаления сообщения/темы
+     * 
+     * @param array $args
+     * 
+     * @return Page
+     */
+    public function delete(array $args)
+    {
+        $post = $this->c->posts->load((int) $args['id']);
+
+        if (empty($post) || ! $post->canDelete) {
+            return $this->c->Message->message('Bad request');
+        }
+
+        $topic       = $post->parent;
+        $deleteTopic = $post->id === $topic->first_post_id;
+
+        $this->c->Lang->load('delete');
+        
+        $this->nameTpl    = 'post';
+        $this->onlinePos  = 'topic-' . $topic->id;
+        $this->canonical  = $post->linkDelete;
+        $this->robots     = 'noindex';
+        $this->formTitle  = \ForkBB\__($deleteTopic ? 'Delete topic' : 'Delete post');
+        $this->crumbs     = $this->crumbs($this->formTitle, $topic);
+        $this->posts      = [$post];
+        $this->postsTitle = \ForkBB\__('Delete info');
+        $this->form       = [
+            'action' => $this->c->Router->link('DeletePost', ['id' => $post->id]),
+            'hidden' => [
+                'token' => $this->c->Csrf->create('DeletePost', ['id' => $post->id]),
+            ],
+            'sets'   => [
+                [
+                    'info' => [
+                        'info1' => [
+                            'type'    => '', //????
+                            'value'   => \ForkBB\__('Topic') . ' «' . \ForkBB\cens($topic->subject) . '»',
+                        ],
+                        'info2' => [
+                            'type'    => '', //????
+                            'value'   => \ForkBB\__($deleteTopic ? 'Topic by' : 'Reply by', $post->poster, \ForkBB\dt($post->posted)),
+                            'html'    => true,
+                        ],
+                    ],
+                ],
+                [
+                    'fields' => [
+                        'confirm' => [
+                            'type'    => 'checkbox',
+                            'label'   => \ForkBB\__($deleteTopic ? 'Confirm delete topic' : 'Confirm delete post'),
+                            'value'   => '1',
+                            'checked' => false,
+                        ],
+                    ],
+                ],
+            ],
+            'btns'   => [
+                'delete'  => [
+                    'type'      => 'submit', 
+                    'value'     => \ForkBB\__($deleteTopic ? 'Delete  topic' : 'Delete  post'), 
+                    'accesskey' => 'd',
+                ],
+                'cancel'  => [
+                    'type'      => 'submit', 
+                    'value'     => \ForkBB\__('Cancel'), 
+                ],
+            ],
+        ];
+                
+        return $this;
+    }
+
+    /**
+     * Обработка данных от формы удаления сообщения/темы
+     * 
+     * @param array $args
+     * 
+     * @return Page
+     */
+    public function deletePost(array $args)
+    {
+        $post = $this->c->posts->load((int) $args['id']);
+
+        if (empty($post) || ! $post->canDelete) {
+            return $this->c->Message->message('Bad request');
+        }
+
+        $topic       = $post->parent;
+        $deleteTopic = $post->id === $topic->first_post_id;
+
+        $this->c->Lang->load('delete');
+
+        $v = $this->c->Validator->setRules([
+            'token'   => 'token:DeletePost',
+            'confirm' => 'integer',
+            'delete'  => 'string',
+            'cancel'  => 'string',
+        ])->setArguments([
+            'token' => $args,
+        ]);
+
+        if (! $v->validation($_POST) || null === $v->delete) {
+            return $this->c->Redirect->page('ViewPost', $args)->message(\ForkBB\__('Cancel redirect'));
+        } elseif ($v->confirm !== 1) {
+            return $this->c->Redirect->page('ViewPost', $args)->message(\ForkBB\__('No confirm redirect'));
+        }
+
+
+
+    }
+}

+ 7 - 7
app/Models/Pages/Edit.php

@@ -3,7 +3,7 @@
 namespace ForkBB\Models\Pages;
 
 use ForkBB\Core\Validator;
-use ForkBB\Models\Post;
+use ForkBB\Models\Post\Model as Post;
 use ForkBB\Models\Page;
 
 class Edit extends Page
@@ -19,9 +19,9 @@ class Edit extends Page
      * 
      * @return Page
      */
-    public function edit(array $args, Post $post = null)
+    public function edit(array $args)
     {
-        $post = $post ?: $this->c->ModelPost->load((int) $args['id']);
+        $post = $this->c->posts->load((int) $args['id']);
 
         if (empty($post) || ! $post->canEdit) {
             return $this->c->Message->message('Bad request');
@@ -63,7 +63,7 @@ class Edit extends Page
      */
     public function editPost(array $args)
     {
-        $post = $this->c->ModelPost->load((int) $args['id']);
+        $post = $this->c->posts->load((int) $args['id']);
 
         if (empty($post) || ! $post->canEdit) {
             return $this->c->Message->message('Bad request');
@@ -148,19 +148,19 @@ class Edit extends Page
         }
 
         // обновление сообщения
-        $post->update();
+        $this->c->posts->update($post);
 
         // обновление темы
         if ($calcTopic) {
             $topic->calcStat();
         }
-        $topic->update();
+        $this->c->topics->update($topic);
 
         // обновление раздела
         if ($calcForum) {
             $topic->parent->calcStat();
         }
-        $topic->parent->update();
+        $this->c->forums->update($topic->parent);
         
         // антифлуд 
         if ($calcPost || $calcForum) { 

+ 17 - 17
app/Models/Pages/Post.php

@@ -5,7 +5,7 @@ namespace ForkBB\Models\Pages;
 use ForkBB\Core\Validator;
 use ForkBB\Models\Model;
 use ForkBB\Models\Forum;
-use ForkBB\Models\Topic;
+use ForkBB\Models\Topic\Model as Topic;
 use ForkBB\Models\Page;
 
 class Post extends Page
@@ -23,7 +23,7 @@ class Post extends Page
      */
     public function newTopic(array $args, Forum $forum = null)
     {
-        $forum = $forum ?: $this->c->forums->forum((int) $args['id']);
+        $forum = $forum ?: $this->c->forums->get((int) $args['id']);
 
         if (empty($forum) || $forum->redirect_url || ! $forum->canCreateTopic) {
             return $this->c->Message->message('Bad request');
@@ -51,7 +51,7 @@ class Post extends Page
      */
     public function newTopicPost(array $args)
     {
-        $forum = $this->c->forums->forum((int) $args['id']);
+        $forum = $this->c->forums->get((int) $args['id']);
         
         if (empty($forum) || $forum->redirect_url || ! $forum->canCreateTopic) {
             return $this->c->Message->message('Bad request');
@@ -82,16 +82,16 @@ class Post extends Page
      * 
      * @return Page
      */
-    public function newReply(array $args, Topic $topic = null)
+    public function newReply(array $args)
     {
-        $topic = $topic ?: $this->c->ModelTopic->load((int) $args['id']);
+        $topic = $this->c->topics->load((int) $args['id']);
 
         if (empty($topic) || $topic->moved_to || ! $topic->canReply) {
             return $this->c->Message->message('Bad request');
         }
 
         if (isset($args['quote'])) {
-            $post = $this->c->ModelPost->load((int) $args['quote'], $topic);
+            $post = $this->c->posts->load((int) $args['quote'], $topic->id);
 
             if (empty($post)) {
                 return $this->c->Message->message('Bad request');
@@ -125,7 +125,7 @@ class Post extends Page
      */
     public function newReplyPost(array $args)
     {
-        $topic = $this->c->ModelTopic->load((int) $args['id']);
+        $topic = $this->c->topics->load((int) $args['id']);
         
         if (empty($topic) || $topic->moved_to || ! $topic->canReply) {
             return $this->c->Message->message('Bad request');
@@ -146,7 +146,7 @@ class Post extends Page
             $this->previewHtml = $this->c->Parser->parseMessage(null, (bool) $v->hide_smilies);
         }
 
-        return $this->newReply($args, $topic);
+        return $this->newReply($args);
     }
 
     /**
@@ -188,7 +188,7 @@ class Post extends Page
         } else {
             $createTopic = true;
             $forum       = $model;
-            $topic       = $this->c->ModelTopic;
+            $topic       = $this->c->topics->create();
 
             $topic->subject     = $v->subject;
             $topic->poster      = $username;
@@ -202,12 +202,12 @@ class Post extends Page
 #           $topic->poll_term   = ;
 #           $topic->poll_kol    = ;
     
-            $topic->insert();
+            $this->c->topics->insert($topic);
         }
 
         // попытка объеденить новое сообщение с крайним в теме
         if ($merge) {
-            $lastPost  = $this->c->ModelPost->load($topic->last_post_id);
+            $lastPost  = $this->c->posts->load($topic->last_post_id, $topic->id);
             $newLength = mb_strlen($lastPost->message . $v->message, 'UTF-8');
 
             if ($newLength < $this->c->MAX_POST_SIZE - 100) {
@@ -215,7 +215,7 @@ class Post extends Page
                 $lastPost->edited    = $now;
                 $lastPost->edited_by = $username;
 
-                $lastPost->update();
+                $this->c->posts->update($lastPost);
             } else {
                 $merge = false;
             }
@@ -223,7 +223,7 @@ class Post extends Page
         
         // создание нового сообщения
         if (! $merge) {
-            $post = $this->c->ModelPost;
+            $post = $this->c->posts->create();
         
             $post->poster       = $username;
             $post->poster_id    = $this->c->user->id;
@@ -237,8 +237,8 @@ class Post extends Page
 #           $post->edited_by    =
             $post->user_agent   = $this->c->user->userAgent;
             $post->topic_id     = $topic->id;
-        
-            $post->insert();
+
+            $this->c->posts->insert($post);
         }
 
         if ($createTopic) {
@@ -247,8 +247,8 @@ class Post extends Page
         }
 
         // обновление данных в теме и разделе
-        $topic->calcStat()->update();
-        $forum->calcStat()->update();
+        $this->c->topics->update($topic->calcStat());
+        $this->c->forums->update($forum->calcStat());
 
         // обновление данных текущего пользователя
         if (! $merge && ! $user->isGuest && $forum->no_sum_mess != '1') {

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

@@ -17,7 +17,7 @@ trait PostValidatorTrait
      */
     public function vCheckEmail(Validator $v, $email)
     {
-        $user = $this->c->ModelUser;
+        $user = $this->c->users->create();
         $user->email = $email;
 
         // email забанен
@@ -37,7 +37,7 @@ trait PostValidatorTrait
      */
     public function vCheckUsername(Validator $v, $username)
     {
-        $user = $this->c->ModelUser;
+        $user = $this->c->users->create();
         $user->username = $username;
 
         // username = Гость

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

@@ -5,7 +5,7 @@ namespace ForkBB\Models\Pages;
 use ForkBB\Core\Validator;
 use ForkBB\Core\Exceptions\MailException;
 use ForkBB\Models\Page;
-use ForkBB\Models\User;
+use ForkBB\Models\User\Model as User;
 
 class Register extends Page
 {
@@ -72,7 +72,7 @@ class Register extends Page
      */
     public function vCheckEmail(Validator $v, $email)
     {
-        $user = $this->c->ModelUser;
+        $user = $this->c->users->create();
         $user->__email = $email;
 
         // email забанен
@@ -95,7 +95,7 @@ class Register extends Page
      */
     public function vCheckUsername(Validator $v, $username)
     {
-        $user = $this->c->ModelUser;
+        $user = $this->c->users->create();
         $user->__username = $username;
 
         // username = Гость
@@ -108,7 +108,7 @@ class Register extends Page
         } elseif ($this->c->bans->isBanned($user) > 0) {
             $v->addError('Banned username');
         // есть пользователь с похожим именем
-        } elseif (empty($v->getErrors()) && ! $user->isUnique()) {
+        } elseif (empty($v->getErrors()) && ! $this->c->users->isUniqueName($user)) {
             $v->addError('Username not unique');
         }
         return $username;
@@ -131,7 +131,7 @@ class Register extends Page
             $key = null;
         }
 
-        $user = $this->c->ModelUser;
+        $user = $this->c->users->create();
         
         $user->username        = $v->username;
         $user->password        = password_hash($v->password, PASSWORD_DEFAULT);
@@ -233,7 +233,7 @@ class Register extends Page
     public function activate(array $args)
     {
         if (! hash_equals($args['hash'], $this->c->Secury->hash($args['id'] . $args['key']))
-            || ! ($user = $this->c->ModelUser->load($args['id'])) instanceof User
+            || ! ($user = $this->c->users->load($args['id'])) instanceof User
             || empty($user->activate_string)
             || $user->activate_string{0} !== 'w'
             || ! hash_equals($user->activate_string, $args['key'])

+ 6 - 1
app/Models/Pages/Topic.php

@@ -105,7 +105,12 @@ class Topic extends Page
      */
     protected function view($type, array $args)
     {
-        $topic = $this->c->ModelTopic->load((int) $args['id'], $type === 'post');
+        if ($type === 'post') {
+            $post  = $this->c->posts->load((int) $args['id']);
+            $topic = null === $post ? null : $post->parent;
+        } else {
+            $topic = $this->c->topics->load((int) $args['id']);
+        }
 
         if (empty($topic) || ! $topic->last_post_id) {
             return $this->c->Message->message('Bad request');

+ 84 - 39
app/Models/Post/Load.php

@@ -2,10 +2,9 @@
 
 namespace ForkBB\Models\Post;
 
-use ForkBB\Models\MethodModel;
-use ForkBB\Models\Topic;
+use ForkBB\Models\Action;
 
-class Load extends MethodModel
+class Load extends Action
 {
     protected $aliases;
 
@@ -25,15 +24,16 @@ class Load extends MethodModel
 
                 if (true === $replName && isset($fields[$originalName])) {
                     $replName = "alias_{$alias}_{$originalName}";
-                }
-
-                if (true === $replName) {
+                    $result[] = $name . ' AS '. $replName;
+                    $aliases[$alias][$replName] = $originalName;
+                    $fields[$replName] = $alias;
+                } elseif (true === $replName) {
                     $result[] = $name;
                     $aliases[$alias][$originalName] = true;
                     $fields[$originalName] = $alias;
                 } else {
                     $result[] = $name . ' AS '. $replName;
-                    $aliases[$alias][$replName] = $originalName;
+                    $aliases[$alias][$replName] = $replName; //???? $originalName;
                     $fields[$replName] = $alias;
                 }
             }
@@ -45,74 +45,119 @@ class Load extends MethodModel
 
     protected function setData(array $args, array $data)
     {
-        foreach ($args as $alias => $model) {
+        foreach ($args as $aliases => $model) {
             $attrs = [];
-            foreach ($this->aliases[$alias] as $key => $repl) {
-                $name = true === $repl ? $key : $repl;
-                $attrs[$name] = $data[$key];
+            foreach (explode('.', $aliases) as $alias) {
+                foreach ($this->aliases[$alias] as $key => $repl) {
+                    $name = true === $repl ? $key : $repl;
+                    $attrs[$name] = $data[$key];
+                }
             }
             $model->setAttrs($attrs);
         }
     }
 
     /**
-     * Заполняет модель данными из БД
-     *
+     * Загружает сообщение из БД с проверкой вхождения в указанную тему
+     * Проверка доступности
+     * 
      * @param int $id
-     * @param Topic $topic
-     *
+     * @param int $tid
+     * 
      * @return null|Post
      */
-    public function load($id, Topic $topic = null)
+    public function loadFromTopic($id, $tid)
     {
-        // пост + топик
-        if (null === $topic) {
+        $vars = [
+            ':pid' => $id,
+            ':tid' => $tid,
+        ];
+        $sql = 'SELECT p.* 
+                FROM ::posts AS p 
+                WHERE p.id=?i:pid AND p.topic_id=?i:tid';
+    
+        $data = $this->c->DB->query($sql, $vars)->fetch();
+                    
+        // сообщение отсутствует или недоступено
+        if (empty($data)) {
+            return null;
+        }
 
-            $fields = $this->queryFields([
-                'p' => array_map(function($val) {return true;}, $this->c->dbMap->posts), // все поля в true
-                't' => array_map(function($val) {return true;}, $this->c->dbMap->topics), // все поля в true
-            ]);
+        $post  = $this->manager->create($data);
+        $topic = $post->parent;
 
+        if (empty($topic) || $topic->moved_to || ! $topic->parent) {
+            return null;
+        }
+
+        return $post;
+    }
+
+    /**
+     * Загружает сообщение из БД 
+     * Загружает тему этого сообщения
+     * Проверка доступности
+     *
+     * @param int $id
+     *
+     * @return null|Post
+     */
+    public function load($id)
+    {
+        if ($this->c->user->isGuest) {
             $vars = [
                 ':pid' => $id,
-                ':fields' => $fields,
+                ':fields' => $this->queryFields([
+                    'p' => array_map(function($val) {return true;}, $this->c->dbMap->posts), // все поля в true
+                    't' => array_map(function($val) {return true;}, $this->c->dbMap->topics), // все поля в true
+                ]),
             ];
 
             $sql = 'SELECT ?p:fields
                     FROM ::posts AS p 
                     INNER JOIN ::topics AS t ON t.id=p.topic_id
                     WHERE p.id=?i:pid';
-        // только пост
         } else {
             $vars = [
                 ':pid' => $id,
-                ':tid' => $topic->id,
+                ':uid' => $this->c->user->id,
+                ':fields' => $this->queryFields([
+                    'p'   => array_map(function($val) {return true;}, $this->c->dbMap->posts), // все поля в true
+                    't'   => array_map(function($val) {return true;}, $this->c->dbMap->topics), // все поля в true
+                    's'   => ['user_id' => 'is_subscribed'],
+                    'mof' => ['mf_mark_all_read' => true],
+                    'mot' => ['mt_last_visit' => true, 'mt_last_read' => true],
+                ]),
             ];
 
-            $sql = 'SELECT p.* 
-                    FROM ::posts AS p 
-                    WHERE p.id=?i:pid AND p.topic_id=?i:tid';
+            $sql = 'SELECT ?p:fields
+                    FROM ::posts AS p
+                    INNER JOIN ::topics AS t ON t.id=p.topic_id
+                    LEFT JOIN ::topic_subscriptions AS s ON (t.id=s.topic_id AND s.user_id=?i:uid)
+                    LEFT JOIN ::mark_of_forum AS mof ON (mof.uid=?i:uid AND t.forum_id=mof.fid)
+                    LEFT JOIN ::mark_of_topic AS mot ON (mot.uid=?i:uid AND t.id=mot.tid)
+                    WHERE p.id=?i:pid';
         }
 
         $data = $this->c->DB->query($sql, $vars)->fetch();
                     
-        // пост отсутствует или недоступен
+        // сообщение отсутствует или недоступено
         if (empty($data)) {
             return null;
         }
 
-        if (null === $topic) {
-            $topic = $this->c->ModelTopic;
-            $this->setData(['p' => $this->model, 't' => $topic], $data);
-        } else {
-            $this->model->setAttrs($data);
-        }
-        $this->model->__parent = $topic;
+        $post  = $this->manager->create();
+        $topic = $this->c->topics->create();
+        $this->setData(['p' => $post, 't.s.mof.mot' => $topic], $data);
 
-        if ($topic->moved_to || ! $topic->parent) { //????
+        if ($topic->moved_to || ! $topic->parent) {
             return null;
         }
-        
-        return $this->model;
+
+        $topic->parent->__mf_mark_all_read = $topic->mf_mark_all_read; //????
+
+        $this->c->topics->set($topic->id, $topic);
+
+        return $post;
     }
 }

+ 76 - 0
app/Models/Post/Manager.php

@@ -0,0 +1,76 @@
+<?php
+
+namespace ForkBB\Models\Post;
+
+use ForkBB\Models\ManagerModel;
+use ForkBB\Models\Post\Model as Post;
+use RuntimeException;
+
+class Manager extends ManagerModel
+{
+    /**
+     * Создает новую модель сообщения
+     * 
+     * @param array $attrs
+     * 
+     * @return Post
+     */
+    public function create(array $attrs = [])
+    {
+        return $this->c->PostModel->setAttrs($attrs);
+    }
+
+    /**
+     * Загружает сообщение из БД 
+     * 
+     * @param int $id
+     * @param int $tid
+     * 
+     * @return null|Post
+     */
+    public function load($id, $tid = null)
+    {
+        $post = $this->get($id);
+
+        if ($post instanceof Post) {
+            if (null !== $tid && $post->topic_id !== $tid) {
+                return null;
+            }
+        } else {
+            if (null !== $tid) {
+                $post = $this->Load->loadFromTopic($id, $tid);
+            } else {
+                $post = $this->Load->load($id);
+            }
+            $this->set($id, $post);
+        }
+
+        return $post;
+    }
+
+    /**
+     * Обновляет сообщение в БД
+     *
+     * @param Post $post
+     * 
+     * @return Post
+     */
+    public function update(Post $post)
+    {
+        return $this->Save->update($post);
+    }
+
+    /**
+     * Добавляет новое сообщение в БД
+     *
+     * @param Post $post
+     * 
+     * @return int
+     */
+    public function insert(Post $post)
+    {
+        $id = $this->Save->insert($post);
+        $this->set($id, $post);
+        return $id;
+    }
+}

+ 36 - 6
app/Models/Post.php → app/Models/Post/Model.php

@@ -1,13 +1,29 @@
 <?php
 
-namespace ForkBB\Models;
+namespace ForkBB\Models\Post;
 
 use ForkBB\Models\DataModel;
-use ForkBB\Core\Container;
+use ForkBB\Models\User\Model as User;
 use RuntimeException;
 
-class Post extends DataModel
+class Model extends DataModel
 {
+    /**
+     * Получение родительского раздела
+     *
+     * @throws RuntimeException
+     *
+     * @return Topic
+     */
+    protected function getparent()
+    {
+        if ($this->topic_id < 1) {
+            throw new RuntimeException('Parent is not defined');
+        }
+
+        return $this->c->topics->get($this->topic_id);
+    }
+
     /**
      * Ссылка на сообщение
      *
@@ -25,9 +41,23 @@ class Post extends DataModel
      */
     protected function getuser() //????
     {
-        $attrs = $this->a; //????
-        $attrs['id'] = $attrs['poster_id'];
-        return $this->c->ModelUser->setAttrs($attrs);
+        $user = $this->c->users->get($this->poster_id);
+
+        if (! $user instanceof User) {
+            var_dump($this->poster_id);
+            $attrs = $this->a; //????
+            $attrs['id'] = $attrs['poster_id'];
+
+            $user = $this->c->users->create($attrs);
+
+            if ($user->isGuest) {
+                $user->__email = $this->poster_email;
+            } else {
+                $this->c->users->set($user->id, $user);
+            }
+        }
+
+        return $user; 
     }
 
     /**

+ 25 - 20
app/Models/Post/Save.php

@@ -2,28 +2,31 @@
 
 namespace ForkBB\Models\Post;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Action;
+use ForkBB\Models\Post\Model as Post;
 use RuntimeException;
 
-class Save extends MethodModel
+class Save extends Action
 {
     /**
-     * Обновляет данные пользователя
+     * Обновляет сообщение в БД
      *
+     * @param Post $post
+     * 
      * @throws RuntimeException
      * 
      * @return Post
      */
-    public function update()
+    public function update(Post $post)
     {
-        if (empty($this->model->id)) {
+        if ($post->id < 1) {
             throw new RuntimeException('The model does not have ID');
         }
-        $modified = $this->model->getModified();
+        $modified = $post->getModified();
         if (empty($modified)) {
-            return $this->model;
+            return $post;
         }
-        $values = $this->model->getAttrs();
+        $values = $post->getAttrs();
         $fileds = $this->c->dbMap->posts;
         $set = $vars = [];
         foreach ($modified as $name) {
@@ -34,29 +37,31 @@ class Save extends MethodModel
             $set[] = $name . '=?' . $fileds[$name];
         }
         if (empty($set)) {
-            return $this->model;
+            return $post;
         }
-        $vars[] = $this->model->id;
+        $vars[] = $post->id;
         $this->c->DB->query('UPDATE ::posts SET ' . implode(', ', $set) . ' WHERE id=?i', $vars);
-        $this->model->resModified();
+        $post->resModified();
 
-        return $this->model;
+        return $post;
     }
 
     /**
-     * Добавляет новую запись в таблицу пользователей
+     * Добавляет новое сообщение в БД
      *
+     * @param Post $post
+     * 
      * @throws RuntimeException
      * 
      * @return int
      */
-    public function insert()
+    public function insert(Post $post)
     {
-        $modified = $this->model->getModified();
-        if (null !== $this->model->id || in_array('id', $modified)) {
+        $modified = $post->getModified();
+        if (null !== $post->id || in_array('id', $modified)) {
             throw new RuntimeException('The model has ID');
         }
-        $values = $this->model->getAttrs();
+        $values = $post->getAttrs();
         $fileds = $this->c->dbMap->posts;
         $set = $set2 = $vars = [];
         foreach ($modified as $name) {
@@ -71,9 +76,9 @@ class Save extends MethodModel
             throw new RuntimeException('The model is empty');
         }
         $this->c->DB->query('INSERT INTO ::posts (' . implode(', ', $set) . ') VALUES (' . implode(', ', $set2) . ')', $vars);
-        $this->model->id = $this->c->DB->lastInsertId();
-        $this->model->resModified();
+        $post->id = $this->c->DB->lastInsertId();
+        $post->resModified();
 
-        return $this->model->id;
+        return $post->id;
     }
 }

+ 2 - 2
app/Models/SmileyList/Load.php

@@ -2,9 +2,9 @@
 
 namespace ForkBB\Models\SmileyList;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Method;
 
-class Load extends MethodModel
+class Load extends Method
 {
     /**
      * Заполняет модель данными из БД

+ 2 - 2
app/Models/Stats/Load.php

@@ -2,9 +2,9 @@
 
 namespace ForkBB\Models\Stats;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Method;
 
-class Load extends MethodModel
+class Load extends Method
 {
     /**
      * Заполняет модель данными из БД

+ 2 - 2
app/Models/Topic/CalcStat.php

@@ -2,10 +2,10 @@
 
 namespace ForkBB\Models\Topic;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Method;
 use RuntimeException;
 
-class CalcStat extends MethodModel
+class CalcStat extends Method
 {
     /**
      * Пересчитывает статистику темы

+ 23 - 53
app/Models/Topic/Load.php

@@ -2,58 +2,36 @@
 
 namespace ForkBB\Models\Topic;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Action;
+use ForkBB\Models\Topic\Model as Topic;
 
-class Load extends MethodModel
+class Load extends Action
 {
     /**
-     * Заполняет модель данными из БД
+     * Загружает тему из БД
      *
      * @param int $id
-     * @param bool $isPost
      *
      * @return null|Topic
      */
-    public function load($id, $isPost = false)
+    public function load($id)
     {
-        if ($isPost) {
-            $vars = [
-                ':pid' => $id,
-                ':uid' => $this->c->user->id,
-            ];
-            if ($this->c->user->isGuest) {
-                $sql = 'SELECT t.*
-                        FROM ::topics AS t
-                        INNER JOIN ::posts AS p ON t.id=p.topic_id
-                        WHERE p.id=?i:pid';
+        $vars = [
+            ':tid' => $id,
+            ':uid' => $this->c->user->id,
+        ];
+        if ($this->c->user->isGuest) {
+            $sql = 'SELECT t.*
+                    FROM ::topics AS t
+                    WHERE t.id=?i:tid';
 
-            } else {
-                $sql = 'SELECT t.*, s.user_id AS is_subscribed, mof.mf_mark_all_read, mot.mt_last_visit, mot.mt_last_read
-                        FROM ::topics AS t
-                        INNER JOIN ::posts AS p ON t.id=p.topic_id
-                        LEFT JOIN ::topic_subscriptions AS s ON (t.id=s.topic_id AND s.user_id=?i:uid)
-                        LEFT JOIN ::mark_of_forum AS mof ON (mof.uid=?i:uid AND t.forum_id=mof.fid)
-                        LEFT JOIN ::mark_of_topic AS mot ON (mot.uid=?i:uid AND t.id=mot.tid)
-                        WHERE p.id=?i:pid';
-            }
         } else {
-            $vars = [
-                ':tid' => $id,
-                ':uid' => $this->c->user->id,
-            ];
-            if ($this->c->user->isGuest) {
-                $sql = 'SELECT t.*
-                        FROM ::topics AS t
-                        WHERE t.id=?i:tid';
-
-            } else {
-                $sql = 'SELECT t.*, s.user_id AS is_subscribed, mof.mf_mark_all_read, mot.mt_last_visit, mot.mt_last_read
-                        FROM ::topics AS t
-                        LEFT JOIN ::topic_subscriptions AS s ON (t.id=s.topic_id AND s.user_id=?i:uid)
-                        LEFT JOIN ::mark_of_forum AS mof ON (mof.uid=?i:uid AND t.forum_id=mof.fid)
-                        LEFT JOIN ::mark_of_topic AS mot ON (mot.uid=?i:uid AND t.id=mot.tid)
-                        WHERE t.id=?i:tid';
-            }
+            $sql = 'SELECT t.*, s.user_id AS is_subscribed, mof.mf_mark_all_read, mot.mt_last_visit, mot.mt_last_read
+                    FROM ::topics AS t
+                    LEFT JOIN ::topic_subscriptions AS s ON (t.id=s.topic_id AND s.user_id=?i:uid)
+                    LEFT JOIN ::mark_of_forum AS mof ON (mof.uid=?i:uid AND t.forum_id=mof.fid)
+                    LEFT JOIN ::mark_of_topic AS mot ON (mot.uid=?i:uid AND t.id=mot.tid)
+                    WHERE t.id=?i:tid';
         }
 
         $data = $this->c->DB->query($sql, $vars)->fetch();
@@ -63,22 +41,14 @@ class Load extends MethodModel
             return null;
         }
 
-        if (! $this->c->user->isGuest) {
-            $forForum['mf_mark_all_read'] = $data['mf_mark_all_read'];
-            unset($data['mf_mark_all_read']);
-        }
-        $this->model->setAttrs($data);
-        $forum = $this->model->parent;
+        $topic = $this->manager->create($data);
 
-        // раздел недоступен
-        if (empty($forum)) {
+        if (! $topic->parent) {
             return null;
         }
 
-        if (! empty($forForum)) {
-            $forum->replAttrs($forForum);
-        }
+        $topic->parent->__mf_mark_all_read = $topic->mf_mark_all_read;
 
-        return $this->model;
+        return $topic;
     }
 }

+ 67 - 0
app/Models/Topic/Manager.php

@@ -0,0 +1,67 @@
+<?php
+
+namespace ForkBB\Models\Topic;
+
+use ForkBB\Models\ManagerModel;
+use ForkBB\Models\Topic\Model as Topic;
+use RuntimeException;
+
+class Manager extends ManagerModel
+{
+    /**
+     * Создает новую модель темы
+     * 
+     * @param array $attrs
+     * 
+     * @return Topic
+     */
+    public function create(array $attrs = [])
+    {
+        return $this->c->TopicModel->setAttrs($attrs);
+    }
+
+    /**
+     * Загружает тему из БД
+     *
+     * @param int $id
+     *
+     * @return null|Topic
+     */
+    public function load($id)
+    {
+        $topic = $this->get($id);
+
+        if (! $topic instanceof Topic) {
+            $topic = $this->Load->load($id);
+            $this->set($id, $topic);
+        }
+
+        return $topic;
+    }
+
+    /**
+     * Обновляет тему в БД
+     *
+     * @param Topic $topic
+     * 
+     * @return Topic
+     */
+    public function update(Topic $topic)
+    {
+        return $this->Save->update($topic);
+    }
+
+    /**
+     * Добавляет новую тему в БД
+     *
+     * @param Topic $topic
+     * 
+     * @return int
+     */
+    public function insert(Topic $topic)
+    {
+        $id = $this->Save->insert($topic);
+        $this->set($id, $topic);
+        return $id;
+    }
+}

+ 6 - 9
app/Models/Topic.php → app/Models/Topic/Model.php

@@ -1,19 +1,18 @@
 <?php
 
-namespace ForkBB\Models;
+namespace ForkBB\Models\Topic;
 
 use ForkBB\Models\DataModel;
-use ForkBB\Core\Container;
 use RuntimeException;
 
-class Topic extends DataModel
+class Model extends DataModel
 {
     /**
      * Получение родительского раздела
      *
      * @throws RuntimeException
      *
-     * @return Models\Forum
+     * @return Forum
      */
     protected function getparent()
     {
@@ -21,7 +20,7 @@ class Topic extends DataModel
             throw new RuntimeException('Parent is not defined');
         }
 
-        return $this->c->forums->forum($this->forum_id);
+        return $this->c->forums->get($this->forum_id);
     }
 
     /**
@@ -296,7 +295,7 @@ class Topic extends DataModel
                        u.email_setting, u.num_posts, u.registered, u.admin_note, u.messages_enable,
                        u.group_id,
                        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,
+                       p.hide_smilies, p.posted, p.edited, p.edited_by, p.edit_post, p.user_agent, p.topic_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
@@ -325,9 +324,7 @@ class Topic extends DataModel
                 $cur['warnings'] = $warnings[$cur['id']];
             }
 
-            $cur['parent'] = $this;
-
-            $cur = $this->c->ModelPost->setAttrs($cur);
+            $cur = $this->c->posts->create($cur);
         }
         unset($cur);
         $this->timeMax = $timeMax;

+ 25 - 20
app/Models/Topic/Save.php

@@ -2,28 +2,31 @@
 
 namespace ForkBB\Models\Topic;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Action;
+use ForkBB\Models\Topic\Model as Topic;
 use RuntimeException;
 
-class Save extends MethodModel
+class Save extends Action
 {
     /**
-     * Обновляет данные пользователя
+     * Обновляет тему в БД
      *
+     * @param Topic $topic
+     * 
      * @throws RuntimeException
      * 
      * @return Topic
      */
-    public function update()
+    public function update(Topic $topic)
     {
-        if (empty($this->model->id)) {
+        if ($topic->id < 1) {
             throw new RuntimeException('The model does not have ID');
         }
-        $modified = $this->model->getModified();
+        $modified = $topic->getModified();
         if (empty($modified)) {
-            return $this->model;
+            return $topic;
         }
-        $values = $this->model->getAttrs();
+        $values = $topic->getAttrs();
         $fileds = $this->c->dbMap->topics;
         $set = $vars = [];
         foreach ($modified as $name) {
@@ -34,29 +37,31 @@ class Save extends MethodModel
             $set[] = $name . '=?' . $fileds[$name];
         }
         if (empty($set)) {
-            return $this->model;
+            return $topic;
         }
-        $vars[] = $this->model->id;
+        $vars[] = $topic->id;
         $this->c->DB->query('UPDATE ::topics SET ' . implode(', ', $set) . ' WHERE id=?i', $vars);
-        $this->model->resModified();
+        $topic->resModified();
 
-        return $this->model;
+        return $topic;
     }
 
     /**
-     * Добавляет новую запись в таблицу пользователей
+     * Добавляет новую тему в БД
      *
+     * @param Topic $topic
+     * 
      * @throws RuntimeException
      * 
      * @return int
      */
-    public function insert()
+    public function insert(Topic $topic)
     {
-        $modified = $this->model->getModified();
-        if (null !== $this->model->id || in_array('id', $modified)) {
+        $modified = $topic->getModified();
+        if (null !== $topic->id || in_array('id', $modified)) {
             throw new RuntimeException('The model has ID');
         }
-        $values = $this->model->getAttrs();
+        $values = $topic->getAttrs();
         $fileds = $this->c->dbMap->topics;
         $set = $set2 = $vars = [];
         foreach ($modified as $name) {
@@ -71,9 +76,9 @@ class Save extends MethodModel
             throw new RuntimeException('The model is empty');
         }
         $this->c->DB->query('INSERT INTO ::topics (' . implode(', ', $set) . ') VALUES (' . implode(', ', $set2) . ')', $vars);
-        $this->model->id = $this->c->DB->lastInsertId();
-        $this->model->resModified();
+        $topic->id = $this->c->DB->lastInsertId();
+        $topic->resModified();
 
-        return $this->model->id;
+        return $topic->id;
     }
 }

+ 38 - 32
app/Models/User/LoadUserFromCookie.php → app/Models/User/Current.php

@@ -2,10 +2,10 @@
 
 namespace ForkBB\Models\User;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Action;
 use RuntimeException;
 
-class LoadUserFromCookie extends MethodModel
+class Current extends Action
 {
     /**
      * Получение юзера на основе куки авторизации
@@ -13,32 +13,32 @@ class LoadUserFromCookie extends MethodModel
      *
      * @return User
      */
-    public function loadCurrent()
+    public function current()
     {
         $cookie = $this->c->Cookie;
-        $this->loadUser((int) $cookie->uId);
+        $user = $this->load((int) $cookie->uId);
 
-        if (! $this->model->isGuest) {
-            if (! $cookie->verifyUser($this->model)) {
-                $this->model = $this->loadUser(1);
+        if (! $user->isGuest) {
+            if (! $cookie->verifyUser($user)) {
+                $user = $this->load(1);
             } elseif ($this->c->config->o_check_ip == '1'
-                && $this->model->isAdmMod
-                && $this->model->registration_ip !== $this->model->ip
+                && $user->isAdmMod
+                && $user->registration_ip !== $user->ip
             ) {
-                $this->model = $this->loadUser(1);
+                $user = $this->load(1);
             }
         }
 
-        $cookie->setUser($this->model);
+        $cookie->setUser($user);
 
-        if ($this->model->isGuest) {
-            $this->model->__isBot = $this->isBot();
-            $this->model->__disp_topics = $this->c->config->o_disp_topics_default;
-            $this->model->__disp_posts = $this->c->config->o_disp_posts_default;
-            $this->model->__timezone = $this->c->config->o_default_timezone;
-            $this->model->__dst = $this->c->config->o_default_dst;
-#            $this->model->language = $this->c->config->o_default_lang;
-#            $this->model->style = $this->c->config->o_default_style;
+        if ($user->isGuest) {
+            $user->__isBot = $this->isBot();
+            $user->__disp_topics = $this->c->config->o_disp_topics_default;
+            $user->__disp_posts = $this->c->config->o_disp_posts_default;
+            $user->__timezone = $this->c->config->o_default_timezone;
+            $user->__dst = $this->c->config->o_default_dst;
+#            $user->language = $this->c->config->o_default_lang;
+#            $user->style = $this->c->config->o_default_style;
 
             // быстрое переключение языка - Visman
 /*            $language = $this->cookie->get('glang');
@@ -46,24 +46,26 @@ class LoadUserFromCookie extends MethodModel
                 $language = preg_replace('%[^a-zA-Z0-9_]%', '', $language);
                 $languages = forum_list_langs();
                 if (in_array($language, $languages)) {
-                    $this->model->language = $language;
+                    $user->language = $language;
                 }
             } */
         } else {
-            $this->model->__isBot = false;
-            if (! $this->model->disp_topics) {
-                $this->model->__disp_topics = $this->c->config->o_disp_topics_default;
+            $user->__isBot = false;
+            if (! $user->disp_topics) {
+                $user->__disp_topics = $this->c->config->o_disp_topics_default;
             }
-            if (! $this->model->disp_posts) {
-                $this->model->__disp_posts = $this->c->config->o_disp_posts_default;
+            if (! $user->disp_posts) {
+                $user->__disp_posts = $this->c->config->o_disp_posts_default;
             }
             // Special case: We've timed out, but no other user has browsed the forums since we timed out
-            if ($this->model->isLogged && $this->model->logged < time() - $this->c->config->o_timeout_visit) {
-                $this->model->updateLastVisit();
+            if ($user->isLogged && $user->logged < time() - $this->c->config->o_timeout_visit) {
+                $this->manager->updateLastVisit($user); //????
             }
+
+            $this->manager->set($user->id, $user);
         }
 
-        return $this->model;
+        return $user;
     }
 
     /**
@@ -72,8 +74,10 @@ class LoadUserFromCookie extends MethodModel
      * @param int $id
      *
      * @throws RuntimeException
+     * 
+     * @return User;
      */
-    public function loadUser($id)
+    protected function load($id)
     {
         $data = null;
         $ip = $this->getIp();
@@ -86,9 +90,11 @@ class LoadUserFromCookie extends MethodModel
                 throw new RuntimeException('Unable to fetch guest information. Your database must contain both a guest user and a guest user group');
             }
         }
-        $this->model->setAttrs($data);
-        $this->model->__ip = $ip;
-        $this->model->__userAgent = $this->getUserAgent();
+
+        $user = $this->manager->create($data);
+        $user->__ip = $ip;
+        $user->__userAgent = $this->getUserAgent();
+        return $user;
     }
 
     /**

+ 9 - 9
app/Models/User/IsUnique.php → app/Models/User/IsUniqueName.php

@@ -2,23 +2,23 @@
 
 namespace ForkBB\Models\User;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Action;
+use ForkBB\Models\User\Model as User;
 
-class IsUnique extends MethodModel
+class IsUniqueName extends Action
 {
     /**
      * Проверка на уникальность имени пользователя
-     * @param string $username
+     * 
+     * @param User $user
+     * 
      * @return bool
      */
-    public function isUnique($username = null)
+    public function isUniqueName(User $user)
     {
-        if (null === $username) {
-            $username = $this->model->username;
-        }
         $vars = [
-            ':name' => $username,
-            ':other' => preg_replace('%[^\p{L}\p{N}]%u', '', $username),
+            ':name' => $user->username,
+            ':other' => preg_replace('%[^\p{L}\p{N}]%u', '', $user->username), //????
         ];
         $result = $this->c->DB->query('SELECT username FROM ::users WHERE UPPER(username)=UPPER(?s:name) OR UPPER(username)=UPPER(?s:other)', $vars)->fetchAll();
         return ! count($result);

+ 21 - 14
app/Models/User/Load.php

@@ -2,45 +2,52 @@
 
 namespace ForkBB\Models\User;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Action;
 use InvalidArgumentException;
 
-class Load extends MethodModel
+class Load extends Action
 {
     /**
      * Получение пользователя по условию
+     * 
      * @param mixed $value
      * @param string $field
+     * 
      * @throws InvalidArgumentException
+     * 
      * @return int|User
      */
     public function load($value, $field = 'id')
     {
         switch ($field) {
             case 'id':
-                $where = 'u.id= ?i';
+                $where = 'u.id=?i:field';
                 break;
             case 'username':
-                $where = 'u.username= ?s';
+                $where = 'u.username=?s:field';
                 break;
             case 'email':
-                $where = 'u.email= ?s';
+                $where = 'u.email=?s:field';
                 break;
             default:
                 throw new InvalidArgumentException('Field not supported');
         }
+        $vars = [':field' => $value];
+        $sql = 'SELECT u.*, g.* 
+                FROM ::users AS u 
+                LEFT JOIN ::groups AS g ON u.group_id=g.g_id 
+                WHERE ' . $where;
 
-        $data = $this->c->DB->query('SELECT u.*, g.* FROM ::users AS u LEFT JOIN ::groups AS g ON u.group_id=g.g_id WHERE ' . $where, [$value])
-            ->fetchAll();
+        $data = $this->c->DB->query($sql, $vars)->fetchAll();
 
         // число найденных пользователей отлично от одного
-        if (count($data) !== 1) {
-            return count($data);
+        $count = count($data);
+        if ($count !== 1) {
+            return $count;
         }
-        // найден гость
-        if ($data[0]['id'] < 2) {
-            return 1;
-        }
-        return $this->model->setAttrs($data[0]);
+
+        $user = $this->manager->create($data[0]);
+
+        return $user->isGuest ? 1 : $user;
     }
 }

+ 77 - 0
app/Models/User/Manager.php

@@ -0,0 +1,77 @@
+<?php
+
+namespace ForkBB\Models\User;
+
+use ForkBB\Models\ManagerModel;
+use ForkBB\Models\User\Model as User;
+use RuntimeException;
+
+class Manager extends ManagerModel
+{
+    /**
+     * Создает новую модель пользователя
+     * 
+     * @param array $attrs
+     * 
+     * @return User
+     */
+    public function create(array $attrs = [])
+    {
+        return $this->c->UserModel->setAttrs($attrs);
+    }
+
+    /**
+     * Получение пользователя по условию
+     * 
+     * @param mixed $value
+     * @param string $field
+     * 
+     * @return int|User
+     */
+    public function load($value, $field = 'id')
+    {
+        $user = $field === 'id' ? $this->get($value) : null;
+
+        if (! $user instanceof User) {
+            $user = $this->Load->load($value, $field);
+
+            if ($user instanceof User) {
+                $test = $this->get($user->id);
+
+                if ($test instanceof User) {
+                    return $test;
+                }
+
+                $this->set($user->id, $user);
+            }
+        }
+
+        return $user;
+    }
+
+    /**
+     * Обновляет данные пользователя
+     *
+     * @param User $user
+     * 
+     * @return User
+     */
+    public function update(User $user)
+    {
+        return $this->Save->update($user);
+    }
+
+    /**
+     * Добавляет новую запись в таблицу пользователей
+     *
+     * @param User $user
+     * 
+     * @return int
+     */
+    public function insert(User $user)
+    {
+        $id = $this->Save->insert($topic);
+        $this->set($id, $topic);
+        return $id;
+    }
+}

+ 6 - 6
app/Models/User.php → app/Models/User/Model.php

@@ -1,13 +1,13 @@
 <?php
 
-namespace ForkBB\Models;
+namespace ForkBB\Models\User;
 
 use ForkBB\Models\DataModel;
-use ForkBB\Models\Model;
+use ForkBB\Models\Model as BaseModel;
 use ForkBB\Models\Forum;
 use RuntimeException;
 
-class User extends DataModel
+class Model extends DataModel
 {
     /**
      * Статус неподтвержденного
@@ -55,13 +55,13 @@ class User extends DataModel
     /**
      * Статус модератора для указанной модели
      * 
-     * @param Model $model
+     * @param BaseModel $model
      * 
      * @throws RuntimeException
      * 
      * @return bool
      */
-    public function isModerator(Model $model)
+    public function isModerator(BaseModel $model)
     {
         if ($this->g_moderator != '1') {
             return false;
@@ -69,7 +69,7 @@ class User extends DataModel
         
         while (! $model instanceof Forum) {
             $model = $model->parent;
-            if (! $model instanceof Model) {
+            if (! $model instanceof BaseModel) {
                 throw new RuntimeException('Moderator\'s rights can not be found');
             }
         }

+ 26 - 21
app/Models/User/Save.php

@@ -2,30 +2,33 @@
 
 namespace ForkBB\Models\User;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Action;
+use ForkBB\Models\User\Model as User;
 use RuntimeException;
 
-class Save extends MethodModel
+class Save extends Action
 {
     /**
      * Обновляет данные пользователя
      *
+     * @param User $user
+     * 
      * @throws RuntimeException
      * 
      * @return User
      */
-    public function update()
+    public function update(User $user)
     {
-        if ($this->model->id < 1) {
+        if ($user->id < 1) {
             throw new RuntimeException('The model does not have ID');
         }
-        $modified = $this->model->getModified();
+        $modified = $user->getModified();
         if (empty($modified)) {
-            return $this->model;
+            return $user;
         }
-        $values = $this->model->getAttrs();
+        $values = $user->getAttrs();
 
-        if ($this->model->isGuest) {
+        if ($user->isGuest) {
             $fileds = $this->c->dbMap->online;
             $table  = 'online';
             $where  = 'user_id=1 AND ident=?s';
@@ -43,33 +46,35 @@ class Save extends MethodModel
             $set[] = $name . '=?' . $fileds[$name];
         }
         if (empty($set)) {
-            return $this->model;
+            return $user;
         }
-        if ($this->model->isGuest) {
-            $vars[] = $this->model->ip;
+        if ($user->isGuest) {
+            $vars[] = $user->ip;
         } else {
-            $vars[] = $this->model->id;
+            $vars[] = $user->id;
         }
         $this->c->DB->query('UPDATE ::' . $table . ' SET ' . implode(', ', $set) . ' WHERE ' . $where, $vars);
-        $this->model->resModified();
+        $user->resModified();
 
-        return $this->model;
+        return $user;
     }
 
     /**
      * Добавляет новую запись в таблицу пользователей
      *
+     * @param User $user
+     * 
      * @throws RuntimeException
      * 
      * @return int
      */
-    public function insert()
+    public function insert(User $user)
     {
-        $modified = $this->model->getModified();
-        if (null !== $this->model->id || in_array('id', $modified)) {
+        $modified = $user->getModified();
+        if (null !== $user->id || in_array('id', $modified)) {
             throw new RuntimeException('The model has ID');
         }
-        $values = $this->model->getAttrs();
+        $values = $user->getAttrs();
         $fileds = $this->c->dbMap->users;
         $set = $set2 = $vars = [];
         foreach ($modified as $name) {
@@ -84,9 +89,9 @@ class Save extends MethodModel
             throw new RuntimeException('The model is empty');
         }
         $this->c->DB->query('INSERT INTO ::users (' . implode(', ', $set) . ') VALUES (' . implode(', ', $set2) . ')', $vars);
-        $this->model->id = $this->c->DB->lastInsertId();
-        $this->model->resModified();
+        $user->id = $this->c->DB->lastInsertId();
+        $user->resModified();
 
-        return $this->model->id;
+        return $user->id;
     }
 }

+ 16 - 7
app/Models/User/UpdateLastVisit.php

@@ -2,18 +2,27 @@
 
 namespace ForkBB\Models\User;
 
-use ForkBB\Models\MethodModel;
+use ForkBB\Models\Action;
+use ForkBB\Models\User\Model as User;
+use RuntimeException;
 
-class UpdateLastVisit extends MethodModel
+class UpdateLastVisit extends Action
 {
     /**
-     * Обновляет время последнего визита для конкретного пользователя
+     * Обновляет время последнего визита пользователя
+     * 
+     * @param User $user
+     *
+     * @throws RuntimeException
      */
-    public function updateLastVisit()
+    public function updateLastVisit(User $user)
     {
-        if ($this->model->isLogged) {
-            $this->c->DB->exec('UPDATE ::users SET last_visit=?i:loggid WHERE id=?i:id', [':loggid' => $this->model->logged, ':id' => $this->model->id]);
-            $this->model->__last_visit = $this->model->logged;
+        if ($user->id < 2) {
+            throw new RuntimeException('Expected user');
+        }
+        if ($user->isLogged) {
+            $this->c->DB->exec('UPDATE ::users SET last_visit=?i:loggid WHERE id=?i:id', [':loggid' => $user->logged, ':id' => $user->id]);
+            $user->__last_visit = $user->logged;
         }
     }
 }

+ 1 - 1
app/functions.php

@@ -166,7 +166,7 @@ function dt($arg, $dateOnly = false, $dateFormat = null, $timeFormat = null, $ti
  */
 function utc($timestamp)
 {
-    return gmdate('Y-m-d\TH:i:s\Z', $timestamp);
+    return gmdate('c', $timestamp); // Y-m-d\TH:i:s\Z
 }
 
 /**

+ 10 - 1
app/lang/English/common.po

@@ -24,6 +24,15 @@ msgstr "."
 msgid "lang_thousands_sep"
 msgstr ","
 
+msgid "Cancel"
+msgstr "Cancel"
+
+msgid "Cancel redirect"
+msgstr "Operation cancelled."
+
+msgid "No confirm redirect"
+msgstr "No confirmation provided. Operation cancelled."
+
 msgid "Forum rules"
 msgstr "Forum rules"
 
@@ -182,7 +191,7 @@ msgstr[1] "<strong>%s</strong> Replies"
 msgid "Pages"
 msgstr "Pages:"
 
-msgid "Page"
+msgid "Page %s"
 msgstr "(Page %s)"
 
 msgid "BBCode"

+ 55 - 0
app/lang/English/delete.po

@@ -0,0 +1,55 @@
+#
+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 "Delete post"
+msgstr "Delete post"
+
+msgid "Delete topic"
+msgstr "Delete topic"
+
+msgid "Delete  post"
+msgstr "Delete post"
+
+msgid "Delete  topic"
+msgstr "Delete topic"
+
+msgid "Confirm delete post"
+msgstr "Delete post"
+
+msgid "Confirm delete topic"
+msgstr "Delete topic (<b>All posts will be deleted from this topic</b>)"
+
+msgid "Warning"
+msgstr "You are about to permanently delete this post."
+
+msgid "Topic warning"
+msgstr "Warning! This is the first post in the topic, the whole topic will be permanently deleted."
+
+msgid "Delete info"
+msgstr "The post you have chosen to delete is set out below for you to review before proceeding"
+
+msgid "Reply by"
+msgstr "Reply by %1$s<br>Posted %2$s"
+
+msgid "Topic by"
+msgstr "Topic started by %1$s<br>Created %2$s"
+
+msgid "Delete"
+msgstr "Delete"
+
+msgid "Post del redirect"
+msgstr "Post deleted. Redirecting …"
+
+msgid "Topic del redirect"
+msgstr "Topic deleted. Redirecting …"

+ 10 - 1
app/lang/Russian/common.po

@@ -24,6 +24,15 @@ msgstr "."
 msgid "lang_thousands_sep"
 msgstr ","
 
+msgid "Cancel"
+msgstr "Отменить"
+
+msgid "Cancel redirect"
+msgstr "Операция отменена."
+
+msgid "No confirm redirect"
+msgstr "Подтверждение не получено. Операция отменена."
+
 msgid "Forum rules"
 msgstr "Правила форума"
 
@@ -183,7 +192,7 @@ msgstr[2] "<strong>%s</strong> Ответов"
 msgid "Pages"
 msgstr "Страницы"
 
-msgid "Page"
+msgid "Page %s"
 msgstr "(Страница %s)"
 
 msgid "BBCode"

+ 55 - 0
app/lang/Russian/delete.po

@@ -0,0 +1,55 @@
+#
+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 "Delete post"
+msgstr "Удаление сообщения"
+
+msgid "Delete topic"
+msgstr "Удаление темы"
+
+msgid "Delete  post"
+msgstr "Удалить сообщение"
+
+msgid "Delete  topic"
+msgstr "Удалить тему"
+
+msgid "Confirm delete post"
+msgstr "Удалить сообщение"
+
+msgid "Confirm delete topic"
+msgstr "Удалить тему (<b>Все сообщения из этой темы будут удалены</b>)"
+
+msgid "Warning"
+msgstr "Внимание! Вы собираетесь навсегда удалить сообщение."
+
+msgid "Topic warning"
+msgstr "Внимание! Это первое сообщение в теме, вся тема будет безвозвратно удалелена."
+
+msgid "Delete info"
+msgstr "Удаляемое сообщение показано ниже"
+
+msgid "Reply by"
+msgstr "Автор сообщения: %1$s<br>Отправлено: %2$s"
+
+msgid "Topic by"
+msgstr "Автор темы: %1$s<br>Создана: %2$s"
+
+msgid "Delete"
+msgstr "Удалить"
+
+msgid "Post del redirect"
+msgstr "Сообщение удалено. Переадресация &hellip;"
+
+msgid "Topic del redirect"
+msgstr "Тема удалена. Переадресация &hellip;"

+ 26 - 16
app/templates/layouts/form.tpl

@@ -4,36 +4,46 @@
           <input type="hidden" name="{{ $key }}" value="{{ $val }}">
   @endforeach
 @endif
-@foreach ($form['sets'] as $fieldset)
+@foreach ($form['sets'] as $set)
+  @if (isset($set['info']))
+    @foreach ($set['info'] as $key => $cur)
+      @if (empty($cur['html']))
+          <p class="f-finfo">{{ $cur['value'] }}</p>
+      @else
+          <p class="f-finfo">{!! $cur['value'] !!}</p>
+      @endif
+    @endforeach
+  @elseif (isset($set['fields']))
           <fieldset>
-  @if(isset ($fieldset['legend']))
-            <legend>{!! $fieldset['legend'] !!}</legend>
-  @endif
-  @foreach ($fieldset['fields'] as $key => $cur)
+    @if (isset($set['legend']))
+            <legend>{!! $set['legend'] !!}</legend>
+    @endif
+    @foreach ($set['fields'] as $key => $cur)
             <dl @if (isset($cur['dl'])) class="f-field-{{ $cur['dl'] }}" @endif>
               <dt> @if (isset($cur['title']))<label class="f-child1 @if (isset($cur['required'])) f-req @endif" for="id-{{ $key }}">{!! $cur['title'] !!}</label> @endif</dt>
               <dd>
-    @if ($cur['type'] === 'textarea')
+      @if ($cur['type'] === 'textarea')
                 <textarea @if (isset($cur['required'])) required @endif @if (isset($cur['autofocus'])) autofocus @endif class="f-ctrl" id="id-{{ $key }}" name="{{ $key }}">{{ $cur['value'] or '' }}</textarea>
-      @if (isset($cur['bb']))
+        @if (isset($cur['bb']))
                 <ul class="f-child5">
-        @foreach ($cur['bb'] as $val)
+          @foreach ($cur['bb'] as $val)
                   <li><span><a href="{!! $val[0] !!}">{!! $val[1] !!}</a> {!! $val[2] !!}</span></li>
-        @endforeach
+          @endforeach
                 </ul>
-      @endif
-    @elseif ($cur['type'] === 'text')
+        @endif
+      @elseif ($cur['type'] === 'text')
                 <input @if (isset($cur['required'])) required @endif @if (isset($cur['autofocus'])) autofocus @endif class="f-ctrl" id="id-{{ $key }}" name="{{ $key }}" type="text" @if (! empty($cur['maxlength'])) maxlength="{{ $cur['maxlength'] }}" @endif @if (isset($cur['pattern'])) pattern="{{ $cur['pattern'] }}" @endif @if (isset($cur['value'])) value="{{ $cur['value'] }}" @endif>
-    @elseif ($cur['type'] === 'checkbox')
+      @elseif ($cur['type'] === 'checkbox')
                 <label class="f-child2"><input @if (isset($cur['autofocus'])) autofocus @endif type="checkbox" id="id-{{ $key }}" name="{{ $key }}" value="{{ $cur['value'] or '1' }}" @if (! empty($cur['checked'])) checked @endif>{!! $cur['label'] !!}</label>
-    @endif
-    @if (isset($cur['info']))
+      @endif
+      @if (isset($cur['info']))
                 <p class="f-child4">{!! $cur['info'] !!}</p>
-    @endif
+      @endif
               </dd>
             </dl>
-  @endforeach
+    @endforeach
           </fieldset>
+  @endif
 @endforeach
           <p>
 @foreach ($form['btns'] as $key => $cur)

+ 24 - 1
app/templates/post.tpl

@@ -26,10 +26,33 @@
     </section>
 @endif
 @if ($form = $p->form)
-    <section class="post-form">
+    <section class="f-post-form">
       <h2>{!! $p->formTitle !!}</h2>
       <div class="f-fdiv">
   @include ('layouts/form')
       </div>
     </section>
 @endif
+@if ($p->posts) 
+    <section class="f-view-posts">
+      <h2>{!! $p->postsTitle !!}</h2>
+  @foreach ($p->posts as $post)
+      <article id="p{!! $post->id !!}" class="clearfix f-post">
+        <header class="f-post-header clearfix">
+          <span class="f-post-posted"><time datetime="{{ utc($post->posted) }}">{{ dt($post->posted) }}</time></span>
+          <span class="f-post-number"><a href="{!! $post->link !!}" rel="bookmark">#{!! $post->postNumber !!}</a></span>
+        </header>
+        <div class="f-post-body clearfix">
+          <address class="f-post-left clearfix">
+            <ul class="f-user-info">
+              <li class="f-username">{{ $post->poster }}</li>
+            </ul>
+          </address>
+          <div class="f-post-right f-post-main">
+            {!! $post->html() !!}
+          </div>
+        </div>
+      </article>
+  @endforeach
+    </section>
+@endif

+ 2 - 2
app/templates/topic.tpl

@@ -57,7 +57,7 @@
           <h3>{{ cens($p->topic->subject) }} - #{!! $post->postNumber !!}</h3>
           <span class="f-post-posted"><time datetime="{{ utc($post->posted) }}">{{ dt($post->posted) }}</time></span>
   @if ($post->edited)
-          <span class="f-post-edited" title="{!! __('Last edit', $post->user->username, dt($post->edited)) !!}">{!! __('Edited') !!}</span>
+          <span class="f-post-edited" title="{!! __('Last edit', $post->edited_by, dt($post->edited)) !!}">{!! __('Edited') !!}</span>
   @endif
           <span class="f-post-number"><a href="{!! $post->link !!}" rel="bookmark">#{!! $post->postNumber !!}</a></span>
         </header>
@@ -138,7 +138,7 @@
   @include ('layouts/stats')
 @endif
 @if ($form = $p->form)
-    <section class="post-form">
+    <section class="f-post-form">
       <h2>{!! __('Quick post') !!}</h2>
       <div class="f-fdiv">
   @include ('layouts/form')

+ 24 - 10
public/style/ForkBB/style.css

@@ -468,12 +468,20 @@ select {
 }
 
 .f-fdiv .f-finfo {
-  margin: 1rem -0.625rem;
+  margin: -0.625rem -0.625rem 1rem -0.625rem;
   background-color: #AA7939;
   padding: 0.625rem;
   color: #F8F4E3;
 }
 
+.f-finfo + .f-finfo {
+  margin-top: -1rem;
+}
+
+fieldset + .f-finfo {
+  margin-top: 1rem;
+}
+
 .f-ctrl {
   border: 0.0625rem solid #AA7939;
 }
@@ -1386,7 +1394,7 @@ li + li .f-btn {
   border-top: 0.0625rem solid #AA7939;
 }
 
-.f-preview h2 {
+.f-preview > h2 {
   font-family: Arial, Helvetica, sans-serif;
   font-size: 1rem;
   line-height: 1.5;
@@ -1404,24 +1412,30 @@ li + li .f-btn {
 }
 
 /*****************************/
-.post-form {
+.f-post-form {
   border-top: 0.0625rem solid #AA7939;
   margin-bottom: 1rem;
 }
 
-.post-form > h2 {
+.f-view-posts {
+  margin-bottom: 1rem;
+  margin-top: -1rem;
+}
+
+.f-post-form > h2,
+.f-view-posts > h2 {
   font-family: Arial, Helvetica, sans-serif;
   font-size: 1rem;
   padding-right: 0.625rem;
   padding: 1rem 0.625rem 0.3125rem 0.625rem
 }
 
-.post-form > .f-fdiv {
+.f-post-form > .f-fdiv {
 /*  margin: 1rem 0; */
   background-color: #F8F4E3;
 }
 
-.post-form legend {
+.f-post-form legend {
   width: 100%;
   padding-bottom: 0.625rem;
   margin-bottom: 0.625rem;
@@ -1429,19 +1443,19 @@ li + li .f-btn {
   font-weight: bold;
 }
 
-.post-form .f-child1 {
+.f-post-form .f-child1 {
   font-size: 0.875rem;
 }
 
-.post-form .f-child5 > li {
+.f-post-form .f-child5 > li {
   display: inline;
 }
 
-.post-form .f-child5 a {
+.f-post-form .f-child5 a {
   border: 0;
 }
 
-.post-form .f-btn {
+.f-post-form .f-btn {
   width: auto;
   display: inline;
 }