1<?php
2require_once __DIR__ . '/../includes/bootstrap.php';
3must_login();
4
5$allCats = DB::rows('SELECT * FROM categories ORDER BY position, id');
6$cats = array_filter($allCats, fn($c) => can_post_topic($c));
7$selCat = (int)get('cat');
8$errs = [];
9
10if ($_SERVER['REQUEST_METHOD'] === 'POST') {
11 if (!csrf_ok()) { $errs[] = 'Invalid request.'; }
12 else {
13 $title = sanitise(post('title'));
14 $content = sanitise(post('content'));
15 $catId = (int)post('cat_id');
16 $tagStr = sanitise(post('tags'));
17
18 if (!$title) $errs[] = 'Title is required.';
19 if (!$content) $errs[] = 'Post content is required.';
20 if (!$catId) $errs[] = 'Please select a category.';
21 if (strlen($title) > 200) $errs[] = 'Title too long (max 200 chars).';
22 if (strlen($content) > 50000) $errs[] = 'Content too long.';
23
24 if (!$errs && !DB::row('SELECT id FROM categories WHERE id=?', [$catId])) $errs[] = 'Invalid category.';
25 if (!$errs) {
26 $postCat = DB::row('SELECT * FROM categories WHERE id=?', [$catId]);
27 if (!$postCat || !can_post_topic($postCat)) $errs[] = 'You do not have permission to post in this category.';
28 }
29
30 if (!$errs && cfg('topic_captcha_enabled', '0') === '1') {
31 if (!post_captcha_verify(post('topic_captcha'))) $errs[] = 'Incorrect security answer.';
32 }
33
34 if (!$errs) {
35 $rl = rate_check((int)$USER['id'], 'topic');
36 if (!$rl['ok']) {
37 $wait = (int)$rl['wait'];
38 $errs[] = "Slow down! Please wait {$wait} second" . ($wait !== 1 ? 's' : '') . '.';
39 }
40 }
41
42 if (!$errs) {
43 $slug = unique_slug($title, 'topics');
44 $tid = DB::insert('INSERT INTO topics (title,slug,category_id,user_id) VALUES (?,?,?,?)',
45 [$title, $slug, $catId, $USER['id']]);
46 $pid = DB::insert('INSERT INTO posts (topic_id,user_id,content,post_num) VALUES (?,?,?,1)',
47 [$tid, $USER['id'], $content]);
48
49 if ($tagStr) {
50 $names = array_slice(array_filter(array_map('trim', explode(',', mb_strtolower($tagStr)))), 0, 5);
51 foreach ($names as $n) {
52 if (!$n) continue;
53 $tg = DB::row('SELECT id FROM tags WHERE name=?', [$n]);
54 $tgId = $tg ? $tg['id'] : DB::insert('INSERT INTO tags (name) VALUES (?)', [$n]);
55 DB::insertIgnore('topic_tags', ['topic_id', 'tag_id'], [$tid, $tgId]);
56 DB::run('UPDATE tags SET topic_count=topic_count+1 WHERE id=?', [$tgId]);
57 }
58 }
59 DB::run('UPDATE users SET topic_count=topic_count+1,post_count=post_count+1 WHERE id=?', [$USER['id']]);
60 DB::run('UPDATE categories SET topic_count=topic_count+1,post_count=post_count+1 WHERE id=?', [$catId]);
61 process_mentions($content, $pid, $slug, (int)$USER['id'], $USER['username']);
62 rate_record((int)$USER['id'], 'topic');
63 addon_hook('after_topic_created', ['topic_id'=>$tid,'title'=>$title,'slug'=>$slug,'category_id'=>$catId,'user_id'=>(int)$USER['id']]);
64 go('forum/topic.php?slug=' . urlencode($slug));
65 }
66 }
67}
68
69$PAGE_TITLE = 'New Topic';
70include __DIR__ . '/../views/partials/layout.php';
71?>
72<nav class="bc"><a href="<?=u('/')?>">Home</a> βΊ <span>New Topic</span></nav>
73<div class="form-card">
74 <h1>Create a New Topic</h1>
75 <?php if($errs):?><div class="alert err"><?=implode('<br>',array_map('e',$errs))?></div><?php endif;?>
76 <form method="POST" id="ntForm">
77 <?=csrf_input()?>
78 <div class="fg">
79 <label>Category <span class="req">*</span></label>
80 <select name="cat_id" class="fi" required>
81 <option value="">β Select a category β</option>
82 <?php foreach($cats as $c):?>
83 <option value="<?=$c['id']?>" <?=($selCat==$c['id']||((int)($_POST['cat_id']??0))==$c['id'])?'selected':''?>>
84 <?=e($c['icon'])?> <?=e($c['name'])?>
85 </option>
86 <?php endforeach;?>
87 </select>
88 </div>
89 <div class="fg">
90 <label>Title <span class="req">*</span></label>
91 <input type="text" name="title" class="fi" required maxlength="200"
92 value="<?=e($_POST['title']??'')?>" placeholder="What is this about?">
93 </div>
94 <div class="fg">
95 <label>Tags <small>(optional, comma-separated)</small></label>
96 <input type="text" name="tags" id="tagsIn" class="fi" value="<?=e($_POST['tags']??'')?>" placeholder="help, tutorial">
97 <div id="tagPreview" class="tag-preview"></div>
98 </div>
99 <div class="fg">
100 <label>Content <span class="req">*</span></label>
101 <div class="ed-toolbar">
102 <button type="button" onclick="fmt('bold','replyTa')"><b>B</b></button>
103 <button type="button" onclick="fmt('italic','replyTa')"><i>I</i></button>
104 <button type="button" onclick="fmt('link','replyTa')">π</button>
105 <button type="button" onclick="fmt('quote','replyTa')" title="Quote"><svg viewBox="0 0 24 24" fill="currentColor" width="13" height="13" style="vertical-align:middle"><path d="M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z"/></svg></button>
106 <button type="button" onclick="fmt('codeblock','replyTa')">π</button>
107 <button type="button" onclick="fmt('heading','replyTa')">H</button>
108 <button type="button" onclick="fmt('ul','replyTa')">β‘</button>
109 <div class="ed-sep"></div>
110 <button type="button" onclick="pickImg('ntImg','replyTa')">πΌ</button>
111 <input type="file" id="ntImg" accept="image/*" style="display:none" onchange="uploadImg(this,'replyTa')">
112 <div class="ed-sep"></div>
113 <button type="button" id="prevBtn" onclick="togglePreview()">π Preview</button>
114 </div>
115 <div class="ed-panes">
116 <textarea id="replyTa" name="content" class="reply-ta" rows="14" required
117 placeholder="Write your post⦠Markdown supported. Tip: use @username to mention someone!"><?=e($_POST['content']??'')?></textarea>
118 <div id="replyPreview" class="reply-preview hidden"></div>
119 </div>
120 </div>
121 <?php if (cfg('topic_captcha_enabled','0') === '1'): ?>
122 <?php $tc = post_captcha_generate(); ?>
123 <div class="fg captcha-box">
124 <label class="captcha-label">π Security β What is <strong class="captcha-q"><?= e($tc['q']) ?></strong></label>
125 <input type="number" name="topic_captcha" class="fi captcha-input" required placeholder="Answer" autocomplete="off" min="-99" max="99">
126 <span class="hint">Solve this math problem to post</span>
127 </div>
128 <?php endif; ?>
129
130 <div class="form-actions">
131 <a href="<?=u('/')?>" class="btn-ghost">Cancel</a>
132 <button type="submit" class="btn-primary btn-lg">Post Topic</button>
133 </div>
134 </form>
135</div>
136<script>
137document.getElementById('tagsIn').addEventListener('input',function(){
138 var tags=this.value.split(',').map(function(t){return t.trim();}).filter(Boolean);
139 document.getElementById('tagPreview').innerHTML=tags.map(function(t){return '<span class="tag">'+escH(t)+'</span>';}).join('');
140});
141function escH(s){return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');}
142document.getElementById('replyTa').addEventListener('paste',function(e){
143 var items=(e.clipboardData||e.originalEvent.clipboardData).items;
144 for(var i=0;i<items.length;i++){if(items[i].type.indexOf('image')!==-1){e.preventDefault();uploadFileToEditor(items[i].getAsFile(),'replyTa');}}
145});
146</script>
147<?php include __DIR__ . '/../views/partials/layout_end.php'; ?>