PageRenderTime 54ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/app/controllers/posts_controller.php

http://github.com/Datawalke/Coordino
PHP | 632 lines | 445 code | 88 blank | 99 comment | 86 complexity | 87175991118a744e7a61abe43637bf6b MD5 | raw file
  1. <?php
  2. class PostsController extends AppController {
  3. var $name = 'Posts';
  4. var $uses = array('Post', 'User', 'Answer', 'History', 'Setting', 'Tag', 'PostTag', 'Vote', 'Widget');
  5. var $components = array('Auth', 'Session', 'Markdownify', 'Markdown', 'Cookie', 'Email', 'Htmlfilter');
  6. var $helpers = array('Javascript', 'Time', 'Cache', 'Thumbnail', 'Session');
  7. //var $cacheAction = "1 hour";
  8. public function beforeRender() {
  9. $this->getWidgets();
  10. $this->underMaintenance();
  11. }
  12. public function beforeFilter() {
  13. parent::beforeFilter();
  14. $this->Auth->allow('ask', 'view', 'answer', 'display', 'miniSearch', 'maintenance');
  15. $this->isAdmin($this->Auth->user('id'));
  16. $this->Cookie->name = 'user_cookie';
  17. $this->Cookie->time = 604800; // or '1 hour'
  18. $this->Cookie->path = '/';
  19. $this->Cookie->domain = $_SERVER['SERVER_NAME'];
  20. $this->Cookie->key = 'MZca3*f113vZ^%v ';
  21. /**
  22. * If a user leaves the site and the session ends they will be relogged in with their cookie information if available.
  23. */
  24. if($this->Cookie->read('User')) {
  25. $this->Auth->login($this->Cookie->read('User'));
  26. }
  27. }
  28. public function afterFilter() {
  29. $this->Session->delete('errors');
  30. }
  31. public function delete($id) {
  32. if(!$this->isAdmin($this->Auth->user('id'))) {
  33. $this->Session->setFlash(__('You do not have permission to access that..',true), 'error');
  34. $this->redirect('/');
  35. }
  36. $this->Post->delete($id);
  37. $this->Session->setFlash(__('Post deleted',true), 'error');
  38. $this->redirect('/');
  39. }
  40. public function ask() {
  41. $this->set('title_for_layout', __('Ask a question',true));
  42. if(!empty($this->data)) {
  43. $this->__validatePost($this->data, '/questions/ask');
  44. /**
  45. * If the user is not logged in create an account for them.
  46. */
  47. if(!empty($this->data['User'])) {
  48. $user = $this->__userSave(array('User' => $this->data['User']));
  49. $this->Auth->login($user);
  50. }
  51. /**
  52. * Add in required Post data
  53. */
  54. if(!empty($user)) {
  55. $userId = $user['User']['id'];
  56. } else {
  57. $userId = $this->Auth->user('id');
  58. }
  59. $post = $this->__postSave('question', $userId, $this->data);
  60. $this->redirect('/questions/' . $post['public_key'] . '/' . $post['url_title']);
  61. }
  62. }
  63. public function answer($public_key) {
  64. $question = $this->Post->findByPublicKey($public_key);
  65. if(!empty($this->data)) {
  66. $this->__validatePost($this->data, '/questions/' . $question['Post']['public_key'] . '/' . $question['Post']['url_title'] . '#user_answer');
  67. if(!empty($this->data['User'])) {
  68. $user = $this->__userSave(array('User' => $this->data['User']));
  69. $this->Auth->login($user);
  70. }
  71. if(!empty($user)) {
  72. $userId = $user['User']['id'];
  73. $username = $user['User']['username'];
  74. } else {
  75. $userId = $this->Auth->user('id');
  76. $username = $this->Auth->user('username');
  77. }
  78. $flag_limit = $this->Setting->find(
  79. 'first', array(
  80. 'conditions' => array('name' => 'flag_display_limit')
  81. )
  82. );
  83. $post = $this->__postSave('answer', $userId, $this->data, $question['Post']['id']);
  84. if(($question['Post']['notify'] == 1) && ($post['flags'] < $flag_limit['Setting']['value'])) {
  85. $user = $this->User->find(
  86. 'first', array(
  87. 'conditions' => array(
  88. 'User.id' => $question['Post']['user_id']
  89. ),
  90. 'fields' => array('User.email', 'User.username')
  91. )
  92. );
  93. $this->set('question', $question);
  94. $this->set('username', $username);
  95. $this->set('dear', $user['User']['username']);
  96. $this->set('answer', $this->data['Post']['content']);
  97. $this->Email->from = 'Answerman <answers@' . $_SERVER['SERVER_NAME'] . '>';
  98. $this->Email->to = $user['User']['email'];
  99. $this->Email->subject = __('Your question has been answered!',true);
  100. $this->Email->template = 'notification';
  101. $this->Email->sendAs = 'both';
  102. $this->Email->send();
  103. }
  104. $this->redirect('/questions/' . $question['Post']['public_key'] . '/' . $question['Post']['url_title']);
  105. }
  106. }
  107. /**
  108. * Validates the Post data.
  109. * Since Posts need to validate for both logged in and non logged in accounts a separate validation technique was needed.
  110. *
  111. * @param string $data
  112. * @param string $redirectUrl
  113. * @return void
  114. */
  115. public function __validatePost($data, $redirectUrl) {
  116. $this->Post->set($data);
  117. $this->User->set($data);
  118. $errors = array();
  119. if(!$this->Post->validates() || !$this->User->validates()) {
  120. $data['Post']['content'] = $this->Markdownify->parseString($data['Post']['content']);
  121. $validationErrors = array_merge($this->Post->invalidFields(), $this->User->invalidFields());
  122. $errors = array(
  123. 'errors' => $validationErrors,
  124. 'data' => $data
  125. );
  126. $this->Session->write(array('errors' => $errors));
  127. $this->redirect($redirectUrl);
  128. }
  129. }
  130. /**
  131. * Saves the Post data for a user
  132. *
  133. * @param string $type Either question or answer
  134. * @param string $userId the ID of the user posting
  135. * @param string $data $this->data
  136. * @return array $post The saved Post data.
  137. */
  138. public function __postSave($type, $userId, $data, $relatedId = null) {
  139. /**
  140. * Add in required Post data
  141. */
  142. $this->data['Post']['type'] = $type;
  143. $this->data['Post']['user_id'] = $userId;
  144. $this->data['Post']['timestamp'] = time();
  145. if($type == 'question') {
  146. $this->data['Post']['url_title'] = $this->Post->niceUrl($this->data['Post']['title']);
  147. }
  148. if($type == 'answer') {
  149. $this->data['Post']['related_id'] = $relatedId;
  150. }
  151. $this->data['Post']['public_key'] = uniqid();
  152. if(!empty($this->data['Post']['tags'])) {
  153. $this->Post->Behaviors->attach('Tag', array('table_label' => 'tags', 'tags_label' => 'tag', 'separator' => ', '));
  154. }
  155. /**
  156. * Filter out any nasty XSS
  157. */
  158. Configure::write('debug', 0);
  159. $this->data['Post']['content'] = str_replace('<code>', '<code class="prettyprint">', $this->data['Post']['content']);
  160. $this->data['Post']['content'] = @$this->Htmlfilter->filter($this->data['Post']['content']);
  161. /**
  162. * Spam Protection
  163. */
  164. $flags = 0;
  165. $content = strip_tags($this->data['Post']['content']);
  166. // Get links in the content
  167. $links = preg_match_all("#(^|[\n ])(?:(?:http|ftp|irc)s?:\/\/|www.)(?:[-A-Za-z0-9]+\.)+[A-Za-z]{2,4}(?:[-a-zA-Z0-9._\/&=+%?;\#]+)#is", $content, $matches);
  168. $links = $matches[0];
  169. $totalLinks = count($links);
  170. $length = strlen($content);
  171. // How many links are in the body
  172. // +2 if less than 2, -1 per link if over 2
  173. if ($totalLinks > 2) {
  174. $flags = $flags + $totalLinks;
  175. } else {
  176. $flags = $flags - 1;
  177. }
  178. // How long is the body
  179. // +2 if more then 20 chars and no links, -1 if less then 20
  180. if ($length >= 20 && $totalLinks <= 0) {
  181. $flags = $flags - 1;
  182. } else if ($length >= 20 && $totalLinks == 1) {
  183. ++$flags;
  184. } else if ($length < 20) {
  185. $flags = $flags + 2;
  186. }
  187. // Keyword search
  188. $blacklistKeywords = $this->Setting->find('first', array('conditions' => array('name' => 'blacklist')));
  189. $blacklistKeywords = unserialize($blacklistKeywords['Setting']['description']);
  190. foreach ($blacklistKeywords as $keyword) {
  191. if (stripos($content, $keyword) !== false) {
  192. $flags = $flags + substr_count(strtolower($content), $keyword);
  193. }
  194. }
  195. $blacklistWords = array('.html', '.info', '?', '&', '.de', '.pl', '.cn');
  196. foreach ($links as $link) {
  197. foreach ($blacklistWords as $word) {
  198. if (stripos($link, $word) !== false) {
  199. ++$flags;
  200. }
  201. }
  202. foreach ($blacklistKeywords as $keyword) {
  203. if (stripos($link, $keyword) !== false) {
  204. ++$flags;
  205. }
  206. }
  207. if (strlen($link) >= 30) {
  208. ++$flags;
  209. }
  210. }
  211. // Body starts with...
  212. // -10 flags
  213. $firstWord = substr($content, 0, stripos($content, ' '));
  214. $firstDisallow = array_merge($blacklistKeywords, array('interesting', 'cool', 'sorry'));
  215. if (in_array(strtolower($firstWord), $firstDisallow)) {
  216. $flags = $flags + 10;
  217. }
  218. $manyTimes = $this->Post->find('count', array(
  219. 'conditions' => array('Post.content' => $this->data['Post']['content'])
  220. ));
  221. // Random character match
  222. // -1 point per 5 consecutive consonants
  223. $consonants = preg_match_all('/[^aAeEiIoOuU\s]{5,}+/i', $content, $matches);
  224. $totalConsonants = count($matches[0]);
  225. if ($totalConsonants > 0) {
  226. $flags = $flags + $totalConsonants;
  227. }
  228. $flags = $flags + $manyTimes;
  229. $this->data['Post']['flags'] = $flags;
  230. if($flags >= $this->Setting->getValue('flag_display_limit')) {
  231. $this->data['Post']['tags'] = '';
  232. }
  233. /**
  234. * Save the Data
  235. */
  236. if($this->Post->save($this->data)) {
  237. if($type == 'question') {
  238. $this->History->record('asked', $this->Post->id, $this->Auth->user('id'));
  239. }elseif($type == 'answer') {
  240. $this->History->record('answered', $this->Post->id, $this->Auth->user('id'));
  241. }
  242. $user_info = $this->User->find('first', array('conditions' => array('id' => $userId)));
  243. if($type == 'question') {
  244. $this->User->save(array('id' => $userId, 'question_count' => $user_info['User']['question_count'] + 1));
  245. }elseif($type == 'answer') {
  246. $this->User->save(array('id' => $userId, 'answer_count' => $user_info['User']['answer_count'] + 1));
  247. }
  248. $post = $this->data['Post'];
  249. /**
  250. * Hack to normalize data.
  251. * Note this should be added to the Tag Behavior at some point.
  252. * This was but in because the behavior would delete the relations as soon as they put them in.
  253. */
  254. $this->Post->Behaviors->detach('Tag');
  255. $tags = array(
  256. 'id' => $this->Post->id,
  257. 'tags' => ''
  258. );
  259. $this->Post->save($tags);
  260. return $post;
  261. } else {
  262. return false;
  263. }
  264. }
  265. /**
  266. * Saves the user data and creates a new user account for them.
  267. *
  268. * @param string $data
  269. * @return void
  270. * @todo this should be moved to the model at some point
  271. */
  272. public function __userSave($data) {
  273. $data['User']['public_key'] = uniqid();
  274. $data['User']['password'] = $this->Auth->password(uniqid('p'));
  275. $data['User']['joined'] = time();
  276. $data['User']['url_title'] = $this->Post->niceUrl($data['User']['username']);
  277. /**
  278. * Set up cookie data incase they leave the site and the session ends and they have not registered yet
  279. */
  280. $this->Cookie->write(array('User' => $data['User']));
  281. /**
  282. * Save the data
  283. */
  284. $this->User->save($data);
  285. $data['User']['id'] = $this->User->id;
  286. return $data;
  287. }
  288. /**
  289. * Allowes the user to view a question.
  290. * If a user tries to view a Post that is a answer type they will be redirected.
  291. *
  292. * @param string $public_key
  293. * @return void
  294. */
  295. public function view($public_key) {
  296. /**
  297. * Set the Post model to recursive 2 so we can pull back the User's information with each comment.
  298. */
  299. $this->Post->recursive = 2;
  300. /**
  301. * Change to contains. Limit to just question and comment data, no answers.
  302. */
  303. $this->Post->unbindModel(
  304. array('hasMany' => array('Answer'))
  305. );
  306. $question = $this->Post->findByPublicKey($public_key);
  307. /*
  308. * Look up the flag limit.
  309. */
  310. $flag_check = $this->Setting->find(
  311. 'first', array(
  312. 'conditions' => array(
  313. 'name' => 'flag_display_limit'
  314. ),
  315. 'fields' => array('value'),
  316. 'recursive' => -1
  317. )
  318. );
  319. /**
  320. * Check to see if the post is spam or not.
  321. * If so redirect.
  322. */
  323. if($question['Post']['flags'] >= $flag_check['Setting']['value'] && $this->Setting->repCheck($this->Auth->user('id'), 'rep_edit')) {
  324. $this->Session->setFlash(__('The question you are trying to view no longer exists.',true), 'error');
  325. $this->redirect('/');
  326. }
  327. /**
  328. * Even though Post can return an array of answers through associations
  329. * we cannot order or sort this data as we need to
  330. */
  331. $this->Answer->recursive = 3;
  332. $answers = $this->Answer->find('all', array(
  333. 'conditions' => array(
  334. 'Answer.related_id' => $question['Post']['id'],
  335. 'Answer.flags <' => $flag_check['Setting']['value']
  336. ),
  337. 'order' => 'Answer.status DESC'
  338. ));
  339. if(!empty($question)) {
  340. $views = array(
  341. 'id' => $question['Post']['id'],
  342. 'views' => $question['Post']['views'] + 1
  343. );
  344. $this->Post->save($views);
  345. }
  346. if($this->Auth->user('id') && !$this->Setting->repCheck($this->Auth->user('id'), 'rep_edit')) {
  347. $this->set('rep_rights', 'yeah!');
  348. }
  349. $this->set('title_for_layout', $question['Post']['title']);
  350. $this->set('question', $question);
  351. $this->set('answers', $answers);
  352. }
  353. public function edit($public_key) {
  354. $question = $this->Post->findByPublicKey($public_key);
  355. $this->set('title_for_layout', $question['Post']['title']);
  356. $redirect = $question;
  357. if(empty($redirect['Post']['title'])) {
  358. $redirect = $this->Post->findById($redirect['Post']['related_id']);
  359. }
  360. if($question['Post']['user_id'] != $this->Auth->user('id') && !$this->isAdmin($this->Auth->user('id')) && !$this->Setting->repCheck($this->Auth->user('id'), 'rep_edit')) {
  361. $this->Session->setFlash(__('That is not your question to edit, and you need more reputation!',true), 'error');
  362. $this->redirect('/questions/' . $redirect['Post']['public_key'] . '/' . $redirect['Post']['url_title']);
  363. }
  364. if(!empty($question['Post']['title'])) {
  365. $tags = $this->PostTag->find(
  366. 'all', array(
  367. 'conditions' => array(
  368. 'PostTag.post_id' => $question['Post']['id']
  369. )
  370. )
  371. );
  372. $this->Tag->recursive = -1;
  373. foreach($tags as $key => $value) {
  374. $tag_names[$key] = $this->Tag->find(
  375. 'first', array(
  376. 'conditions' => array(
  377. 'Tag.id' => $tags[$key]['PostTag']['tag_id']
  378. ),
  379. 'fields' => array('Tag.tag')
  380. )
  381. );
  382. if($key == 0) {
  383. $tag_list = $tag_names[$key]['Tag']['tag'];
  384. }else {
  385. $tag_list = $tag_list . ', ' . $tag_names[$key]['Tag']['tag'];
  386. }
  387. }
  388. $this->set('tags', $tag_list);
  389. }
  390. if(!empty($this->data['Post']['tags'])) {
  391. $this->Post->Behaviors->attach('Tag', array('table_label' => 'tags', 'tags_label' => 'tag', 'separator' => ', '));
  392. }
  393. if(!empty($this->data)) {
  394. $this->data['Post']['id'] = $question['Post']['id'];
  395. if(!empty($this->data['Post']['title'])) {
  396. $this->data['Post']['url_title'] = $this->Post->niceUrl($this->data['Post']['title']);
  397. }
  398. $this->data['Post']['last_edited_timestamp'] = time();
  399. if($this->Post->save($this->data)) {
  400. $this->History->record('edited', $this->Post->id, $this->Auth->user('id'));
  401. $this->redirect('/questions/' . $redirect['Post']['public_key'] . '/' . $redirect['Post']['url_title']);
  402. }
  403. } else {
  404. $question['Post']['content'] = $this->Markdownify->parseString($question['Post']['content']);
  405. $this->set('question', $question);
  406. }
  407. }
  408. public function display($type='recent', $page=1) {
  409. $this->set('title_for_layout', ucwords($type) . ' Questions');
  410. $this->Post->recursive = -1;
  411. if(isset($this->passedArgs['type'])) {
  412. $search = $this->passedArgs['search'];
  413. if($search == 'yes') {
  414. $type = array(
  415. 'needle' => $this->passedArgs['type']
  416. );
  417. }else {
  418. $type = $this->passedArgs['type'];
  419. }
  420. $page = $this->passedArgs['page'];
  421. }elseif(!empty($this->data['Post'])) {
  422. $type = $this->data['Post'];
  423. $search = 'yes';
  424. }else {
  425. $search = 'no';
  426. }
  427. if($page <= 1) {
  428. $page = 1;
  429. }else{
  430. $previous = $page - 1;
  431. $this->set('previous', $previous);
  432. }
  433. $questions = $this->Post->monsterSearch($type, $page, $search);
  434. $count = $this->Post->monsterSearchCount($type, $search);
  435. if($count['0']['0']['count'] % 15 == 0) {
  436. $end_page = $count['0']['0']['count'] / 15;
  437. }else {
  438. $end_page = floor($count['0']['0']['count'] / 15) + 1;
  439. }
  440. if(($count['0']['0']['count'] - ($page * 15)) > 0) {
  441. $next = $page + 1;
  442. $this->set('next', $next);
  443. }
  444. $keywords = array('hot', 'week', 'month', 'recent', 'solved', 'unanswered');
  445. if(($search == 'no') && (!in_array($type, $keywords))) {
  446. $this->Session->setFlash(__('Invalid search type.',true), 'error');
  447. $this->redirect('/');
  448. }
  449. if(empty($questions)) {
  450. if(isset($type['needle'])) {
  451. $this->Session->setFlash(__('No results for',true) . ' "' . $type['needle'] . '"!', 'error');
  452. }else {
  453. $this->Session->setFlash(__('No results for',true) . ' "' . $type . '"!', 'error');
  454. }
  455. if($this->Post->find('count') > 0) {
  456. $this->redirect('/');
  457. }
  458. }
  459. if($search == 'yes') {
  460. $this->set('type', $type['needle']);
  461. }else {
  462. $this->set('type', $type);
  463. }
  464. $this->set('questions', $questions);
  465. $this->set('end_page', $end_page);
  466. $this->set('current', $page);
  467. $this->set('search', $search);
  468. }
  469. public function miniSearch() {
  470. Configure::write('debug', 0);
  471. $this->autoLayout = false;
  472. $questions = $this->Post->monsterSearch(array('needle' => $_GET['query']), 1, 'yes');
  473. $this->set('questions', $questions);
  474. }
  475. public function markCorrect($public_key) {
  476. $answer = $this->Post->findByPublicKey($public_key);
  477. /**
  478. * Check to make sure the Post is an answer
  479. */
  480. if($answer['Post']['type'] != 'answer') {
  481. $this->Session->setFlash(__('What are you trying to do?',true), 'error');
  482. $this->redirect('/');
  483. }
  484. $question = $this->Post->findById($answer['Post']['related_id']);
  485. /**
  486. * Check to make sure the logged in user is authorized to edit this Post
  487. */
  488. if($question['Post']['user_id'] != $this->Auth->user('id')) {
  489. $this->Session->setFlash(__('You are not allowed to edit that.',true));
  490. $this->redirect('/questions/' . $question['Post']['public_key'] . '/' . $question['Post']['url_title']);
  491. }
  492. $rep = $this->User->find(
  493. 'first', array(
  494. 'conditions' => array(
  495. 'User.id' => $answer['Post']['user_id']
  496. ),
  497. 'fields' => array('User.reputation', 'User.id')
  498. )
  499. );
  500. /**
  501. * Set the Post as correct, and its question as closed.
  502. */
  503. $quest = array(
  504. 'id' => $question['Post']['id'],
  505. 'status' => 'closed'
  506. );
  507. $answ = array(
  508. 'id' => $answer['Post']['id'],
  509. 'status' => 'correct'
  510. );
  511. $user = array(
  512. 'User' => array(
  513. 'id' => $rep['User']['id'],
  514. 'reputation' => $rep['User']['reputation'] + 15
  515. )
  516. );
  517. $this->Post->save($answ);
  518. $this->Post->save($quest);
  519. $this->User->save($user);
  520. $this->redirect('/questions/' . $question['Post']['public_key'] . '/' . $question['Post']['url_title'] . '#a_' . $answer['Post']['public_key']);
  521. }
  522. public function flag($public_key) {
  523. $redirect = $this->Post->correctRedirect($public_key);
  524. if(!$this->Auth->user('id')) {
  525. $this->Session->setFlash(__('You need to be logged in to do that!',true), 'error');
  526. $this->redirect('/questions/' . $redirect['Post']['public_key'] . '/' . $redirect['Post']['url_title']);
  527. }elseif(!$this->Setting->repCheck($this->Auth->user('id'), 'rep_flag')) {
  528. $this->Session->setFlash(__('You need more reputation to do that.',true), 'error');
  529. $this->redirect('/questions/' . $redirect['Post']['public_key'] . '/' . $redirect['Post']['url_title']);
  530. }else{
  531. $flag = $this->Vote->throwFlag($this->Auth->user('id'), $public_key);
  532. if($flag == 'exists') {
  533. $this->Session->setFlash(__('You have already flagged that.',true), 'error');
  534. $this->redirect('/questions/' . $redirect['Post']['public_key'] . '/' . $redirect['Post']['url_title']);
  535. }elseif($flag == 'success') {
  536. $this->Session->setFlash(__('Post flagged.',true), 'error');
  537. $this->redirect('/questions/' . $redirect['Post']['public_key'] . '/' . $redirect['Post']['url_title']);
  538. }
  539. }
  540. }
  541. public function maintenance() {
  542. }
  543. }
  544. ?>