1 <?php 2 require_once __DIR__ . '/../includes/bootstrap.php'; 3 if (!$USER) json_out(['error'=>'Not logged in'],401); 4 if ($_SERVER['REQUEST_METHOD']!=='POST') json_out(['error'=>'Method not allowed'],405); 5 if (!csrf_ok()) json_out(['error'=>'Invalid CSRF token'],403); 6 if ($USER['silenced']) json_out(['error'=>'You are silenced and cannot post'],403); 7 8 // ── Rate limit check ───────────────────────────────────────── 9 $rl = rate_check((int)$USER['id'], 'post'); 10 if (!$rl['ok']) { 11 $wait = (int)$rl['wait']; 12 json_out(['error'=>"You're posting too fast. Please wait {$wait} second".($wait!==1?'s':'').' before posting again.','rate_limited'=>true,'wait'=>$wait], 429); 13 } 14 15 // ── Post captcha check ─────────────────────────────────────── 16 if (post_captcha_enabled()) { 17 $cap = post('post_captcha'); 18 if (!post_captcha_verify($cap)) { 19 json_out(['error'=>'Incorrect security answer.','captcha_failed'=>true], 403); 20 } 21 } 22 23 $slug = sanitise(post('slug')); 24 $content = sanitise(post('content')); 25 $replyTo = (int)post('reply_to'); 26 27 if (!$slug || !$content) json_out(['error'=>'Missing required fields'],400); 28 if (strlen($content) < 1) json_out(['error'=>'Reply cannot be empty'],400); 29 if (strlen($content) > 20000) json_out(['error'=>'Post too long (max 20,000 chars)'],400); 30 31 $topic = DB::row('SELECT * FROM topics WHERE slug=?', [$slug]); 32 if (!$topic) json_out(['error'=>'Topic not found'],404); 33 if ($topic['closed']) json_out(['error'=>'This topic is closed'],403); 34 $replyCat = DB::row('SELECT * FROM categories WHERE id=?',[$topic['category_id']]); 35 if (!can_reply_topic($replyCat ?? [])) json_out(['error'=>'No permission to reply in this category'],403); 36 37 $last = DB::row('SELECT post_num FROM posts WHERE topic_id=? ORDER BY post_num DESC LIMIT 1',[$topic['id']]); 38 $num = ($last ? (int)$last['post_num'] : 0) + 1; 39 40 $pid = DB::insert( 41 'INSERT INTO posts (topic_id,user_id,content,post_num,reply_to) VALUES (?,?,?,?,?)', 42 [$topic['id'], $USER['id'], $content, $num, $replyTo ?: null] 43 ); 44 45 // Record rate limit event AFTER successful post 46 rate_record((int)$USER['id'], 'post'); 47 48 $now = DB::now(); 49 DB::run("UPDATE topics SET last_post_at=$now,reply_count=reply_count+1 WHERE id=?",[$topic['id']]); 50 DB::run('UPDATE users SET post_count=post_count+1 WHERE id=?',[$USER['id']]); 51 DB::run('UPDATE categories SET post_count=post_count+1 WHERE id=?',[$topic['category_id']]); 52 53 if ($topic['user_id'] !== $USER['id']) { 54 add_notification((int)$topic['user_id'],'reply',[ 55 'slug'=>$topic['slug'],'title'=>$topic['title'],'from'=>$USER['username'] 56 ]); 57 } 58 process_mentions($content, $pid, $topic['slug'], (int)$USER['id'], $USER['username']); 59 60 $post = DB::row(" 61 SELECT p.*,u.username,u.avatar,u.role,u.post_count,0 AS likes 62 FROM posts p JOIN users u ON u.id=p.user_id WHERE p.id=? 63 ",[$pid]); 64 65 // Send new captcha if post captcha enabled 66 $newCaptcha = post_captcha_enabled() ? post_captcha_generate() : null; 67 68 addon_hook('after_reply_saved', ['post_id'=>$pid,'topic_id'=>$topic['id'],'user_id'=>(int)$USER['id']]); 69 json_out(['ok'=>true,'post'=>$post,'new_captcha'=>$newCaptcha]);