Model.php 11 KB

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