Topic.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. <?php
  2. namespace ForkBB\Models;
  3. use ForkBB\Models\DataModel;
  4. use ForkBB\Core\Container;
  5. use RuntimeException;
  6. class Topic extends DataModel
  7. {
  8. /**
  9. * Получение родительского раздела
  10. *
  11. * @throws RuntimeException
  12. *
  13. * @return Models\Forum
  14. */
  15. protected function getparent()
  16. {
  17. if ($this->forum_id < 1) {
  18. throw new RuntimeException('Parent is not defined');
  19. }
  20. return $this->c->forums->forum($this->forum_id);
  21. }
  22. /**
  23. * Статус возможности ответа в теме
  24. *
  25. * @return bool
  26. */
  27. protected function getcanReply()
  28. {
  29. if ($this->c->user->isAdmin) {
  30. return true;
  31. } elseif ($this->closed || $this->c->user->isBot) {
  32. return false;
  33. } elseif ($this->parent->post_replies == '1'
  34. || (null === $this->parent->post_replies && $this->c->user->g_post_replies == '1')
  35. || $this->c->user->isModerator($this)
  36. ) {
  37. return true;
  38. } else {
  39. return false;
  40. }
  41. }
  42. /**
  43. * Ссылка на тему
  44. *
  45. * @return string
  46. */
  47. protected function getlink()
  48. {
  49. return $this->c->Router->link('Topic', ['id' => $this->moved_to ?: $this->id, 'name' => \ForkBB\cens($this->subject)]);
  50. }
  51. /**
  52. * Ссылка для ответа в теме
  53. *
  54. * @return string
  55. */
  56. protected function getlinkReply()
  57. {
  58. return $this->c->Router->link('NewReply', ['id' => $this->id]);
  59. }
  60. /**
  61. * Ссылка для перехода на последнее сообщение темы
  62. *
  63. * @return null|string
  64. */
  65. protected function getlinkLast()
  66. {
  67. if ($this->moved_to) {
  68. return null;
  69. } else {
  70. return $this->c->Router->link('ViewPost', ['id' => $this->last_post_id]);
  71. }
  72. }
  73. /**
  74. * Ссылка для перехода на первое новое сообщение в теме
  75. *
  76. * @return string
  77. */
  78. protected function getlinkNew()
  79. {
  80. return $this->c->Router->link('TopicViewNew', ['id' => $this->id]);
  81. }
  82. /**
  83. * Ссылка для перехода на первое не прочитанное сообщение в теме
  84. */
  85. protected function getlinkUnread()
  86. {
  87. return $this->c->Router->link('TopicViewUnread', ['id' => $this->id]);
  88. }
  89. /**
  90. * Статус наличия новых сообщений в теме
  91. *
  92. * @return false|int
  93. */
  94. protected function gethasNew()
  95. {
  96. if ($this->c->user->isGuest || $this->moved_to) {
  97. return false;
  98. }
  99. $time = max(
  100. (int) $this->c->user->u_mark_all_read,
  101. (int) $this->parent->mf_mark_all_read,
  102. (int) $this->c->user->last_visit,
  103. (int) $this->mt_last_visit
  104. );
  105. return $this->last_post > $time ? $time : false;
  106. }
  107. /**
  108. * Статус наличия не прочитанных сообщений в теме
  109. *
  110. * @return false|int
  111. */
  112. protected function gethasUnread()
  113. {
  114. if ($this->c->user->isGuest || $this->moved_to) {
  115. return false;
  116. }
  117. $time = max(
  118. (int) $this->c->user->u_mark_all_read,
  119. (int) $this->parent->mf_mark_all_read,
  120. (int) $this->mt_last_read
  121. );
  122. return $this->last_post > $time ? $time : false;
  123. }
  124. /**
  125. * Номер первого нового сообщения в теме
  126. *
  127. * @return int
  128. */
  129. protected function getfirstNew()
  130. {
  131. if (false === $this->hasNew) {
  132. return 0;
  133. }
  134. $vars = [
  135. ':tid' => $this->id,
  136. ':visit' => $this->hasNew,
  137. ];
  138. $sql = 'SELECT MIN(id) FROM ::posts WHERE topic_id=?i:tid AND posted>?i:visit';
  139. $pid = $this->c->DB->query($sql, $vars)->fetchColumn();
  140. return $pid ?: 0;
  141. }
  142. /**
  143. * Номер первого не прочитанного сообщения в теме
  144. *
  145. * @return int
  146. */
  147. protected function getfirstUnread()
  148. {
  149. if (false === $this->hasUnread) {
  150. return 0;
  151. }
  152. $vars = [
  153. ':tid' => $this->id,
  154. ':visit' => $this->hasUnread,
  155. ];
  156. $sql = 'SELECT MIN(id) FROM ::posts WHERE topic_id=?i:tid AND posted>?i:visit';
  157. $pid = $this->c->DB->query($sql, $vars)->fetchColumn();
  158. return $pid ?: 0;
  159. }
  160. /**
  161. * Количество страниц в теме
  162. *
  163. * @throws RuntimeException
  164. *
  165. * @return int
  166. */
  167. protected function getnumPages()
  168. {
  169. if (null === $this->num_replies) {
  170. throw new RuntimeException('The model does not have the required data');
  171. }
  172. return (int) ceil(($this->num_replies + 1) / $this->c->user->disp_posts);
  173. }
  174. /**
  175. * Массив страниц темы
  176. *
  177. * @return array
  178. */
  179. protected function getpagination()
  180. {
  181. $page = (int) $this->page;
  182. if ($page < 1 && $this->numPages === 1) {
  183. // 1 страницу в списке тем раздела не отображаем
  184. return [];
  185. } else { //????
  186. return $this->c->Func->paginate($this->numPages, $page, 'Topic', ['id' => $this->id, 'name' => \ForkBB\cens($this->subject)]);
  187. }
  188. }
  189. /**
  190. * Статус наличия установленной страницы в теме
  191. *
  192. * @return bool
  193. */
  194. public function hasPage()
  195. {
  196. return $this->page > 0 && $this->page <= $this->numPages;
  197. }
  198. /**
  199. * Вычисляет страницу темы на которой находится данное сообщение
  200. *
  201. * @param int $pid
  202. */
  203. public function calcPage($pid)
  204. {
  205. $vars = [
  206. ':tid' => $this->id,
  207. ':pid' => $pid,
  208. ];
  209. $sql = 'SELECT COUNT(p.id) AS num, j.id AS flag
  210. FROM ::posts AS p
  211. INNER JOIN ::posts AS j ON (j.topic_id=?i:tid AND j.id=?i:pid)
  212. WHERE p.topic_id=?i:tid AND p.id<?i:pid';
  213. $result = $this->c->DB->query($sql, $vars)->fetch();
  214. $this->page = empty($result['flag']) ? null : (int) ceil(($result['num'] + 1) / $this->c->user->disp_posts);
  215. }
  216. /**
  217. * Возвращает массив сообщений с установленной рание страницы темы
  218. *
  219. * @throws InvalidArgumentException
  220. *
  221. * @return array
  222. */
  223. public function posts()
  224. {
  225. if (! $this->hasPage()) {
  226. throw new InvalidArgumentException('Bad number of displayed page');
  227. }
  228. $offset = ($this->page - 1) * $this->c->user->disp_posts;
  229. $vars = [
  230. ':tid' => $this->id,
  231. ':offset' => $offset,
  232. ':rows' => $this->c->user->disp_posts,
  233. ];
  234. $sql = 'SELECT id
  235. FROM ::posts
  236. WHERE topic_id=?i:tid
  237. ORDER BY id LIMIT ?i:offset, ?i:rows';
  238. $ids = $this->c->DB->query($sql, $vars)->fetchAll(\PDO::FETCH_COLUMN);
  239. if (empty($ids)) {
  240. return [];
  241. }
  242. // приклейка первого сообщения темы
  243. if ($this->stick_fp || $this->poll_type) {
  244. $ids[] = $this->first_post_id;
  245. }
  246. $vars = [
  247. ':ids' => $ids,
  248. ];
  249. $sql = 'SELECT id, message, poster, posted
  250. FROM ::warnings
  251. WHERE id IN (?ai:ids)';
  252. $warnings = $this->c->DB->query($sql, $vars)->fetchAll(\PDO::FETCH_GROUP);
  253. $vars = [
  254. ':ids' => $ids,
  255. ];
  256. $sql = 'SELECT u.warning_all, u.gender, u.email, u.title, u.url, u.location, u.signature,
  257. u.email_setting, u.num_posts, u.registered, u.admin_note, u.messages_enable,
  258. u.group_id,
  259. p.id, p.poster as username, p.poster_id, p.poster_ip, p.poster_email, p.message,
  260. p.hide_smilies, p.posted, p.edited, p.edited_by, p.edit_post, p.user_agent,
  261. g.g_user_title, g.g_promote_next_group, g.g_pm
  262. FROM ::posts AS p
  263. INNER JOIN ::users AS u ON u.id=p.poster_id
  264. INNER JOIN ::groups AS g ON g.g_id=u.group_id
  265. WHERE p.id IN (?ai:ids) ORDER BY p.id';
  266. $posts = $this->c->DB->query($sql, $vars)->fetchAll();
  267. $postCount = 0;
  268. $timeMax = 0;
  269. foreach ($posts as &$cur) {
  270. if ($cur['posted'] > $timeMax) {
  271. $timeMax = $cur['posted'];
  272. }
  273. // номер сообшения в теме
  274. if ($cur['id'] == $this->first_post_id && $offset > 0) {
  275. $cur['postNumber'] = 1;
  276. } else {
  277. ++$postCount;
  278. $cur['postNumber'] = $offset + $postCount;
  279. }
  280. if (isset($warnings[$cur['id']])) {
  281. $cur['warnings'] = $warnings[$cur['id']];
  282. }
  283. $cur['parent'] = $this;
  284. $cur = $this->c->ModelPost->setAttrs($cur);
  285. }
  286. unset($cur);
  287. $this->timeMax = $timeMax;
  288. return $posts;
  289. }
  290. /**
  291. * Статус показа/подсчета просмотров темы
  292. *
  293. * @return bool
  294. */
  295. protected function getshowViews()
  296. {
  297. return $this->c->config->o_topic_views == '1';
  298. }
  299. /**
  300. * Увеличивает на 1 количество просмотров темы
  301. */
  302. public function incViews()
  303. {
  304. $vars = [
  305. ':tid' => $this->id,
  306. ];
  307. $sql = 'UPDATE ::topics SET num_views=num_views+1 WHERE id=?i:tid';
  308. $this->c->DB->query($sql, $vars);
  309. }
  310. /**
  311. * Обновление меток последнего визита и последнего прочитанного сообщения
  312. */
  313. public function updateVisits()
  314. {
  315. if ($this->c->user->isGuest) {
  316. return;
  317. }
  318. $vars = [
  319. ':uid' => $this->c->user->id,
  320. ':tid' => $this->id,
  321. ':read' => $this->mt_last_read,
  322. ':visit' => $this->mt_last_visit,
  323. ];
  324. $flag = false;
  325. if (false !== $this->hasNew) {
  326. $flag = true;
  327. $vars[':visit'] = $this->last_post;
  328. }
  329. if (false !== $this->hasUnread && $this->timeMax > $this->hasUnread) {
  330. $flag = true;
  331. $vars[':read'] = $this->timeMax;
  332. }
  333. if ($flag) {
  334. if (empty($this->mt_last_read) && empty($this->mt_last_visit)) {
  335. $sql = 'INSERT INTO ::mark_of_topic (uid, tid, mt_last_visit, mt_last_read)
  336. SELECT ?i:uid, ?i:tid, ?i:visit, ?i:read
  337. FROM ::groups
  338. WHERE NOT EXISTS (SELECT 1
  339. FROM ::mark_of_topic
  340. WHERE uid=?i:uid AND tid=?i:tid)
  341. LIMIT 1';
  342. } else {
  343. $sql = 'UPDATE ::mark_of_topic
  344. SET mt_last_visit=?i:visit, mt_last_read=?i:read
  345. WHERE uid=?i:uid AND tid=?i:tid';
  346. }
  347. $this->c->DB->exec($sql, $vars);
  348. }
  349. }
  350. }