PageRenderTime 63ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/models/topic.php

https://github.com/mkfmn/cake-forum
PHP | 467 lines | 263 code | 59 blank | 145 comment | 44 complexity | 39ed2616d37c642f245fda9fa3e32bd8 MD5 | raw file
  1. <?php
  2. /**
  3. * Forum - Topic Model
  4. *
  5. * @author Miles Johnson - http://milesj.me
  6. * @copyright Copyright 2006-2010, Miles Johnson, Inc.
  7. * @license http://opensource.org/licenses/mit-license.php - Licensed under The MIT License
  8. * @link http://milesj.me/resources/script/forum-plugin
  9. */
  10. class Topic extends ForumAppModel {
  11. /**
  12. * Type constants.
  13. */
  14. const NORMAL = 0;
  15. const STICKY = 1;
  16. const IMPORTANT = 2;
  17. const ANNOUNCEMENT = 3;
  18. /**
  19. * Behaviors
  20. *
  21. * @access public
  22. * @var array
  23. */
  24. public $actsAs = array(
  25. 'Utils.Sluggable' => array(
  26. 'separator' => '-',
  27. 'update' => true
  28. )
  29. );
  30. /**
  31. * Belongs to.
  32. *
  33. * @access public
  34. * @var array
  35. */
  36. public $belongsTo = array(
  37. 'User',
  38. 'Forum' => array(
  39. 'className' => 'Forum.Forum',
  40. 'counterCache' => true
  41. ),
  42. 'FirstPost' => array(
  43. 'className' => 'Forum.Post',
  44. 'foreignKey' => 'firstPost_id'
  45. ),
  46. 'LastPost' => array(
  47. 'className' => 'Forum.Post',
  48. 'foreignKey' => 'lastPost_id'
  49. ),
  50. 'LastUser' => array(
  51. 'className' => 'User',
  52. 'foreignKey' => 'lastUser_id'
  53. )
  54. );
  55. /**
  56. * Has one.
  57. *
  58. * @access public
  59. * @var array
  60. */
  61. public $hasOne = array(
  62. 'Poll' => array(
  63. 'className' => 'Forum.Poll',
  64. 'dependent' => true
  65. )
  66. );
  67. /**
  68. * Has many.
  69. *
  70. * @access public
  71. * @var array
  72. */
  73. public $hasMany = array(
  74. 'Post' => array(
  75. 'className' => 'Forum.Post',
  76. 'exclusive' => true,
  77. 'dependent' => true,
  78. 'order' => array('Post.created' => 'DESC'),
  79. )
  80. );
  81. /**
  82. * Validation.
  83. *
  84. * @access public
  85. * @var array
  86. */
  87. public $validate = array(
  88. 'title' => 'notEmpty',
  89. 'forum_id' => 'notEmpty',
  90. 'expires' => array(
  91. 'rule' => 'numeric',
  92. 'message' => 'Expiration must be a numerical value for days'
  93. ),
  94. 'options' => array(
  95. 'checkOptions' => array(
  96. 'rule' => array('checkOptions'),
  97. 'message' => 'You must supply a minimum of 2 options and a max of 10'
  98. ),
  99. 'notEmpty' => array(
  100. 'rule' => 'notEmpty',
  101. 'message' => 'Please supply some answer options for your poll'
  102. )
  103. ),
  104. 'content' => 'notEmpty'
  105. );
  106. /**
  107. * Validate and add a topic.
  108. *
  109. * @access public
  110. * @param array $data
  111. * @return boolean|int
  112. */
  113. public function add($data) {
  114. $this->set($data);
  115. if ($this->validates()) {
  116. $isAdmin = $this->Session->read('Forum.isAdmin');
  117. if (($secondsLeft = $this->checkFlooding($this->settings['topic_flood_interval'])) > 0 && !$isAdmin) {
  118. return $this->invalidate('title', 'You must wait %s more second(s) till you can post a topic', $secondsLeft);
  119. } else if ($this->checkHourly($this->settings['topics_per_hour']) && !$isAdmin) {
  120. return $this->invalidate('title', 'You are only allowed to post %s topic(s) per hour', $this->settings['topics_per_hour']);
  121. } else {
  122. $data['title'] = Sanitize::clean($data['title']);
  123. $this->create();
  124. $this->save($data, false, array('forum_id', 'user_id', 'title', 'slug', 'status', 'type'));
  125. $data['topic_id'] = $this->id;
  126. $data['post_id'] = $this->Post->addFirstPost($data);
  127. $this->update($data['topic_id'], array(
  128. 'firstPost_id' => $data['post_id'],
  129. 'lastPost_id' => $data['post_id'],
  130. 'lastUser_id' => $data['user_id'],
  131. ));
  132. $this->Forum->chainUpdate($data['forum_id'], array(
  133. 'lastTopic_id' => $data['topic_id'],
  134. 'lastPost_id' => $data['post_id'],
  135. 'lastUser_id' => $data['user_id'],
  136. ));
  137. if (isset($data['options'])) {
  138. $this->Poll->addPoll($data);
  139. }
  140. return $data['topic_id'];
  141. }
  142. }
  143. return false;
  144. }
  145. /**
  146. * Check the posting flood interval.
  147. *
  148. * @access public
  149. * @param int $interval
  150. * @return boolean|int
  151. */
  152. public function checkFlooding($interval) {
  153. $topics = $this->Session->read('Forum.topics');
  154. if (!empty($topics)) {
  155. $timeLeft = time() - array_pop($topics);
  156. if ($timeLeft <= $interval) {
  157. return $interval - $timeLeft;
  158. }
  159. }
  160. return false;
  161. }
  162. /**
  163. * Check the hourly posting.
  164. *
  165. * @access public
  166. * @param int $max
  167. * @return boolean
  168. */
  169. public function checkHourly($max) {
  170. $topics = $this->Session->read('Forum.topics');
  171. $pastHour = strtotime('-1 hour');
  172. if (!empty($topics)) {
  173. $count = 0;
  174. foreach ($topics as $id => $time) {
  175. if ($time >= $pastHour) {
  176. ++$count;
  177. }
  178. }
  179. if ($count >= $max) {
  180. return true;
  181. }
  182. }
  183. return false;
  184. }
  185. /**
  186. * Check to make sure the poll is valid.
  187. *
  188. * @access public
  189. * @param array $data
  190. * @return boolean
  191. */
  192. public function checkOptions($data) {
  193. $data = array_values($data);
  194. $options = explode("\n", $data[0]);
  195. $clean = array();
  196. if (!empty($options)) {
  197. foreach ($options as $option) {
  198. if ($option !== '') {
  199. $clean[] = $option;
  200. }
  201. }
  202. }
  203. $total = count($clean);
  204. return ($total >= 2 && $total <= 10);
  205. }
  206. /**
  207. * Finds difference in days between dates.
  208. *
  209. * @access public
  210. * @param int $start
  211. * @param int $finish
  212. * @return int
  213. */
  214. public function daysBetween($start, $finish) {
  215. if (!is_int($start)) {
  216. $start = strtotime($start);
  217. }
  218. if (!is_int($finish)) {
  219. $finish = strtotime($finish);
  220. }
  221. $diff = $finish - $start;
  222. $days = $diff / 86400;
  223. return round($days);
  224. }
  225. /**
  226. * Robust method for saving all topic data.
  227. *
  228. * @access public
  229. * @param int $id
  230. * @param array $topic
  231. * @return boolean
  232. */
  233. public function edit($id, $topic) {
  234. if (!empty($topic)) {
  235. foreach ($topic as $model => $data) {
  236. if ($model == 'Topic') {
  237. $this->id = $id;
  238. $this->save($data, false);
  239. } else if ($model == 'FirstPost') {
  240. $this->Post->id = $data['id'];
  241. $this->Post->save($data, false, array('content', 'contentHtml'));
  242. } else if ($model == 'Poll') {
  243. $data['expires'] = !empty($data['expires']) ? date('Y-m-d H:i:s', strtotime('+'. $data['expires'] .' days')) : null;
  244. $this->Poll->id = $data['id'];
  245. $this->Poll->save($data, false, array('expires'));
  246. if (!empty($data['PollOption'])) {
  247. foreach ($data['PollOption'] as $option) {
  248. if ($option['delete']) {
  249. $this->Poll->PollOption->delete($option['id'], true);
  250. } else if ($option['option'] !== '') {
  251. $this->Poll->PollOption->id = $option['id'];
  252. $this->Poll->PollOption->save($option, false, array('option', 'vote_count'));
  253. }
  254. }
  255. }
  256. }
  257. }
  258. }
  259. return true;
  260. }
  261. /**
  262. * Get all info for reading a topic.
  263. *
  264. * @access public
  265. * @param string $slug
  266. * @param int $user_id
  267. * @return array
  268. */
  269. public function get($slug) {
  270. $topic = $this->find('first', array(
  271. 'conditions' => array('Topic.slug' => $slug),
  272. 'contain' => array(
  273. 'FirstPost',
  274. 'Forum' => array('Parent'),
  275. 'Poll' => array('PollOption')
  276. ),
  277. 'cache' => __FUNCTION__ .'-'. $slug
  278. ));
  279. if (!empty($topic['Poll']['id'])) {
  280. $topic['Poll'] = $this->Poll->process($topic['Poll']);
  281. }
  282. return $topic;
  283. }
  284. /**
  285. * Return a topic based on ID.
  286. *
  287. * @acccess public
  288. * @param int $id
  289. * @return array
  290. */
  291. public function getById($id) {
  292. return $this->find('first', array(
  293. 'conditions' => array('Topic.id' => $id)
  294. ));
  295. }
  296. /**
  297. * Get the latest topics.
  298. *
  299. * @access public
  300. * @param int $limit
  301. * @return array
  302. */
  303. public function getLatest($limit = 10) {
  304. return $this->find('all', array(
  305. 'order' => array('Topic.created' => 'DESC'),
  306. 'contain' => array('User', 'LastPost', 'FirstPost'),
  307. 'limit' => $limit,
  308. 'cache' => array(__FUNCTION__ .'-'. $limit, '+5 minutes')
  309. ));
  310. }
  311. /**
  312. * Get the latest topics by a user.
  313. *
  314. * @access public
  315. * @param int $user_id
  316. * @param int $limit
  317. * @return array
  318. */
  319. public function getLatestByUser($user_id, $limit = 10) {
  320. return $this->find('all', array(
  321. 'conditions' => array('Topic.user_id' => $user_id),
  322. 'order' => array('Topic.created' => 'DESC'),
  323. 'contain' => array('LastPost'),
  324. 'limit' => $limit,
  325. ));
  326. }
  327. /**
  328. * Get all high level topics within a forum.
  329. *
  330. * @access public
  331. * @param int $forum_id
  332. * @return array
  333. */
  334. public function getStickiesInForum($forum_id) {
  335. return $this->find('all', array(
  336. 'order' => array('Topic.type' => 'DESC'),
  337. 'conditions' => array(
  338. 'OR' => array(
  339. array('Topic.type' => self::ANNOUNCEMENT),
  340. array(
  341. 'Topic.forum_id' => $forum_id,
  342. 'Topic.type' => array(self::STICKY, self::IMPORTANT)
  343. )
  344. )
  345. ),
  346. 'contain' => array('User', 'LastPost', 'LastUser', 'Poll')
  347. ));
  348. }
  349. /**
  350. * Increase the view count.
  351. *
  352. * @access public
  353. * @param int $id
  354. * @return boolean
  355. */
  356. public function increaseViews($id) {
  357. return $this->query('UPDATE `'. $this->tablePrefix .'topics` AS `Topic` SET `Topic`.`view_count` = `Topic`.`view_count` + 1 WHERE `Topic`.`id` = '. (int) $id);
  358. }
  359. /**
  360. * Move all topics to a new forum.
  361. *
  362. * @access public
  363. * @param int $start_id
  364. * @param int $moved_id
  365. * @return boolean
  366. */
  367. public function moveAll($start_id, $moved_id) {
  368. return $this->updateAll(
  369. array('Topic.forum_id' => $moved_id),
  370. array('Topic.forum_id' => $start_id)
  371. );
  372. }
  373. /**
  374. * After find.
  375. *
  376. * @access public
  377. * @param array $results
  378. * @param boolean $primary
  379. * @return array
  380. */
  381. public function afterFind($results, $primary = false) {
  382. if (!empty($results)) {
  383. $postsPerPage = $this->settings['posts_per_page'];
  384. $autoLock = $this->settings['days_till_autolock'];
  385. if (isset($results[0])) {
  386. foreach ($results as &$result) {
  387. if (isset($result['Topic'])) {
  388. $lock = isset($result['Forum']) ? $result['Forum']['settingAutoLock'] : false;
  389. if (isset($result['LastPost'])) {
  390. $lastTime = $result['LastPost']['created'];
  391. } else if (isset($result['Topic']['modified'])) {
  392. $lastTime = $result['Topic']['modified'];
  393. }
  394. if (isset($result['Topic']['post_count'])) {
  395. $result['Topic']['page_count'] = ($result['Topic']['post_count'] > $postsPerPage) ? ceil($result['Topic']['post_count'] / $postsPerPage) : 1;
  396. }
  397. if ($lock && $lastTime && (strtotime($lastTime) < strtotime('-'. $autoLock .' days'))) {
  398. $result['Topic']['status'] = 1;
  399. }
  400. }
  401. }
  402. } else if (isset($results['post_count'])) {
  403. $results['page_count'] = ($results['post_count'] > $postsPerPage) ? ceil($results['post_count'] / $postsPerPage) : 1;
  404. }
  405. }
  406. return $results;
  407. }
  408. }