Forum.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. <?php
  2. /**
  3. * This file is part of the ForkBB <https://github.com/forkbb>.
  4. *
  5. * @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
  6. * @license The MIT License (MIT)
  7. */
  8. declare(strict_types=1);
  9. namespace ForkBB\Models\Forum;
  10. use ForkBB\Models\DataModel;
  11. use ForkBB\Models\User\User;
  12. use PDO;
  13. use RuntimeException;
  14. use InvalidArgumentException;
  15. class Forum extends DataModel
  16. {
  17. /**
  18. * Ключ модели для контейнера
  19. * @var string
  20. */
  21. protected $cKey = 'Forum';
  22. /**
  23. * Получение родительского раздела
  24. */
  25. protected function getparent(): ?Forum
  26. {
  27. if (
  28. null === $this->parent_forum_id
  29. && 0 !== $this->id
  30. ) {
  31. throw new RuntimeException('Parent is not defined');
  32. }
  33. return $this->c->forums->get($this->parent_forum_id);
  34. }
  35. /**
  36. * Возвращает название раздела
  37. */
  38. protected function getname(): ?string
  39. {
  40. return $this->forum_name;
  41. }
  42. /**
  43. * Статус возможности создания новой темы
  44. */
  45. protected function getcanCreateTopic(): bool
  46. {
  47. $user = $this->c->user;
  48. return 1 === $this->post_topics
  49. || (
  50. null === $this->post_topics
  51. && 1 === $user->g_post_topics
  52. )
  53. || $user->isAdmin
  54. || $user->isModerator($this);
  55. }
  56. /**
  57. * Статус возможности пометки всех тем прочтенными
  58. */
  59. protected function getcanMarkRead(): bool
  60. {
  61. return ! $this->c->user->isGuest
  62. && ! $this->c->user->isUnverified;
  63. }
  64. /**
  65. * Статус возможности использования подписок
  66. */
  67. protected function getcanSubscription(): bool
  68. {
  69. return 1 === $this->c->config->b_forum_subscriptions
  70. && $this->id > 0
  71. && ! $this->c->user->isGuest
  72. && ! $this->c->user->isUnverified;
  73. }
  74. /**
  75. * Получение массива подразделов
  76. */
  77. protected function getsubforums(): array
  78. {
  79. $sub = [];
  80. $attr = $this->getAttr('subforums');
  81. if (\is_array($attr)) {
  82. foreach ($attr as $id) {
  83. $sub[$id] = $this->c->forums->get($id);
  84. }
  85. }
  86. return $sub;
  87. }
  88. /**
  89. * Получение массива всех дочерних разделов
  90. */
  91. protected function getdescendants(): array
  92. {
  93. $all = [];
  94. $attr = $this->getAttr('descendants');
  95. if (\is_array($attr)) {
  96. foreach ($attr as $id) {
  97. $all[$id] = $this->c->forums->get($id);
  98. }
  99. }
  100. return $all;
  101. }
  102. /**
  103. * Ссылка на раздел
  104. */
  105. protected function getlink(): string
  106. {
  107. if (0 === $this->id) {
  108. return $this->c->Router->link('Index');
  109. } else {
  110. return $this->c->Router->link(
  111. 'Forum',
  112. [
  113. 'id' => $this->id,
  114. 'name' => $this->forum_name,
  115. ]
  116. );
  117. }
  118. }
  119. /**
  120. * Ссылка на поиск новых сообщений
  121. */
  122. protected function getlinkNew(): string
  123. {
  124. if (0 === $this->id) {
  125. return $this->c->Router->link(
  126. 'SearchAction',
  127. [
  128. 'action' => 'new',
  129. ]
  130. );
  131. } else {
  132. return $this->c->Router->link(
  133. 'SearchAction',
  134. [
  135. 'action' => 'new',
  136. 'forum' => $this->id,
  137. ]
  138. );
  139. }
  140. }
  141. /**
  142. * Ссылка на последнее сообщение в разделе
  143. */
  144. protected function getlinkLast(): string
  145. {
  146. if ($this->last_post_id < 1) {
  147. return '';
  148. } else {
  149. return $this->c->Router->link(
  150. 'ViewPost',
  151. [
  152. 'id' => $this->last_post_id,
  153. ]
  154. );
  155. }
  156. }
  157. /**
  158. * Ссылка на создание новой темы
  159. */
  160. protected function getlinkCreateTopic(): string
  161. {
  162. return $this->c->Router->link(
  163. 'NewTopic',
  164. [
  165. 'id' => $this->id,
  166. ]
  167. );
  168. }
  169. /**
  170. * Ссылка на пометку всех тем прочтенными
  171. */
  172. protected function getlinkMarkRead(): string
  173. {
  174. return $this->c->Router->link(
  175. 'MarkRead',
  176. [
  177. 'id' => $this->id,
  178. ]
  179. );
  180. }
  181. /**
  182. * Ссылка на подписку
  183. */
  184. protected function getlinkSubscribe(): string
  185. {
  186. if ($this->id < 1) {
  187. return '';
  188. } else {
  189. return $this->c->Router->link(
  190. 'ForumSubscription',
  191. [
  192. 'fid' => $this->id,
  193. 'type' => 'subscribe',
  194. ]
  195. );
  196. }
  197. }
  198. /**
  199. * Ссылка на отписку
  200. */
  201. protected function getlinkUnsubscribe(): string
  202. {
  203. if ($this->id < 1) {
  204. return '';
  205. } else {
  206. return $this->c->Router->link(
  207. 'ForumSubscription',
  208. [
  209. 'fid' => $this->id,
  210. 'type' => 'unsubscribe',
  211. ]
  212. );
  213. }
  214. }
  215. /**
  216. * Получение массива модераторов
  217. */
  218. protected function getmoderators(): array
  219. {
  220. $attr = $this->getAttr('moderators');
  221. if (
  222. empty($attr)
  223. || ! \is_array($attr)
  224. ) {
  225. return [];
  226. }
  227. $viewUsers = $this->c->user->viewUsers;
  228. foreach ($attr as $id => &$cur) {
  229. $cur = [
  230. 'name' => $cur,
  231. 'link' => $viewUsers ?
  232. $this->c->Router->link(
  233. 'User',
  234. [
  235. 'id' => $id,
  236. 'name' => $cur,
  237. ]
  238. )
  239. : null,
  240. ];
  241. }
  242. unset($cur);
  243. return $attr;
  244. }
  245. /**
  246. * Добавляет указанных пользователей в список модераторов
  247. */
  248. public function modAdd(User ...$users): void
  249. {
  250. $attr = $this->getAttr('moderators');
  251. if (
  252. empty($attr)
  253. || ! \is_array($attr)
  254. ) {
  255. $attr = [];
  256. }
  257. foreach ($users as $user) {
  258. if (! $user instanceof User) {
  259. throw new InvalidArgumentException('Expected User');
  260. }
  261. $attr[$user->id] = $user->username;
  262. }
  263. $this->moderators = $attr;
  264. }
  265. /**
  266. * Удаляет указанных пользователей из списка модераторов
  267. */
  268. public function modDelete(User ...$users): void
  269. {
  270. $attr = $this->getAttr('moderators');
  271. if (
  272. empty($attr)
  273. || ! \is_array($attr)
  274. ) {
  275. return;
  276. }
  277. foreach ($users as $user) {
  278. if (! $user instanceof User) {
  279. throw new InvalidArgumentException('Expected User');
  280. }
  281. unset($attr[$user->id]);
  282. }
  283. $this->moderators = $attr;
  284. }
  285. /**
  286. * Возвращает общую статистику по дереву разделов с корнем в текущем разделе
  287. */
  288. protected function gettree(): Forum
  289. {
  290. $attr = $this->getAttr('tree');
  291. if (empty($attr)) { //????
  292. $numT = (int) $this->num_topics;
  293. $numP = (int) $this->num_posts;
  294. $time = (int) $this->last_post;
  295. $postId = (int) $this->last_post_id;
  296. $poster = $this->last_poster;
  297. $topic = $this->last_topic;
  298. $fnew = $this->newMessages;
  299. foreach ($this->descendants as $chId => $children) {
  300. $fnew = $fnew || $children->newMessages;
  301. $numT += $children->num_topics;
  302. $numP += $children->num_posts;
  303. if ($children->last_post > $time) {
  304. $time = $children->last_post;
  305. $postId = $children->last_post_id;
  306. $poster = $children->last_poster;
  307. $topic = $children->last_topic;
  308. }
  309. }
  310. $attr = $this->c->forums->create([
  311. 'num_topics' => $numT,
  312. 'num_posts' => $numP,
  313. 'last_post' => $time,
  314. 'last_post_id' => $postId,
  315. 'last_poster' => $poster,
  316. 'last_topic' => $topic,
  317. 'newMessages' => $fnew,
  318. ]);
  319. $this->setAttr('tree', $attr);
  320. }
  321. return $attr;
  322. }
  323. /**
  324. * Количество страниц в разделе
  325. */
  326. protected function getnumPages(): int
  327. {
  328. if (! \is_int($this->num_topics)) {
  329. throw new RuntimeException('The model does not have the required data');
  330. }
  331. return (int) \ceil(($this->num_topics ?: 1) / $this->c->user->disp_topics);
  332. }
  333. /**
  334. * Массив страниц раздела
  335. */
  336. protected function getpagination(): array
  337. {
  338. return $this->c->Func->paginate(
  339. $this->numPages,
  340. $this->page,
  341. 'Forum',
  342. [
  343. 'id' => $this->id,
  344. 'name' => $this->forum_name,
  345. ]
  346. );
  347. }
  348. /**
  349. * Статус наличия установленной страницы в разделе
  350. */
  351. public function hasPage(): bool
  352. {
  353. return $this->page > 0 && $this->page <= $this->numPages;
  354. }
  355. /**
  356. * Возвращает массив тем с установленной страницы
  357. */
  358. public function pageData(): array
  359. {
  360. if (! $this->hasPage()) {
  361. throw new InvalidArgumentException('Bad number of displayed page');
  362. }
  363. if (empty($this->num_topics)) {
  364. return [];
  365. }
  366. switch ($this->sort_by) {
  367. case 1:
  368. $sortBy = 't.posted DESC';
  369. break;
  370. case 2:
  371. $sortBy = 't.subject ASC';
  372. break;
  373. default:
  374. $sortBy = 't.last_post DESC';
  375. break;
  376. }
  377. $vars = [
  378. ':fid' => $this->id,
  379. ':offset' => ($this->page - 1) * $this->c->user->disp_topics,
  380. ':rows' => $this->c->user->disp_topics,
  381. ];
  382. $query = "SELECT t.id
  383. FROM ::topics AS t
  384. WHERE t.forum_id=?i:fid
  385. ORDER BY t.sticky DESC, {$sortBy}, t.id DESC
  386. LIMIT ?i:rows OFFSET ?i:offset";
  387. $this->idsList = $this->c->DB->query($query, $vars)->fetchAll(PDO::FETCH_COLUMN);
  388. return empty($this->idsList) ? [] : $this->c->topics->view($this);
  389. }
  390. /**
  391. * Возвращает значения свойств в массиве
  392. */
  393. public function getAttrs(): array
  394. {
  395. $data = parent::getAttrs();
  396. $data['moderators'] = empty($data['moderators']) || ! \is_array($data['moderators'])
  397. ? ''
  398. : \json_encode($data['moderators']);
  399. return $data;
  400. }
  401. }